简体   繁体   English

如何使 QToolButton 图标居中?

[英]How to center QToolButton icon?

when I connect a Qt QToolButton to a QAction that has an icon assigned to it, the icon shows in the QToolButton, but the icon is off-center and misaligned, see the image below:当我将 Qt QToolButton 连接到分配有图标的 QAction 时,图标显示在 QToolButton 中,但图标偏离中心且未对齐,请参见下图:

图标明显向左移动了几个像素

I am setting up the buttons like this (Python code):我正在像这样设置按钮(Python 代码):

self.actionExpand_All.setIcon(QIcon("icons_16:arrow-out.png"))
self.actionCollapse_All.setIcon(QIcon("icons_16:arrow-in.png"))

self.toolButton_expandAll.setDefaultAction(self.actionExpand_All)
self.toolButton_collapseAll.setDefaultAction(self.actionCollapse_All)

The icons come from the Fugue set and are 16x16 pngs.这些图标来自赋格曲集,大小为 16x16 png。 The toolButtonStyle is set to 'ToolButtonIconOnly'. toolButtonStyle 设置为“ToolButtonIconOnly”。 The QActions and the QToolButtons are defined via Qt Designer in a.ui file which I convert to Python via pyuic command. QActions 和 QToolButtons 是通过 Qt Designer 在 a.ui 文件中定义的,我通过 pyuic 命令将其转换为 Python。 I am using PyQt 6.4.我正在使用 PyQt 6.4。

I googled but could not find any solution, only mention of this problem from 2017 on StackExchange had some suggestions but none worked.我用谷歌搜索但找不到任何解决方案,只有从 2017 年开始在 StackExchange 上提到这个问题有一些建议,但都没有用。 I also tried centering the icon myself via QToolButton stylesheet fields such as 'margin' and 'padding' but to no avail.我还尝试通过 QToolButton 样式表字段(例如“边距”和“填充”)自己将图标居中,但无济于事。 I would be happy with making the QToolButton a bit bigger to center the icon but the QToolButton size seems to 'automatically' fit the icon and is not controlled from Qt Designer.我很乐意让 QToolButton 大一点以使图标居中,但 QToolButton 大小似乎“自动”适合图标,不受 Qt Designer 的控制。

Thanks谢谢

For some reason, Qt developers chose to return sizes that may result in odd numbers for the size hint of QToolButton.出于某种原因,Qt 开发人员选择返回可能导致 QToolButton 的大小提示为奇数的大小。

To understand that, we need to remember that Qt uses QStyle for many aspects, including indirect size management: many widgets query the current style() of the widget in order to compute correct size requirements for their contents, by calling styleFromContents() .要理解这一点,我们需要记住 Qt 在许多方面使用 QStyle,包括间接大小管理:许多小部件通过调用styleFromContents()查询小部件的当前style()以便为其内容计算正确的大小要求。

Almost all styles use a default QCommonStyle as basis for many aspects, and here we have the first issue.几乎所有 styles 都使用默认的QCommonStyle作为许多方面的基础,这里我们有第一个问题。

According to the sources, QCommonStyle does that when sizeFromContents() is called along with CT_ToolButton :根据消息来源,当sizeFromContents()CT_ToolButton一起调用时,QCommonStyle 会执行此操作:

QSize QCommonStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt,
                                     const QSize &csz, const QWidget *widget) const
{
    Q_D(const QCommonStyle);
    QSize sz(csz);
    switch (ct) {

    # ...

    case CT_ToolButton:
        sz = QSize(sz.width() + 6, sz.height() + 5);

    # ...

    }
    return sz;
}

As you can see, we already have a problem: assuming that the given csz is based on the icon size alone (which usually has even values, usually 16x16), we will get a final hint with an odd value for the height (eg. 22x21).如您所见,我们已经遇到了一个问题:假设给定的csz仅基于图标大小(通常具有偶数值,通常为 16x16),我们将得到一个具有奇数高度值的最终提示(例如。 22x21)。

This happens even in styles that don't rely on QCommonStyle, which is the case of the "Windows" style:即使在不依赖 QCommonStyle 的 styles 中也会发生这种情况,这是“Windows”样式的情况:

    case CT_ToolButton:
        if (qstyleoption_cast<const QStyleOptionToolButton *>(opt))
            return sz += QSize(7, 6);

