0

I need to develop a dynamic theme changing in PyQt application, I have different components, and depending on the theme I need to change styles. I need to change the theme from some specific component and all components should change their styles. I do not use global styles, I need to write styles for each component separately in its class.

At the moment I came up with such an implementation:

I grab GlobalObject class from this article - https://stackoverflow.com/a/55554690/15709458.

And for each component, I put a listener for the "themeChanged" event. And by dispatching this event, I notify all components which listen to this event that the theme of the application has changed.

Dispatching event example:

def set_dark_theme(app: QApplication):
    app.setProperty("theme", "dark")
    GlobalObject().dispatchEvent("themeChanged")

Component example:

class CheckBox(QCheckBox):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.update_stylesheet()

        GlobalObject().addEventListener("themeChanged", self.update_stylesheet)

    def update_stylesheet(self):
        theme = QApplication.instance().property("theme")

        if theme == "dark":
            text_color = __colors__["gray"][200]
        else:
            text_color = __colors__["gray"][700]

        self.setStyleSheet(
            f"QCheckBox {{ spacing: 8px; font-size: 14px; font-weight: 500; color: {text_color}; }}"
        )

Will such an implementation adversely affect performance due to the fact that all components in the application of which there can be a lot will be subscribed to the event? Is it possible to somehow improve this solution, for example using some built-in methods?

monotype
  • 217
  • 2
  • 10
  • Qt Style Sheets are probably what you are looking for. A style sheet applies to all children of a widget with the specified type. A good overview is given by the Qt Python documentation: https://doc.qt.io/qtforpython/overviews/stylesheet-examples.html. – Creepsy Sep 30 '22 at 21:43
  • It should be enough to set the style sheet of your QApplication. – Creepsy Sep 30 '22 at 21:50
  • @Creepsy I use Qt Style Sheet in specific components and these component styles should be updated when the user changes the theme, how can I do that? Like how can I notify all components in my app when the user changes the theme? Apart from Qt Stylesheets In some components, I overwrite QPaintEvent. Each component and its style are isolated. Also, I use variables with theme colors. – monotype Sep 30 '22 at 22:08
  • Don't try to reinvent the wheel by complicating things unnecessarily. Just use stylesheets *properly*, with correct class/object names and property [selectors](https://doc.qt.io/qt-5/stylesheet-syntax.html#selector-types). Custom painting might also be properly achieved, but that depends on the widget and how properly are used palettes and style options. – musicamante Oct 01 '22 at 07:03
  • @musicamante And what should I do with components that have a custom PaintEvent, how do I tell this component that the theme has changed and you need to change your color, for example. – monotype Oct 01 '22 at 13:16
  • @musicamante Well, that is, you propose to set styles for all components globally. I thought about how to deal with components with a custom paintEvent, if I set styles like this app.setStylesheet(...), then paintEvent should be called for all components for which I changed styles, according to the idea, and in paintEvent I can get new colors from QPallete (as described here - https://stackoverflow.com/questions/71207148/how-to-get-the-color-of-a-widget-text-and-set-it-as-the-color-of- another-widget). Is this implementation correct? – monotype Oct 01 '22 at 13:44
  • @monotype Exactly: note that all widgets receive a `StyleChange` event whenever `QApplication.setStyleSheet()` is called (even if it doesn't concern the widget) and a PaletteChange` event *if* the stylesheet selector *does* concern the widget and alters the palette; those events can be received in the `changeEvent()` override of the widget if it requires important changes *before* repainting (for instance, to change contents margins, etc), otherwise a `paintEvent()` will be called in any case if the global stylesheet does concern the widget (not only for color changes). – musicamante Oct 01 '22 at 18:18

1 Answers1

1

You do not have to use events at all!

You can simply have a QWidget as main class which holds all the other widgets. Then you can use setStyleSheet() once and the appearance will change for all widgets as long as they are in the stylesheet or inherit from a class in the stylesheet.

If you set a style for QWidget for example, the properties will be set for almost all widgets as they all inherit from QWidget.

You can see the minimal example below for reference. Click the CheckBox and the style will change to dark.

import sys
from PyQt5 import QtWidgets, QtGui

class Window(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        
        self.checkbox = QtWidgets.QCheckBox(self)
        self.checkbox.toggled.connect(self.change_theme)
    
    def change_theme(self):
        if theme == "dark":
            text_color = __colors__["gray"][200]
        else:
            text_color = __colors__["gray"][700]
        self.setStyleSheet("QWidget { background-color: black; color: white; } QCheckBox { spacing: 8px; font-size: 14px; font-weight: 500; color: {text_color}; background-color: grey; }")

app = QtWidgets.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())

Edit: I added the theme color variables

  • But the problem is that I can't use global styles, since the styling of each component and the component itself must be isolated so that it can be reused in any application. And also in some components, I not only use Qt Stylesheets but also overwrite QPaint event. And in addition, I use variables with theme colors. – monotype Sep 30 '22 at 22:11
  • You can set an object name (see: [link](https://stackoverflow.com/questions/57779395/style-for-a-class-instance-using-qt-style-sheets)) and set a specific stylesheet like this: `QCheckBox#cb2 { background-color: grey; }` I don't know about the paint event though. – Igli Gerdon Sep 30 '22 at 22:20