如果 QMenu 中有菜单,则 QWidgetAction 是不可检查的

QWidgetAction in QMenu are un-checkable if it has menu within

我正在尝试将三态复选框实现到 QMenu 中。 我的菜单层次结构类似于:

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

其中第一层(menuA、menuB)是三态复选框,而其子项是普通复选框,使用 QAction 实现。

因此,使用 QWidgetActionQCheckBox,似乎我能够让三态在第一层工作。

然而,当我尝试将包含子项目的 setMenu 用于第一层项目时,即使它能够相应地显示子项目,这些选项也不再是可检查的。

最初我只使用 QAction 小部件但是当我迭代子项目时,第一层项目总是显示为完整检查,如果可能我想纠正它,因此我正在尝试使用三态的。

例如。如果选中 a101menuA 将设置为部分状态。如果 a101a102 都被选中,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_())

无法设置子菜单状态的原因是QMenu自动使用点击子菜单打开它,"consuming"点击事件。

要做到这一点,您必须确保用户点击的位置,如果它是您的 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)