Which seems partially consistent with your case, with the button border occupying 21 pixels: I suppose that the "missing" 2 pixels (16 + 7 = 23) are used as a margin, or for the "outset" border shown when the button is hovered.这似乎与您的情况部分一致,按钮边框占用 21 个像素:我假设“缺失”2 像素(16 + 7 = 23)用作边距,或者用于按钮时显示的“开始”边框徘徊。

Now, there are various possible solutions, depending on your needs.现在,有多种可能的解决方案,具体取决于您的需要。

Subclass QToolButton子类 QToolButton

If you explicitly need to use QToolButton, you can use a subclass that will "correct" the size hint:如果您明确需要使用 QToolButton,则可以使用一个子类来“更正”大小提示:

class ToolButton(QToolButton):
    def sizeHint(self):
        hint = super().sizeHint()
        if hint.width() & 1:
            hint.setWidth(hint.width() + 1)
        if hint.height() & 1:
            hint.setHeight(hint.height() + 1)
        return hint

This is the simplest solution, but it only works for QToolButtons created in python: it will not work for buttons of QToolBar.这是最简单的解决方案,但它只适用于在 python 中创建的 QToolButtons:它不适用于 QToolBar 的按钮。

You could even make it a default behavior without subclassing, with a little hack that uses some "monkey patching":您甚至可以在不进行子类化的情况下将其设为默认行为,只需使用一些“猴子修补”的小技巧:

def toolButtonSizeHint(btn):
    hint = btn.__sizeHint(btn)
    if hint.width() & 1:
        hint.setWidth(hint.width() + 1)
    if hint.height() & 1:
        hint.setHeight(hint.height() + 1)
    return hint
QToolButton.__sizeHint = QToolButton.sizeHint
QToolButton.sizeHint = toolButtonSizeHint

Note that the above code must be put as soon as possible in your script (possibly, in the main script, right after importing Qt), and should be used with extreme care, as "monkey patching" using class methods may result in unexpected behavior, and may be difficult to debug.请注意,以上代码必须尽快放入您的脚本中(可能在主脚本中,在导入 Qt 之后),并且使用时应格外小心,因为使用 class 方法的“猴子修补”可能会导致意外行为,并且可能难以调试。

Use a proxy style使用代理样式

QProxyStyle works as an "intermediary" within the current style and a custom implementation, which potentially overrides the default "base style". QProxyStyle在当前样式和自定义实现中充当“中介”,它可能会覆盖默认的“基本样式”。 You can create a QProxyStyle subclass and override sizeFromContents() :您可以创建一个 QProxyStyle 子类并覆盖sizeFromContents()

class ToolButtonStyle(QProxyStyle):
    def sizeFromContents(self, ct, opt, csz, w):
        size = super().sizeFromContents(ct, opt, csz, w)
        if ct == QStyle.CT_ToolButton:
            w = size.width()
            if w & 1:
                size.setWidth(w + 1)
            h = size.height()
            if h & 1:
                size.setHeight(h + 1)
        return size

# ...
if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle(ToolButtonStyle())

This has the benefit of working for any QToolButton, including those internally created for QToolBar.这具有适用于任何 QToolButton 的好处,包括那些在内部为 QToolBar 创建的。 But it's not perfect:但这并不完美:

  • if you set a stylesheet (for the toolbar, a parent, or the whole application), that alters aspects of the look of QToolButtons which may affect the size (most importantly, borders), the override will be ignored: the private QStyleSheetStyle never calls QProxyStyle overridden functions whenever the style sheet may affect them;如果您设置样式表(用于工具栏、父级或整个应用程序),它会改变 QToolButtons 外观的各个方面,这可能会影响大小(最重要的是边框),覆盖将被忽略:私有 QStyleSheetStyle从不调用只要样式表可能影响 QProxyStyle 覆盖函数;
  • only an application wide style ( QApplication.setSheet() ) is propagated to all widgets, but setting a QStyle to a widget will not propagate the style to its children: toolbar.setStyle() will not change the style (and resulting size) of its buttons;只有应用程序范围的样式( QApplication.setSheet() )传播到所有小部件,但是将 QStyle 设置为小部件不会将样式传播到其子项: toolbar.setStyle()不会更改样式(和结果大小)它的按钮;

Subclass QToolBar and set minimum sizes子类化 QToolBar 并设置最小尺寸

