Trying to keep styling separate from the rest of my PyQt code, I try to do my styling using qss files. However, doing all the styling using this seems to be difficult (if not impossible).
What would help me achieve this would be the possibility of using custom qss properties. To give an example, I have a class that represents a line which can be either horizontal or vertical:
class Line(QWidget):
def __init__(self, direction: Qt.Orientation, thickness: int, parent: Optional[QWidget]):
super().__init__(parent)
self.setAttribute(QtCore.Qt.WA_StyledBackground, True)
if direction == Qt.Orientation.Vertical:
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
self.setFixedWidth(thickness)
else:
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.setFixedHeight(thickness)
However, there is no way (that I know of) to set a 'thickness' property from a qss file. I can of course create subclasses from Line: HLine and VLine and then, using respectively the height and width properties, set the thickness. However, that does not change the fact that it seems impossible to set custom styling properties such as thickness from a qss file. If not possible, what would be the preferred approach to achieve this?
First of all, in order to set custom properties, those must be actual properties. Initialization arguments are not sufficient, because properties might be changed at runtime, or caused by other aspects (like styling, which is your case).
Qt does allow setting properties. See the "Setting QObject Properties" section from the Style Sheet Syntax documentation:
Using the
propertydecorator alone is not enough, as Qt has no knowledge about the "underlying" Python binding. All custom properties that may be potentially set by style sheets must then be created as actual Qt properties, which is by usingpyqtProperty(PyQt) andProperty(PySide) decorators from the QtCore module. Note that one should always consider using custom properties of QObject subclasses as actual Qt properties, as it makes them more consistent in the Qt context.Note that I specifically changed the
__init__syntax so that defined properties are directly (and properly) set by the PyQt property management upon initialization.With the above, you can use a basic selector (for instance, using the object name) and set both the direction and the thickness.
Be aware about the note at the end of the documentation above, though:
This is important not only for what explained, but because it also implies that any property definition that might be used elsewhere in the style sheet as a selector may not result in proper updates.
Consider the following attempt:
While the
Line#topSeparatorselector properly sets the direction of the vertical line, the color and thickness are not the expected ones.This is because setting the Qt property from the style sheet doesn't make the widget evaluate again its current state, so the
Line[direction=Horizontal]is actually ignored.The solution to this is to force a style repolishing, while being careful to avoid any further repolishing within the same change: since the style may attempt to alter the very property that caused the change, the result may be unnecessary calls to the style un/polishing and even infinite recursion under certain conditions.
Note that since the introduction of actual enums in PyQt6 (and PySide6), actual enum names can be used as values for property selectors. With Qt5, only numerical values could be used for enums, so in the case above it should have been
Line[direction="1"]etc.Obviously, you must be very careful in not using properties and selectors that may have conflicting results, such as a selector that changes a property that triggers another selector which may reset it again.