QMenu mousePressEvent 阻碍了 QAction 切换

Posted

技术标签:

【中文标题】QMenu mousePressEvent 阻碍了 QAction 切换【英文标题】:QAction toggling is hinder by QMenu mousePressEvent 【发布时间】:2019-04-03 01:00:23 【问题描述】:

我有一个嵌套的菜单项,我试图在其中使所有项都可以检查。最初,切换不适用于主项目(设置为 QAction 的 QMenu),而它适用于子项目。

但是在使用mousePressEvent 时,切换现在适用于主要项目,但不适用于子项目。

试图复制我为子项目功能的主要项目所做的事情,但切换仍然不起作用。看起来,它在_callActionItem() 中被调用了两次。

但是,由于某些原因,如果子项目窗口处于撕下模式,则可以切换,但如果您在工具本身中执行右键单击菜单,则不能切换。

此外,如果我在 QCustomMenu 中禁用 mousePressEvent,我将回到第一格,其中切换适用于子项目,但不适用于主项目

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


class QAddAction(QtGui.QAction):
    def __init__(self, icon=None, text="Add Item", parent=None):
        if icon:
            super(QAddAction, self).__init__(icon, text, parent)
        else:
            super(QAddAction, self).__init__(text, parent)

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 isinstance(action, QtGui.QAction):
            action.trigger()
        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 Example(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(Example, self).__init__(parent)
        self.initUI()

    def initUI(self):         
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Context menu')   

        self.qmenu = QCustomMenu(title='', parent=self)
        add_item_action = QtGui.QAction('Add Main item', self,
            triggered=self.add_new_item)
        self.qmenu.addAction(add_item_action)

    def contextMenuEvent(self, event):
        action = self.qmenu.exec_(self.mapToGlobal(event.pos()))

    def add_new_item(self):
        main_menu_name, ok = QtGui.QInputDialog.getText(
            self,
            'Main Menu',
            'Name of new Menu Item:'
        )
        if ok:
            self._addMenuItemTest(main_menu_name)

    def _addMenuItemTest(self, main_menu_name):
        icon_path = '/user_data/add.png'

        base_qmenu = QCustomMenu(title=main_menu_name, parent=self)
        base_qmenu.setTearOffEnabled(True)                     

        add_item_action = QAddAction(None, 'Add Sub Item', base_qmenu)
        slot = functools.partial(self.add_sub_item, base_qmenu)
        add_item_action.triggered.connect(slot)
        base_qmenu.addAction(add_item_action)

        test_action = QtGui.QAction(main_menu_name, self)
        test_action.setMenu(base_qmenu)
        test_action.setCheckable(True)
        test_action.setChecked(True)

        self.connect(
            test_action,
            QtCore.SIGNAL("triggered(bool)"),
            self.main_toggling
        )

        self.qmenu.addAction(test_action)

    def main_toggling(self, check_state):
        sender_obj = self.sender()
        if isinstance(sender_obj, QtGui.QAction):
            sender_obj.setChecked(check_state)

    def add_sub_item(self, base_menu):
        sub_menu_name, ok = QtGui.QInputDialog.getText(
            self,
            'Sub Menu',
            'Name of new Sub Item:'
        )
        if ok:
            action = QSubAction(sub_menu_name, self)
            slot = functools.partial(
                self._callActionItem,
                action
            )
            # action.toggled.connect(slot)
            # from pprint import pprint
            # pprint(help(action))
            # action.connect(action, QtCore.SIGNAL("triggered(bool)"), self._callActionItem)

            base_menu.addAction(action)

    def _callActionItem(self, action):
        # This is called twice, False and True again
        print '>>> sub check-state : ', action.isChecked()


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

【问题讨论】:

【参考方案1】:

默认情况下,QMenu 只会使没有子项的 QAction 可以检查,因此您已经做了一个技巧来启用另一个不符合上述可能影响操作的 QAction 的功能,这就是您的情况。解决方法是区分按下的是哪种类型的QAction:

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

【讨论】:

抱歉,评论有点晚了,但我想了解为什么action 会返回两次——一次是QSubActionNone。尤其是None来自哪里?

以上是关于QMenu mousePressEvent 阻碍了 QAction 切换的主要内容,如果未能解决你的问题,请参考以下文章

mousePressEvent 没有正确绑定?

QWidget::mousePressEvent() 同时在两个小部件上

无法隐藏 QMenu 对象 QMenu::setVisible()?

如何在 QPushButtons 上使用 mousePressEvent

在 QTableWidgetItem 中设置 mousePressEvent 处理程序

如何知道触发了哪个 QMenu 的动作