Изменение визуального флажка QGroupBox на расширитель - PullRequest
2 голосов
/ 04 мая 2019

Я изменил поведение флажка QGroupBox, чтобы скрыть / показать дочерние элементы группы, эффективно выступая в качестве расширителя. Он отлично работает, но единственная проблема заключается в том, что значок флажка по умолчанию выглядит так, будто он предназначен для включения / отключения группы, а не для ее расширения. Вместо этого я хотел бы заменить его значком в стиле расширителя.

Я нашел этот пост, который почти отвечает на мой вопрос (и в конечном итоге мне может понадобиться это решение): Изменить изображение флажка PySide QGroupBox . Проблема в том, что ответ, предложенный там, предлагает использовать пользовательские изображения-флажки, тогда как я хочу использовать встроенный специфичный для ОС экспандер, такой как в QTreeView, который выглядит так на моем ПК: QTreeView expanders

Это вообще возможно? Считается ли расширитель стилизованным флажком или чем-то еще? Я довольно новичок в Qt, поэтому я не очень знаком с тем, как обрабатываются стили. Это то, что я мог бы запросить, и если да, будет ли оно совместимо со стилями QCheckBox? На странице документации QTreeView я мало что мог найти о расширителях, кроме как просто включить / отключить их, и в настоящее время я пытаюсь покопаться в исходном коде QTreeView.cpp, чтобы выяснить, как они работают.

Обновление

Исходя из ответа eyllanesc, я все ближе к решению, но у меня есть некоторые проблемы, переопределяющие методы QProxyStyle. Я пытаюсь сделать что-то вроде следующего:

class GroupBoxExpanderStyle(QtWidgets.QProxyStyle):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._replaceCheckboxWithExpander = False

    def drawComplexControl(self, control, option, painter, widget):
        try:
            if control == QtWidgets.QStyle.CC_GroupBox and widget.isCheckable():
                self._replaceCheckboxWithExpander = True
            super().drawComplexControl(control, option, painter, widget)
        finally:
            self._replaceCheckboxWithExpander = False

    def drawPrimitive(self, element, option, painter, widget):
        if element == QtWidgets.QStyle.PE_IndicatorCheckBox and self._replaceCheckboxWithExpander:
            indicatorBranchOption = ... # Set up options for drawing PE_IndicatorBranch
            super().drawPrimitive(QtWidgets.QStyle.PE_IndicatorBranch, indicatorBranchOption, painter, widget)
        else:
            super().drawPrimitive(element, option, painter, widget)

Нечто подобное, похоже, было сделано в этом коде . Проблема, с которой я сталкиваюсь, заключается в том, что моя переопределенная функция drawPrimitive() вообще не вызывается для виджетов QGroupBox ... но она вызывается для других виджетов с таким же стилем прокси! Если посмотреть на drawComplexControl() функцию qcommonstyle.cpp , случай CC_GroupBox вызывает

proxy()->drawPrimitive(PE_IndicatorCheckBox, &box, p, widget);

чтобы установить флажок, поэтому я не понимаю, почему моя переопределенная функция не работает.

Я не совсем уверен, как отладить это, так как я не могу войти в код C ++, чтобы увидеть, что на самом деле вызывается. Может ли кто-нибудь предложить какие-либо предложения, которые могут помочь мне понять, почему мой переопределенный drawPrimitive() не вызывается?

Обновление 2:

Я разгадал загадку, почему мой переопределенный drawPrimitive() не вызывается - это потому, что мое приложение использует таблицу стилей корневого уровня, в результате чего QStyleSheetStyle используется в качестве активного стиля для виджетов. QStyleSheetStyle напрямую вызывает собственный drawPrimitive() метод для CC_GroupBox вместо вызова proxy()->drawPrimitive() - мне это кажется ошибкой, и на самом деле эта ошибка Qt утверждает, что таблицы стилей не смешиваются хорошо с QProxyStyle. Я попытаюсь отказаться от использования таблиц стилей.

