Dynamic Object Properties and Stylesheets: PyQt PySide

Dynamic Object Properties and Stylesheets: PyQt PySide

Qt, and by extension, PyQt and PySide, offers the ability to style widgets based on properties of that widget.
For example, you could style buttons differently based on whether it is clicked, whether it’s good or bad, or any other arbitrary property that you set.

Dynamic Properties

Qt  supports custom properties on objects by using the setProperty method.


QObject.setProperty('PropertyName', value)

Usually you’d want to restrict this to just be booleans, but it supports anything that can be cast into a QVariant like a string or int etc.
This is really handy if you want to store attributes on the widget but it is incredibly useful when you want to use it to toggle stylesheet styles on an object as the rest of my post will hopefully demonstrate.

QStylesheets

Stylesheets are the Qt equivalent to CSS on the web.
Qt uses a similar box model, which has the advantage of both being very familiar to anyone who’s done web, but also means you can lift a lot of CSS examples and use them easily.

An example of this to make a button with a red font color and black background:


QPushButton{
 color: #F00;
 background-color: #000;
}

You can read more about them here: http://doc.qt.io/qt-5/stylesheet-examples.html

But the really cool thing is that stylesheets can be coupled with dynamic properties to do state based styles.
So for example I want to only have the above when a Test property is True


QPushButton[Test=true] {...}

You might be wondering why true is lowercase. This is because Qt is a C++ app and true is lowercase there.

How about if I only want it to affect a button with an object name StyledButton and only when the state is True?


QPushButton#StyledButton[Test=true] {...}

So it can be really useful for defining all your styles in one place and then simply updating the property later and calling an update function.

Updating On Property Changes

Unfortunately Qt doesn’t automatically hook up property changes to style changes, so you need to call a simple update function after.
This is very easy and there are two ways you can do this:


# Option 1
myWidget.setStyle(myWidget.style())

#OR

# Option 2
myWidget.style().unpolish(myWidget)
myWidget.style().polish(myWidget)
myWidget.update()

Personally I prefer the first one since it’s a one liner, but just presenting both options here for you in case you want more control over the process.

Putting It All Together

You can run this code.
It will generate a QDialog with two buttons.
The stylesheet in this example affects both buttons when Test=true, but additionally affects the button with the name StyledButton differently than the other one.
Because of style inheritance, the StyledButton inherits the style of the generic button with Test=True but also it’s own overrides

This example uses PyQt4, but the same applies for PyQt5, PySide, PySide2 etc…
If you’re using the Qt5 bindings (PyQt5 or PySide2) you need to use QtWidgets instead of QtGui


class Test(QtGui.QDialog):
    """A sample widget to show dyanmic properties with stylesheets"""
    def __init__(self):
        # Do the usual
        super(Test, self).__init__()
        self.setWindowTitle('Style Sheet Test')
        layout = QtGui.QVBoxLayout(self)

        # Set the stylesheet
        self.setStyleSheet("""
            QPushButton[Test=true] {
                border: 2px solid #8f8f91;
                border-radius: 6px;
                background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                      stop: 0 #f6f7fa, stop: 1 #dadbde);
                min-width: 80px;
            }

            QPushButton#StyledButton[Test=true] {
                color: #F00;
                background-color: #000;
            }
                           """)

        # Create the button
        btn = QtGui.QPushButton('Click Me')
        btn.setProperty('Test', True)
        btn.setObjectName('StyledButton')
        btn.clicked.connect(lambda: self.toggle(btn))
        layout.addWidget(btn)

        btn2 = QtGui.QPushButton('Click Me')
        btn2.setProperty('Test', True)
        btn2.clicked.connect(lambda: self.toggle(btn2))
        layout.addWidget(btn2)

    def toggle(self, widget):
        # Query the attribute
        isTest = widget.property('Test').toBool()
        widget.setProperty('Test', not isTest)

        # Update the style
        widget.setStyle(widget.style())

if __name__ == '__main__':
    app = QtGui.QApplication([])
    dlg = Test()
    dlg.show()
    app.exec_()

Anyway that’s it for this little introduction. Hope you find that useful!

Pin It on Pinterest