将菜单添加到 QToolBar 时,直接显示菜单。

huangapple go评论76阅读模式
英文:

Directly show the menu when its action is added to a QToolBar

问题

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

我知道可以将菜单的 menuAction() 添加到工具栏,但这样会正确显示其侧边的“菜单提示”,并通过单击它来弹出菜单,但单击按钮的主区域将不会产生任何效果。

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

我想要的是,无论用户在何处点击,菜单都会显示。

我知道可以添加操作,然后使用 widgetForAction() 获取实际的 QToolButton,然后更改其 popupMode,但因为我知道将有更多类似的情况,我正在寻找更好的方法。

这个答案 建议使用 QPushButton,然后将该按钮添加到工具栏,但该解决方案并不理想:QPushButton 的样式与默认的 QToolButton 稍有不同,并且正如文档建议的,即使我使用 QToolButton,它也不会遵守 ToolButtonStyle

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

# 以上是代码,无法提供翻译
英文:

I have a menu that I want to add to a QToolBar.

I know that I can add the menuAction() of the menu to the tool bar, but while that will properly show the "menu hint" on its side and popup the menu by clicking on it, clicking the main area of the button will have no effect.

That action is not supposed to have any result when triggered: the menu is used to set the font color in a text editor, and since it automatically updates its icon based on the current color, making it checkable (to set/unset the font color) is ineffective.

What I want is that the menu will be shown, no matter where the user clicks.

I know that I can add the action, then use widgetForAction() to get the actual QToolButton, and then change its popupMode, but since I know that I will have more situations like this, I was looking for a better approach.

This answer suggests to use QPushButton instead, and add that button to the toolbar, but that solution is not ideal: QPushButton is styled slightly differently from the default QToolButton, and, as the documentation suggests, even if I use a QToolButton it will not respect the ToolButtonStyle.

Here is a basic MRE of my current code. Please consider that the ColorMenu class is intended to be extended for other features (background text, colors for table borders and backgrounds, etc) by using subclasses:

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

答案1

得分: 1

这是一个使用QMenu的子类并实现一个专门返回专用操作的方法的可能性。

这有点像是一个小技巧或变通方法,但在某些情况下可能是有效的。

新操作将会:

  • 通过提供工具栏创建,工具栏将成为其父对象(以确保在工具栏被销毁时能正确删除);
  • 当触发时强制显示菜单;
  • 当菜单操作被更改时更新自身(标题和图标);
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):
                # 该操作已被销毁
                pass

        def updateAction():
            try:
                action.setIcon(self.icon())
                action.setText(self.title())
            except (TypeError, RuntimeError):
                # 该操作已被销毁
                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):
        # ...

        # 将相关行替换为以下内容
        self.toolbar.addAction(self.colorMenu.toolBarAction(self.toolbar))
英文:

A possibility is to use a subclass of QMenu and implement a specialized function that will return a dedicated action.

This is a bit of a hack/workaround, but may be effective in some situations.

That new action will:

  • be created by providing the tool bar, which will become its parent (to ensure proper deletion if the tool bar is destroyed);
  • forcibly show the menu when triggered;
  • update itself (title and icon) whenever the menu action is changed;
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))

</details>



huangapple
  • 本文由 发表于 2023年2月19日 11:27:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/75497795.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定