skip to Main Content

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:

enter image description here enter image description here enter image description here

Note that I am running this code in Maya but it should be the same problem in an QT environment I think.

2

Answers


  1. Chosen as BEST ANSWER

    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:

    button.setStyleSheet("font-size: 100px; padding-bottom: 10px")
    

    enter image description here


  2. For example this Python code creates a button where the text is not vertically aligned.

    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Ö_+:

    Vertical aspects of a font

    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 letter x (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 a Qt.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:

    Button using underscore character as text

    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:

    void QPushButton::paintEvent(QPaintEvent *)
    {
        QStylePainter p(this);
        QStyleOptionButton option;
        initStyleOption(
    &option);
        p.drawControl(QStyle::CE_PushButton, 
    option);
    }
    

    This can be ported in Python with the following:

    class CustomButton(QPushButton):
        def paintEvent(self, event):
            p = QStylePainter(self)
            option = QStyleOptionButton()
            self.initStyleOption(option)
            p.drawControl(QStyle.CE_PushButton, option)
    

    Since p.drawControl(QStyle.CE_PushButton, option) will use the option.text, we can draw a no-text button with the following:

    class CustomButton(QPushButton):
        def paintEvent(self, event):
            p = QStylePainter(self)
            option = QStyleOptionButton()
            option.text = ''
            self.initStyleOption(option)
            p.drawControl(QStyle.CE_PushButton, option)
    

    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 metrics height().

    Here is the final result:

    class CenterTextButton(QPushButton):
        def paintEvent(self, event):
            if not self.text().strip():
                super().paintEvent(event)
                return
    
            qp = QStylePainter(self)
    
            # draw the button without text
            opt = QStyleOptionButton()
            self.initStyleOption(opt)
            text = opt.text
            opt.text = ''
            qp.drawControl(QStyle.CE_PushButton, opt)
    
            fm = self.fontMetrics()
    
            # ignore mnemonics temporarily
            tempText = text.replace('&', '')
            if '&&' in text:
                # consider *escaped* & characters
                tempText += '&'
    
            p = QPainterPath()
            p.addText(0, 0, self.font(), tempText)
    
            # the relative center of the font metrics height
            fontCenter = fm.ascent() - fm.height() / 2
            # the relative center of the actual text
            textCenter = p.boundingRect().center().y()
            # here comes the magic...
            qp.translate(0, -(fontCenter + textCenter))
    
            # restore the original text and draw it as a real button text
            opt.text = text
            qp.drawControl(QStyle.CE_PushButtonLabel, opt)
    

    Further notes

    The above implementation is not perfect. Most importantly:

    • some styles draw a "focus outline", which normally is a dashed line around the text, and in that case a small dashed rectangle may be drawn at the center of the button; this can be completely disabled by doing opt.state &= ~QStyle.State_HasFocus before qp.drawControl(QStyle.CE_PushButton, opt), but may have effects in case stylesheets are being used;
    • as said, mnemonics that show underlined characters may result in unexpected behavior;
    • character display is not regular: the apparent visual center of a complex shape as that of one or more characters is not always the center of its bounding rectangle;
    • fonts are not reliable: while most common fonts are well designed, there are plenty of faulty fonts that are inconsistent to some extent (and depending on the given size); even fonts have bugs;
    • the above obviously does not consider buttons with icons;

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search