如何在 QComboBox 中创建子菜单?我当前正在使用小部件提供的默认布局,但这会创建一个冗长的下拉列表,如附图所示。
QComboBox 通常使用 QListView 作为其弹出窗口。虽然可以通过调用
setView()
将其更改为 QTreeView,但其结果有时很麻烦,并且通常需要进一步调整才能使用。
最重要的是,视图不会使用更多弹出窗口,如果整个结构水平或垂直需要太多空间,或者层次结构太复杂,这可能会成为一个问题。
对于这种情况,一个更简单的解决方案是使用带有实际菜单的 QToolButton。
使用一些自定义函数和信号,您可以获得类似于 QComboBox 的行为,获取当前所选选项的文本(即使在其完整层次结构中)。
class FakeCombo(QToolButton):
currentItemChanged = pyqtSignal(str)
currentItemPathChanged = pyqtSignal(list)
_currentAction = None
def __init__(self, data=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setPopupMode(self.MenuButtonPopup)
menu = QMenu(self)
self.setMenu(menu)
menu.triggered.connect(self.setCurrentAction)
self.pressed.connect(self.showMenu)
if data:
self.setData(data)
def _currentActionPath(self):
if not self._currentAction:
return []
action = self._currentAction
path = [action]
while action.parent() != self.menu():
action = action.parent().menuAction()
path.append(action)
return reversed(path)
def _getActionsRecursive(self, parent):
for action in parent.actions():
if action.menu():
yield from self._getActionsRecursive(action.menu())
else:
yield action
def _rebuildList(self):
self._actions = tuple(self._getActionsRecursive(self.menu()))
def currentItem(self):
if not self._currentAction:
return ''
return self._currentAction.text()
def currentItemPath(self):
return [a.text() for a in self._currentActionPath()]
def setCurrentAction(self, action):
if self._currentAction == action:
return
if not isinstance(action, QAction):
action = None
self._currentAction = action
if action is None:
self.currentItemChanged.emit('')
self.currentItemPathChanged.emit([])
return
path = self.currentItemPath()
self.setText(': '.join(path))
self.currentItemChanged.emit(path[-1])
self.currentItemPathChanged.emit(path)
def setData(self, data):
menu = self.menu()
menu.clear()
if not data:
self.setCurrentAction(None)
return
for item in data:
self.addItem(item, menu)
self._rebuildList()
self.setCurrentAction(self._actions[0])
def addItem(self, item, parent):
if isinstance(item, str):
action = QAction(item, parent)
elif isinstance(item, (tuple, list)):
main, subitems = item
action = parent.addAction(main)
menu = QMenu()
action.setMenu(menu)
for other in subitems:
self.addItem(other, menu)
action.destroyed.connect(menu.clear)
parent.addAction(action)
return action
def mousePressEvent(self, event):
if self.menu().actions():
QAbstractButton.mousePressEvent(self, event)
def keyPressEvent(self, event):
if self.menu().actions() or event.key() != Qt.Key_Space:
super().keyPressEvent(event)
# simple example of data structure made of tuples:
# - "final" items are simple strings
# - groups are tuples made of a string and another tuple
DATA = (
'Top level item',
('Group #1', (
'sub item #1 ',
'sub item #2 ',
('Sub group', (
'sub-sub item #1',
'sub-sub item #2',
)),
)),
('Group #2', (
'sub item #3',
)),
)
app = QApplication([])
win = QWidget()
box = FakeCombo(DATA)
itemField = QLineEdit(readOnly=True)
pathField = QLineEdit(readOnly=True)
layout = QFormLayout(win)
layout.addRow('Options:', box)
layout.addRow('Current item:', itemField)
layout.addRow('Current path:', pathField)
def updateCurrent(item):
itemField.setText(item)
pathField.setText(', '.join(box.currentItemPath()))
box.currentItemChanged.connect(updateCurrent)
updateCurrent(box.currentItem())
win.show()
app.exec()
显然还有一些改进空间,例如允许滚轮和箭头键导航、在弹出窗口中突出显示当前项目等。