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)