【问题标题】:QWidgetAction in QMenu are un-checkable if it has menu within如果 QMenu 中有菜单,则 QWidgetAction 是不可检查的
【发布时间】:2019-08-27 23:53:31
【问题描述】:

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

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

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

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

但是,一旦我尝试使用包含子项的setMenu 到第一层项中,即使它能够相应地显示子项,这些选项也不再可检查。

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

例如。如果选中a101,则menuA 将设置为部分状态。如果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_())

【问题讨论】:

    标签: python pyqt4 qmenu qcheckbox qwidgetaction


    【解决方案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)
    

    【讨论】:

    • 嗨@musciamante,谢谢你的回复。我注意到基本菜单缺少“右箭头”,这可能是由于使用了 QWidgetAction 吗?
    • 是的。我想 QMenu 只有在找到一个注册到菜单的 QAction 时才决定绘制一个箭头,这意味着它实际上绘制了 QAction 内容(因此它的功能:图标、检查/收音机和子菜单箭头),而如果有QWidgetAction 它只在它周围绘制框架,并让 defaultWidget() 完成其余的工作。如果你想画箭头,恐怕你需要自己做,可能通过添加箭头字符或增加 contentMargin() 的右组件并在paintEvent中绘制箭头。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-22
    • 2018-07-04
    • 1970-01-01
    相关资源
    最近更新 更多