QWidgetAction в QMenu не проверяется, если в нем есть меню

Я пытаюсь реализовать флажки с тремя состояниями в QMenu. Моя иерархия меню будет выглядеть примерно так:

menuA
    |-- a101
    |-- a102
menuB
    |-- b101

Где первый уровень (menuA, menuB) состоит из флажков с тремя состояниями, в то время как его подэлементы являются обычными флажками, реализованными с использованием QAction.

И так, с использованием QWidgetAction а также QCheckBoxПо-видимому, я могу заставить Tristate работать на уровне первого уровня.

Однако, как только я попытался использовать setMenu который содержит подэлементы в элементах первого уровня, опции больше не проверяются, даже если он может отображать подэлементы соответственно.

Первоначально я использую только виджеты QAction, но, поскольку я выполняю итерации подэлементов, элемент первого уровня всегда отображается как полная проверка, в которой я хотел бы исправить ее, если это возможно, и, следовательно, я пытаюсь использовать три состояния,

Например. Если a101 проверено, menuA будет установлен с частичным состоянием. Если оба a101 а также a102 проверены, menuA затем будет установлено (полное) состояние проверки.

class CustomCheckBox(QtGui.QCheckBox):
    def __init__(self, text="", parent=None):
        super(CustomCheckBox, self).__init__(text, parent=parent)

        self.setText(text)
        self.setTristate(True)


class QSubAction(QtGui.QAction):
    def __init__(self, text="", parent=None):
        super(QSubAction, self).__init__(text, parent)
        self.setCheckable(True)

        self.toggled.connect(self.checkbox_toggle)

    def checkbox_toggle(self, value):
        print value


class QCustomMenu(QtGui.QMenu):
    """Customized QMenu."""

    def __init__(self, title, parent=None):
        super(QCustomMenu, self).__init__(title=str(title), parent=parent)
        self.setup_menu()

    def mousePressEvent(self,event):
        action = self.activeAction()
        if not isinstance(action,QSubAction) and action is not None:
            action.trigger()
            return
        elif isinstance(action,QSubAction):
            action.toggle()
            return
        return QtGui.QMenu.mousePressEvent(self,event)

    def setup_menu(self):
        self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)

    def contextMenuEvent(self, event):
        no_right_click = [QAddAction]
        if any([isinstance(self.actionAt(event.pos()), instance) for instance in no_right_click]):
            return
        pos = event.pos()

    def addAction(self, action):
        super(QCustomMenu, self).addAction(action)


class MainApp(QtGui.QWidget):
    def __init__(self, parent=None):
        super(MainApp, self).__init__(parent)

        self.test_dict = {
            "testA" :{
                "menuA": ["a101", "a102"],
            },
            "testBC": {
                "menuC": ["c101", "c102", "c103"],
                "menuB": ["b101"]
            },
        }

        v_layout = QtGui.QVBoxLayout()
        self.btn1 = QtGui.QPushButton("TEST BTN1")
        v_layout.addWidget(self.btn1)

        self.setLayout(v_layout)

        self.setup_connections()

    def setup_connections(self):
        self.btn1.clicked.connect(self.button1_test)

    def button1_test(self):
        self.qmenu = QCustomMenu(title='', parent=self)

        for pk, pv in self.test_dict.items():
            base_qmenu = QCustomMenu(title=pk, parent=self)

            base_checkbox = CustomCheckBox(pk, base_qmenu)
            base_action = QtGui.QWidgetAction(base_checkbox)
            base_action.setMenu(base_qmenu) # This is causing the option un-checkable
            base_action.setDefaultWidget(base_checkbox)

            self.qmenu.addAction(base_action)

            for v in pv:
                action = QSubAction(v, self)
                base_qmenu.addAction(action)

        self.qmenu.exec_(QtGui.QCursor.pos())


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    w = MainApp()
    w.show()
    sys.exit(app.exec_())

1 ответ

Решение

Причина, по которой вы не можете установить состояние подменю, заключается в том, что QMenu автоматически использует щелчок по подменю, чтобы открыть его, "потребляя" событие щелчка.

Чтобы получить это, вы должны будете убедиться, где пользователь нажимает кнопку, и, если это один из ваших QWidgetActions, инициировать ее, гарантируя, что событие больше не будет распространяться.

Кроме того, логика три состояния добавляется в состояние детей, используя toggled сигнал, который проверяет все действия меню, чтобы определить фактическое состояние.

Обратите внимание, что contextMenuEvent (вместе с параметром политики меню) был удален.

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

class CustomCheckBox(QtGui.QCheckBox):
    def __init__(self, text="", parent=None):
        super(CustomCheckBox, self).__init__(text, parent=parent)

        self.setText(text)
        self.setTristate(True)

    def mousePressEvent(self, event):
        # only react to left click buttons and toggle, do not cycle
        # through the three states (which wouldn't make much sense)
        if event.button() == QtCore.Qt.LeftButton:
            self.toggle()

    def toggle(self):
        super(CustomCheckBox, self).toggle()
        newState = self.isChecked()
        for action in self.actions():
            # block the signal to avoid recursion
            oldState = action.isChecked()
            action.blockSignals(True)
            action.setChecked(newState)
            action.blockSignals(False)
            if oldState != newState:
                # if you *really* need to trigger the action, do it
                # only if the action wasn't already checked
                action.triggered.emit(newState)


class QSubAction(QtGui.QAction):
    def __init__(self, text="", parent=None):
        super(QSubAction, self).__init__(text, parent)
        self.setCheckable(True)


class QCustomMenu(QtGui.QMenu):
    """Customized QMenu."""

    def __init__(self, title, parent=None):
        super(QCustomMenu, self).__init__(title=str(title), parent=parent)

    def mousePressEvent(self,event):
        actionAt = self.actionAt(event.pos())
        if isinstance(actionAt, QtGui.QWidgetAction):
            # the first mousePressEvent is sent from the parent menu, so the
            # QWidgetAction found is one of the sub menu actions
            actionAt.defaultWidget().toggle()
            return
        action = self.activeAction()
        if not isinstance(action,QSubAction) and action is not None:
            action.trigger()
            return
        elif isinstance(action,QSubAction):
            action.toggle()
            return
        QtGui.QMenu.mousePressEvent(self,event)

    def addAction(self, action):
        super(QCustomMenu, self).addAction(action)
        if isinstance(self.menuAction(), QtGui.QWidgetAction):
            # since this is a QWidgetAction menu, add the action
            # to the widget and connect the action toggled signal
            action.toggled.connect(self.checkChildrenState)
            self.menuAction().defaultWidget().addAction(action)

    def checkChildrenState(self):
        actionStates = [a.isChecked() for a in self.actions()]
        if all(actionStates):
            state = QtCore.Qt.Checked
        elif any(actionStates):
            state = QtCore.Qt.PartiallyChecked
        else:
            state = QtCore.Qt.Unchecked
        self.menuAction().defaultWidget().setCheckState(state)
Другие вопросы по тегам