QMenu 中的 QWidgetAction 如果其中包含菜单,则无法检查



我正在尝试在QMenu中实现三态复选框。 我的菜单层次结构将是这样的:

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

其中第一层(菜单A,菜单B(是三态复选框,而其子项是使用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_())

无法设置子菜单状态的原因是 QMenu 自动使用子菜单上的单击将其打开,从而"消耗"单击事件。

要做到这一点,你必须确保用户点击的位置,如果它是你的QWidgetActions之一触发它,请确保事件不会进一步传播。

此外,三态逻辑被添加到子状态中,使用检查所有菜单操作以确定实际状态的toggled信号。

请注意,上下文菜单事件(以及菜单策略设置(已被删除。

最后,请考虑不建议使用不会触发菜单项中操作的复选框,因为这违反直觉,因为它违背了菜单项的预期行为。

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)

最新更新