The font of a stylesheeted spinbox cannot be resized

Viewed 458

Modifying the fontsize of a QSpinBox has no effect if a stylesheet was applied to it, while all the other widget classes I tested are resized properly regardless of their stylesheet. QSpinBoxes without a stylesheet are also resized properly. Note that all the widgets' .font().pointSize() are equal so I think this is only a display issue.

A possible workaround would be to save the current stylesheet, set it to None temporarily, resize the font and restore the stylesheet but that sounds horrible and hacky.

I am using Python 3.7.4 and PyQt5 5.12.2.

This is how my MRE looks:

enter image description here

Below is the code which was used:

# -*- coding: utf-8 -*-
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QLabel, QLineEdit, QPushButton, QSpinBox, QMainWindow
import sys

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        # Setup widgets.
        self.widgets = []

        for n in range(2):
            label = QLabel(parent=self)
            label.move(10+150*n, 20)
            label.setText("QLabel")
            setattr(self, f"label{n+1}", label)

            pb = QPushButton(parent=self)
            pb.move(10+150*n, 60)
            pb.setText("QPushButton")
            setattr(self, f"pb{n+1}", pb)

            le = QLineEdit(parent=self)
            le.move(10+150*n, 100)
            le.setText("QLineEdit")
            setattr(self, f"le{n+1}", le)

            sb = QSpinBox(parent=self)
            sb.move(10+150*n, 140)
            sb.setSpecialValueText("QSpinBox")
            setattr(self, f"sb{n+1}", sb)

            for widget in (label, pb, le, sb):
                self.widgets.append(widget)

        # Take note of the initial window size.
        self.initial_size = (self.width(), self.height(), 30)
        self.resize(300, 200)

        # Paint the right-hand side widgets in red.
        for widget in self.widgets[int(len(self.widgets)/2):]:
            class_name = widget.__class__.__name__ # QLabel, QPushButton, QSpinBox.
            widget.setStyleSheet(class_name + "{background-color: red}")



    def resizeEvent(self, event):
        """Adjust the fontsize of all the widgets upon resizing the main window."""

        # Calculate a scale factor by comparing current and initial size.
        w0, h0, f0 = self.initial_size
        w, h = self.width(), self.height()
        scale = min(w/w0, h/h0)

        # Apply the scale factor to each widget font.
        for widget in self.widgets:
            font = widget.font()
            font.setPointSize(scale * f0)
            widget.setFont(font)

        # Check that all fontsizes are identical.
        assert len(set(widget.font().pointSize() for widget in self.widgets)) == 1



if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setAttribute(Qt.AA_DisableHighDpiScaling) # Because it's irrelevant here.
    app.setQuitOnLastWindowClosed(True)
    window = MainWindow()
    window.show()
    app.exec_()
1 Answers

Whenever a stylesheet is set to a widget, Qt automatically disables that widget's palette and font propagation. This means that all child widget will not be notified about font (and palette) changes from the parent.

This is explained in the inheritance chapter of the stylesheet syntax:

In classic CSS, when font and color of an item is not explicitly set, it gets automatically inherited from the parent. By default, when using Qt Style Sheets, a widget does not automatically inherit its font and color setting from its parent widget.

Note that the above obviously refers to fonts set using setFont() and colors set using setPalette(). If you set a font or a color in the stylesheet, the propagation of those properties will work as expected according to the cascading nature of stylesheets.

QSpinBox, like other widgets such as QComboBox and item views, is a complex widget, that use children widgets to display and interact with it, and in this case it's a QLineEdit (accessible through lineEdit()).

In order to solve your issue there are multiple approaches available:

  1. set the AA_UseStyleSheetPropagationInWidgetStyles attribute on the application:

    QApplication.setAttribute(
        Qt.AA_UseStyleSheetPropagationInWidgetStyles, True)
    

    This obviously has the side effect that the propagation will work for all widgets of the application, it's up to you to decide if that is fine or not.
    Note: this attribute has been introduced from Qt 5.7.

  2. check if the widget is a QSpinBox and set the font for its line edit:

    for widget in self.widgets:
        font = widget.font()
        font.setPointSize(scale * f0)
        if isinstance(widget, QSpinBox):
            widget.lineEdit().setFont(font)
        else:
            widget.setFont(font)
    
  3. set the font with the stylesheet. In your case, you could check if the widget has a stylesheet and use a template to update it:

    baseStyleSheet = '''
    {} {{
        background-color: red;
        font-size: {}px;
    }}
    '''
    
    # ...
        def resizeEvent(self, event):
            w0, h0, f0 = self.initial_size
            w, h = self.width(), self.height()
            scale = min(w/w0, h/h0)
            font_size = scale * f0
    
            for widget in self.widgets:
                if widget.styleSheet():
                    widget.setStyleSheet(baseStyleSheet.format(
                        widget.__class__.__name__, font_size))
                else:
                    font = widget.font()
                    font.setPointSize(font_size)
                    widget.setFont(font)
    

Obviously the above works only in this specific situation where only simple properties are set.

Related