3

I've got a small example of the GUI I'm working on in Qt6, that has a problem switching palette colors (to switch from dark to light theme). When I apply my changes to QPalette to change the text color, they only work when the window is inactive. Weirdly, if I remove the font-family specification from the stylesheet then the color change works properly. This all works fine in Qt5 without any messing around.

  1. On load, the GUI looks fine
  2. After clicking the "Change Theme" button, it looks fine except that the text color setting that I change using Palette does not work (it's still black)
  3. If I click on my desktop or a different window to make my GUI inactive, it then shows the correct text color (red)

Light - Working, Dark - Broken, Dark - Working

Any workaround suggestions (that make color and font both always work correctly) are welcome, but I'd love to know what I'm actually doing wrong here, and why it used to work in Qt5 and doesn't in Qt6! Thanks!

from PyQt6 import QtWidgets
from PyQt6.QtGui import QPalette, QColor, QFont

APP = QtWidgets.QApplication([])


class UiMain(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setObjectName("MainWindow")
        central_widget = QtWidgets.QWidget(self)
        self.setCentralWidget(central_widget)
        tabs = QtWidgets.QTabWidget(central_widget)
        vertical_layout_26 = QtWidgets.QVBoxLayout(central_widget)
        vertical_layout_26.addWidget(tabs)
        search_tab = QtWidgets.QWidget()
        tabs.addTab(search_tab, "")
        tabs.setTabText(tabs.indexOf(search_tab), "Search")
        filter_group_box = QtWidgets.QGroupBox(search_tab)
        filter_group_box.setTitle("Filters")
        self.theme_btn = QtWidgets.QPushButton()
        self.theme_btn.setText("Change Theme")
        searchbar_layout = QtWidgets.QHBoxLayout(search_tab)
        searchbar_layout.addWidget(QtWidgets.QLabel("asdf"))
        searchbar_layout.addWidget(filter_group_box)
        searchbar_layout.addWidget(self.theme_btn)


class View(UiMain):
    def __init__(self):
        super().__init__()
        self.theme_btn.clicked.connect(self.change_theme)  # noqa

        # Create Palettes
        self.light_palette = QPalette()
        self.dark_palette = QPalette()
        self.dark_palette.setColor(QPalette.ColorRole.WindowText, QColor("red"))

        # # This didn't help
        # self.dark_palette.setColor(QPalette.ColorGroup.Active, QPalette.ColorRole.WindowText, QColor("red"))  

        # Create Stylesheets
        self.style_light = """
            * {font-family: 'Noto Sans';}  /* REMOVING THIS LINE AVOIDS THE ISSUE, BUT THEN FONTS ARE WRONG INITIALLY */
            QMainWindow {background-color: white;}
            """
        self.style_dark = """
            * {font-family: 'Noto Sans';} 
            QMainWindow {background-color: gray;}
            """

        # Set initial theme
        self.dark = False
        APP.setPalette(self.light_palette)
        APP.setStyleSheet(self.style_light)

        self.show()

    def change_theme(self):
        """Allow user to switch between dark and light theme"""
        if self.dark:
            self.dark = False
            APP.setPalette(self.light_palette)
            APP.setStyleSheet(self.style_light)
        else:
            self.dark = True
            APP.setPalette(self.dark_palette)
            APP.setStyleSheet(self.style_dark)


if __name__ == '__main__':

    gui = View()
    APP.exec()
Nat
  • 43
  • 6
  • To clarify, I think the problem is related to interaction between stylesheets and palette. I don't know what's happening, but I've found specific specific parts of palette (WindowText, Base) will break depending on whether I touch them with stylesheets or not. It did all work in Qt5 though. – Nat Jan 05 '22 at 02:16

1 Answers1

0

I ran into this as well using PySide6 (occurs both on Windows and Linux).

In my case, the issue was that changing the palette using QApplication.setPalette after I had done anything with stylesheets resulted in the stylesheet not being properly applied to existing windows / objects or their children. Deleting the window and creating a new one worked as intended.

The issue (in my case) can be seen using something like the following. The application's palette would show my text color from my palette, but the window's palette shows the default black text color.

# Change theme
my_app.setStyleSheet(new_stylesheet)
my_app.setStyle(new_style)
my_app.setPalette(new_palette)

# self is a QMainWindow that was open when the theme was changed
print(QApplication.palette().color(QPalette.Text).name())
print(self.palette().color(QPalette.Text).name())

I do not know if this is a bug or not, but I was able to work around it without creating a new window instance by manually (and recursively) applying the palette to my existing window and its children using something like the following function

def change_palette_recursive(root: QWidget, palette: QPalette):
    root.setPalette(palette)
    for child in root.children():
        if isinstance(child, QWidget):
            change_palette_recursive(child, palette)

This should be called after changing the theme. It likely needs to be called on each open window (unless it is a child of another open window).

change_theme()
change_palette_recursive(existing_window, QApplication.palette())

I would generally consider this sort of thing bad form, but it is the only thing I have found (so far) that works.

Androprise
  • 128
  • 10
  • You may also have to apply your stylesheet after the `change_palette_recursive` call – Androprise Jan 24 '22 at 15:36
  • Thanks for that. My conclusion at the end of it was to do everything in stylesheets. It took some time to concert all of my styling over, but I'm glad to have it all sorted now, with finer grained control over everything also. – Nat Jan 25 '22 at 07:23