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:
I am setting up the buttons like this (Python code):
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. The toolButtonStyle is set to 'ToolButtonIconOnly'. The QActions and the QToolButtons are defined via Qt Designer in a .ui file which I convert to Python via pyuic command. I am using 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. I also tried centering the icon myself via QToolButton stylesheet fields such as 'margin' and 'padding' but to no avail. 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.
Thanks
For some reason, Qt developers chose to return sizes that may result in odd numbers for the size hint of 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 callingstyleFromContents()
.Almost all styles use a default QCommonStyle as basis for many aspects, and here we have the first issue.
According to the sources, QCommonStyle does that when
sizeFromContents()
is called along withCT_ToolButton
: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).This happens even in styles that don't rely on QCommonStyle, which is the case of the "Windows" style:
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.
Now, there are various possible solutions, depending on your needs.
Subclass QToolButton
If you explicitly need to use QToolButton, you can use a subclass that will "correct" the size hint:
This is the simplest solution, but it only works for QToolButtons created in python: it will not work for buttons of QToolBar.
You could even make it a default behavior without subclassing, with a little hack that uses some "monkey patching":
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.
Use a proxy style
QProxyStyle works as an "intermediary" within the current style and a custom implementation, which potentially overrides the default "base style". You can create a QProxyStyle subclass and override
sizeFromContents()
:This has the benefit of working for any QToolButton, including those internally created for QToolBar. But it's not perfect:
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;Subclass QToolBar and set minimum sizes
Another possibility is to subclass QToolBar and explicitly set a minimum size (based on their hints) for all QToolButton created for its actions. 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; the only solution is to react toLayoutRequest
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.This will work even if style sheets have effect on tool bars and 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.Final notes
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;opt
, above);Note that while the linked post could make this as a duplicate, I don't know C++ enough to provide an answer to that. 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.