Another possibility is to subclass QToolBar and explicitly set a minimum size (based on their hints) for all QToolButton created for its actions.另一种可能性是子类化 QToolBar 并为所有为其操作创建的 QToolButton显式设置最小大小(基于它们的提示)。 In order to do that, we need a small hack: access to functions (and overwriting virtuals) on objects created outside Python is not allowed, meaning that we cannot just try to "monkey patch" things like sizeHint() at runtime;为了做到这一点,我们需要一个小技巧:不允许访问在 Python 之外创建的对象上的函数(和覆盖虚拟),这意味着我们不能仅仅尝试在运行时“猴子修补”诸如sizeHint()之类的东西; the only solution is to react to LayoutRequest events and always set an explicit minimum size whenever the hint of the QToolButton linked to the action does not use even numbers for its values.唯一的解决方案是对LayoutRequest事件做出反应,并且只要链接到操作的 QToolButton 提示不使用偶数作为其值,始终设置明确的最小大小。

class ToolBar(QToolBar):
    def event(self, event):
        if event.type() == event.LayoutRequest:
            for action in self.actions():
                if not isinstance(action, QWidgetAction):
                    btn = self.widgetForAction(action)
                    hint = btn.sizeHint()
                    w = hint.width()
                    h = hint.height()
                    if w & 1 or h & 1:
                        if w & 1:
                            w += 1
                        if h & 1:
                            h += 1
                        btn.setMinimumSize(w, h)
                    else:
                        btn.setMinimumSize(0, 0)
        return super().event(event)

This will work even if style sheets have effect on tool bars and QToolButtons.即使样式表对工具栏和 QToolButtons 有影响,这也会起作用。 It will obviously have no effect for non-toolbar buttons, but you can still use the first solution above.它显然对非工具栏按钮没有影响,但你仍然可以使用上面的第一个解决方案。 In the rare case you explicitly set a minimum size on a tool button (not added using addWidget() , that size would be potentially reset.在极少数情况下,您明确设置了工具按钮的最小尺寸(不是使用addWidget()添加的,该尺寸可能会被重置。

Final notes最后的笔记

  • solutions 1 and 3 only work when explicitly creating objects (QToolButton or QToolBar) from python, not when using ui or pyuic generated files;解决方案 1 和 3 仅在从 python 显式创建对象(QToolButton 或 QToolBar)时有效,在使用 ui 或 pyuic 生成的文件时无效; for that, you'll need to use promoted widgets;为此,您需要使用升级的小部件;
  • considering the common implementation of sizeFromContents() you can probably subtract the "extra odd pixel" instead of adding it, but there's no guarantee that it will always work: some styles might completely ignore borders and margins, which would potentially result in unexpected behavior and display;考虑到sizeFromContents()的常见实现,您可能可以减去“额外的奇数像素”而不是添加它,但不能保证它总是有效:一些 styles 可能会完全忽略边框和边距,这可能会导致意外行为和展示;
  • not all styles do the above: for instance, it seems like the "Oxygen" style always uses even values for QToolButton sizes;并非所有 styles 都执行上述操作:例如,“Oxygen”样式似乎总是对 QToolButton 大小使用偶数值;
  • any of the above considers the case of tool buttons that may display text, in case no icon is set, or if the button style (of the button or the tool bar) also requires text to be shown and a text actually exists;以上任何一种情况都考虑了可能显示文本的工具按钮的情况,如果没有设置图标,或者按钮样式(按钮或工具栏的)也需要显示文本并且实际存在文本; you can check that by using the relative functions of QToolButton in case of subclassing, by querying the given QStyleOption ( opt , above);您可以通过在子类化的情况下使用 QToolButton 的相关函数来检查,通过查询给定的 QStyleOption ( opt ,上面);
  • you may consider filing a report on the Qt bug manager about this;您可以考虑就此向Qt 错误管理器提交报告;

Note that while the linked post could make this as a duplicate, I don't know C++ enough to provide an answer to that.请注意,虽然链接的帖子可能会使它重复,但我不知道 C++ 足以提供答案。 To anybody reading this, feel free to make that one as a duplicate of this, or post a related answer with appropriate code in that language.对于阅读本文的任何人,请随时将其复制为本文的副本,或使用该语言的适当代码发布相关答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM