当它的动作添加到 QToolBar 时直接显示菜单

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

我有一个要添加到 QToolBar 的菜单。

我知道我可以将菜单的 menuAction() 添加到工具栏,但是虽然这会在其侧面正确显示“菜单提示”并通过单击它弹出菜单,但单击按钮的主要区域将没有效果。

该操作在触发时不应有任何结果:菜单用于在文本编辑器中设置字体颜色,并且由于它会根据当前颜色自动更新其图标,使其可检查(设置/取消设置字体颜色)是无效的。

我想要的是无论用户点击哪里,菜单都会显示。

我知道我可以添加动作,然后使用 widgetForAction() 获取实际的 QToolButton,然后更改它的 popupMode,但是因为我知道我会有更多这样的情况,所以我一直在寻找更好的方法。

这个答案建议改用 QPushButton,并将该按钮添加到工具栏,但该解决方案并不理想:QPushButton 的样式与默认 QToolButton 略有不同,而且,正如文档所建议的那样,即使我使用QToolButton 它不会尊重 ToolButtonStyle.

这是我当前代码的基本 MRE。请考虑 ColorMenu 类旨在通过使用子类扩展其他功能(背景文本、表格边框和背景的颜色等):

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


class ColorMenu(QMenu):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setTitle('Text color')

        self.group = QActionGroup(self)

        iconSize = self.style().pixelMetric(QStyle.PM_LargeIconSize)

        pm = QPixmap(iconSize, iconSize)
        pm.fill(self.palette().text().color())
        self.defaultAction = self.addAction(QIcon(pm), 'Default color')
        self.defaultAction.setCheckable(True)
        self.group.addAction(self.defaultAction)

        self.addSeparator()

        self.customColorAction = self.addAction('Custom color')
        self.customColorAction.setVisible(False)
        self.customColorAction.setCheckable(True)
        self.group.addAction(self.customColorAction)

        self.addSeparator()

        self.baseColorActions = []
        colors = {}
        # get valid global colors
        for key, value in Qt.__dict__.items():
            if (
                isinstance(value, Qt.GlobalColor)
                and 1 < value < 19
            ):
                # make names more readable
                if key.startswith('light'):
                    key = 'light {}'.format(key[5:].lower())
                elif key.startswith('dark'):
                    key = 'dark {}'.format(key[4:].lower())
                colors[value] = key.capitalize()

        # more logical sorting of global colors
        for i in (2, 4, 5, 6, 3, 7, 13, 8, 14, 9, 15, 10, 16, 11, 17, 12, 18):
            color = QColor(Qt.GlobalColor(i))
            pm = QPixmap(iconSize, iconSize)
            pm.fill(color)
            action = self.addAction(QIcon(pm), colors[i])
            action.setData(color)
            action.setCheckable(True)
            self.group.addAction(action)
            self.baseColorActions.append(action)

        self.setColor(None)

    def setColor(self, color):
        if isinstance(color, QBrush) and color.style():
            color = color.color()
        elif isinstance(color, (Qt.GlobalColor, int):
            color = QColor(color)
        if instance(color, QColor) and color.isValid():
            for action in self.baseColorActions:
                if action.data() == color:
                    self.setIcon(action.icon())
                    action.setChecked(True)
                    self.customColorAction.setVisible(False)
                    break
            else:
                iconSize = self.style().pixelMetric(QStyle.PM_LargeIconSize)
                pm = QPixmap(iconSize, iconSize)
                pm.fill(color)
                icon = QIcon(pm)
                self.setIcon(icon)

                self.customColorAction.setIcon(icon)
                self.customColorAction.setData(color)
                self.customColorAction.setVisible(True)
                self.customColorAction.setChecked(True)
            return

        self.setIcon(self.defaultAction.icon())
        self.defaultAction.setChecked(True)
        self.customColorAction.setVisible(False)


class Editor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.editor = QTextEdit()
        self.setCentralWidget(self.editor)

        self.formatMenu = self.menuBar().addMenu('Format')
        self.colorMenu = ColorMenu(self)
        self.formatMenu.addMenu(self.colorMenu)

        self.toolbar = QToolBar('Format')
        self.addToolBar(Qt.TopToolBarArea, self.toolbar)
        self.toolbar.addAction(self.colorMenu.menuAction())

        self.editor.currentCharFormatChanged.connect(self.updateColorMenu)
        self.colorMenu.triggered.connect(self.setTextColor)

    def setTextColor(self, action):
        # assume that the action.data() has a color value, if not, revert to the default
        if action.data():
            self.editor.setTextColor(action.data())
        else:
            tc = self.editor.textCursor()
            fmt = tc.charFormat()
            fmt.clearForeground()
            tc.setCharFormat(fmt)

    def updateColorMenu(self, fmt):
        self.colorMenu.setColor(fmt.foreground())


app = QApplication([])
editor = Editor()
editor.show()
app.exec()
qt pyqt pyqt5 pyqt6
1个回答
0
投票

一种可能性是使用 QMenu 的子类并实现将返回专用操作的专门函数。

这有点 hack/workaround,但在某些情况下可能有效。

新行动将:

  • 通过提供工具栏来创建,它将成为其父级(以确保在工具栏被销毁时正确删除);
  • 触发时强制显示菜单;
  • 每当菜单操作更改时自行更新(标题和图标);
class ColorMenu(QMenu):
    # ...
    def toolBarAction(self, toolbar):
        def triggerMenu():
            try:
                button = toolbar.widgetForAction(action)
                if isinstance(button, QToolButton):
                    button.showMenu()
                else:
                    print('Warning: action triggered from somewhere else')
            except (TypeError, RuntimeError):
                # the action has been destroyed
                pass

        def updateAction():
            try:
                action.setIcon(self.icon())
                action.setText(self.title())
            except (TypeError, RuntimeError):
                # the action has been destroyed
                pass

        action = QAction(self.icon(), self.title(), toolbar)
        action.triggered.connect(triggerMenu)
        self.menuAction().changed.connect(updateAction)
        action.setMenu(self)
        return action


class Editor(QMainWindow):
    def __init__(self):
        # ...

        # replace the related line with the following
        self.toolbar.addAction(self.colorMenu.toolBarAction(self.toolbar))
© www.soinside.com 2019 - 2024. All rights reserved.