【问题标题】:Directly show the menu when its action is added to a QToolBar当其操作添加到 QToolBar 时直接显示菜单
【发布时间】:2023-02-19 11:35:12
【问题描述】:

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

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

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

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

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

This answer 建议改为使用 QPushButton,并将该按钮添加到工具栏,但该解决方案并不理想:QPushButton 的样式与默认 QToolButton 略有不同,并且,如the documentation suggests,即使我使用 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】:

    一种可能性是使用 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))
    

    【讨论】:

      猜你喜欢
      • 2016-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多