向 QComboBox 添加子菜单

问题描述 投票:0回答:1

如何在 QComboBox 中创建子菜单?我当前正在使用小部件提供的默认布局,但这会创建一个冗长的下拉列表,如附图所示。

image

python pyqt pyside6
1个回答
0
投票

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()

显然还有一些改进空间,例如允许滚轮和箭头键导航、在弹出窗口中突出显示当前项目等。

© www.soinside.com 2019 - 2024. All rights reserved.