Техника eyllanesc работает со стилем Fusion, поэтому я принял его ответ, но он несовместим с другими стилями.

1 Ответ

1 голос
/ 04 мая 2019

Одним из возможных решений является реализация QProxyStyle:

from PySide2 import QtCore, QtGui, QtWidgets


class GroupBoxProxyStyle(QtWidgets.QProxyStyle):
    def subControlRect(self, control, option, subControl, widget):
        ret = super(GroupBoxProxyStyle, self).subControlRect(
            control, option, subControl, widget
        )
        if (
            control == QtWidgets.QStyle.CC_GroupBox
            and subControl == QtWidgets.QStyle.SC_GroupBoxLabel
            and widget.isCheckable()
        ):
            r = self.subControlRect(
                QtWidgets.QStyle.CC_GroupBox,
                option,
                QtWidgets.QStyle.SC_GroupBoxCheckBox,
                widget,
            )
            ret.adjust(r.width(), 0, 0, 0)
        return ret

    def drawComplexControl(self, control, option, painter, widget):
        is_group_box = False
        if control == QtWidgets.QStyle.CC_GroupBox and widget.isCheckable():
            option.subControls &= ~QtWidgets.QStyle.SC_GroupBoxCheckBox
            is_group_box = True
        super(GroupBoxProxyStyle, self).drawComplexControl(
            control, option, painter, widget
        )
        if is_group_box and widget.isCheckable():
            opt = QtWidgets.QStyleOptionViewItem()
            opt.rect = self.proxy().subControlRect(
                QtWidgets.QStyle.CC_GroupBox,
                option,
                QtWidgets.QStyle.SC_GroupBoxCheckBox,
                widget,
            )
            opt.state = QtWidgets.QStyle.State_Children
            opt.state |= (
                QtWidgets.QStyle.State_Open
                if widget.isChecked()
                else QtWidgets.QStyle.State_None
            )
            self.drawPrimitive(
                QtWidgets.QStyle.PE_IndicatorBranch, opt, painter, widget
            )


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    style = GroupBoxProxyStyle(app.style())
    app.setStyle(style)

    w = QtWidgets.QGroupBox(title="Exclusive Radio Buttons")
    w.setCheckable(True)
    vbox = QtWidgets.QVBoxLayout()
    for text in ("Radio button 1", "Radio button 2", "Radio button 3"):
        radiobutton = QtWidgets.QRadioButton(text)
        vbox.addWidget(radiobutton)
    vbox.addStretch(1)
    w.setLayout(vbox)

    w.resize(320, 240)
    w.show()
    sys.exit(app.exec_())

enter image description here

enter image description here

Обновление:

Преобразование кода , предоставленного OP в Python, выглядит следующим образом:

from PySide2 import QtCore, QtGui, QtWidgets


class GroupBoxProxyStyle(QtWidgets.QProxyStyle):
    def drawPrimitive(self, element, option, painter, widget):
        if element == QtWidgets.QStyle.PE_IndicatorCheckBox and isinstance(
            widget, QtWidgets.QGroupBox
        ):
            super().drawPrimitive(
                QtWidgets.QStyle.PE_IndicatorArrowDown
                if widget.isChecked()
                else QtWidgets.QStyle.PE_IndicatorArrowRight,
                option,
                painter,
                widget,
            )
        else:
            super().drawPrimitive(element, option, painter, widget)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    style = GroupBoxProxyStyle(app.style())
    app.setStyle(style)

    w = QtWidgets.QGroupBox(title="Exclusive Radio Buttons")
    w.setCheckable(True)
    vbox = QtWidgets.QVBoxLayout()
    for text in ("Radio button 1", "Radio button 2", "Radio button 3"):
        radiobutton = QtWidgets.QRadioButton(text)
        vbox.addWidget(radiobutton)
    vbox.addStretch(1)
    w.setLayout(vbox)

    w.resize(320, 240)
    w.show()
    sys.exit(app.exec_())
...