How can I vertically align the text in a large font QPushButton? For example this Python code creates a button where the text is not vertically aligned. I have tried everything I can think of to solve it but I can’t get it working.
from PySide2 import QtCore, QtGui, QtWidgets
button = QtWidgets.QPushButton("+") # "a" or "A"
button.setStyleSheet("font-size: 100px")
layout = QtWidgets.QVBoxLayout()
layout.addWidget(button)
window = QtWidgets.QWidget()
window.setLayout(layout)
window.show()'
Here is what the code above creates:
Note that I am running this code in Maya but it should be the same problem in an QT environment I think.
2
Answers
musicamante's comment is spot on: this is not being caused because of a vertical align issue, it is being caused because the lowercase letter is smaller than the uppercase letter (and the vertical align happens according to the uppercase letter's size).
Adding some padding is an easy way to fix the issue visually:
In reality, the text is vertically aligned: you are not considering how text is normally rendered.
Brief sum of vertical font elements
When dealing with text drawing, the font metrics (see typeface anatomy) should always be considered. Vertically speaking, a typeface always has a fundamental height, and its drawing is always based on the base line.
Consider the following image, which renders the text
xgdÖ_+
:The "baseline" is the vertical reference position from which every character is being drawn; you can consider it like the lines of a notebook: it’s where you normally place the bottom part of the circle of "o", or the dot of a question mark, and the bottom of letters such as "g" are normally drawn underneath that line.
Qt always uses the QFontMetrics of a given font, and it uses font metrics functions in order to properly display text; those functions always have some reference to the base line above.
From the base line, then, we can get the following relative distances:
descent()
: which is the distance to the lowest point of the characters (the descender);xHeight()
: the distance to the height of a lower case character that has no ascender, which normally is the height of the letterx
(see x-height);capHeight()
: the height of a standard upper case letter (see cap height);ascent()
: the maximum upper extent of a font above the x-height, normally (but not always) allowing further space for letters such "d", diacritic marks for upper case letters, or fancy decorations;Finally, the whole font
height()
is the sum of the ascent and descent. From there, you can get the "center" (often coinciding but not to be confused with the median or mean line), which is where aQt.AlignVCenter
aligned text would normally have as its virtual middle line when showing vertically centered text in Qt.Simply put (as more complex text layouts may complicate things), when Qt draws some text, it uses the font metrics
height()
as a reference, and then aligns the text on the computed distances considering the overall height and/or the ascent and descent of the metrics. Once the proper base line has been found, the text is finally drawn based on it.Vertical alignment is wrong (or not?)
When Qt aligns some text, it always considers the metrics of the font, which can be misleading. Consider the case above, and imagine that you wanted to use the underscore character (
_
) for your button, which clearly is placed way below the base line.The result would be something like this:
This is clearly not "vertically aligned". It seems wrong, but is it?
As you can see from the image above, the "+" symbol is not vertically aligned to the center in the font I’m using. But, even if it was, would it be valid?
For instance, consider a button with "x" as its text, but using that letter as its mnemonic shortcut, which is normally underlined. Even assuming that you can center the x, would it be properly aligned when underlined?
Some styles even show the underlined mnemonic only upon user interaction (usually by pressing Alt): should the text be vertically translated in that case?
Possible implementation
Now, it’s clear that considering the overall vertical alignment including the mnemonic is not a valid choice.
But there is still a possibility, using QStyle functions and some ingenuity.
QPushButton draws its contents using a relatively simple
paintEvent()
override:This can be ported in Python with the following:
Since
p.drawControl(QStyle.CE_PushButton, option)
will use theoption.text
, we can draw a no-text button with the following:Now comes the problem: how to draw the text.
While we could simply use QPainter functions such as
drawText()
, this won’t be appropriate, as we should consider style aspects that use custom drawing: for instance, disabled buttons, or style sheets.A more appropriate approach should consider the offset between the center of the font metrics and the visual center of the character(s) we want to draw.
QPainterPath allows us to add some text to a vector path and get its visual center based on its contents. So, we can use
addText()
and subtract the difference of its center from the center of the font metricsheight()
.Here is the final result:
Further notes
The above implementation is not perfect. Most importantly:
opt.state &= ~QStyle.State_HasFocus
beforeqp.drawControl(QStyle.CE_PushButton, opt)
, but may have effects in case stylesheets are being used;Finally, for simple symbols like in this case, using an icon is almost always the better choice.
Luckily, Qt also has SVG file support for icons, and it also provides a QIconEngine API that allows further customization.