Search code examples
pythonpyqtqlistwidget

how to automatically change the color of selected item in QListWidget


I want to change the color of selected item in QListItem, and I find qss may be a solution. And the code is:

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys

class Window(QWidget):

    def __init__(self):
        super().__init__()
        with open('mainStyle.qss', 'r', encoding='utf-8') as file:
            self.setStyleSheet(file.read())
        # self.setStyleSheet('*{font-size: 15px;background-color: rgb(150, 150, 150);}'
        #                    'QListWidget::item:selected{background: rgb(128,128,255);}')
        self.setStyleSheet('QListWidget::item:selected{background: rgb(128,128,255);}')

        layout = QVBoxLayout()
        self.setLayout(layout)

        listWidget = QListWidget()
        layout.addWidget(listWidget)

        w1 = QWidget()
        w1Item = QListWidgetItem()
        w1Item.setSizeHint(QSize(150, 150))

        listWidget.insertItem(0, w1Item)
        listWidget.setItemWidget(w1Item, w1)

        w2 = QWidget()
        w2Item = QListWidgetItem()
        w2Item.setSizeHint(QSize(150, 150))

        listWidget.insertItem(1, w2Item)
        listWidget.setItemWidget(w2Item, w2)


if __name__ == '__main__':

    app = QApplication(sys.argv)
    win = Window()
    win.show()
    app.exec_()

enter image description here

We can see that the color is changed to be blue when the item is selected.

However, I need to provide a general background color for other widgets. So I change the style from

self.setStyleSheet('QListWidget::item:selected{background: rgb(0,0,255);}')

to

self.setStyleSheet('*{font-size: 15px;background-color: rgb(150, 150, 150);}'
                   'QListWidget::item:selected{background: rgb(0,0,0);}')

Then, I find QListWidget::item:selected do not work. The color do not change when I select a item.

What's the wrong with my code?


Solution

  • The problem is that you're setting a QWidget for the item, and since you're using a universal selector (with the wildcard), the result is that all QWidget will have that background color, including those added as item widgets for the list view.
    The color you used for the :selected pseudo is only valid for the item painted by the view, since the item widget has its own background set from the universal selector, that background won't be visible.

    The solution is to use a proper selector combination ensuring that the rule only matches children of the list view that have a usable selector, and set a transparent color for those widgets.

    A possibility is to set a custom property for the widgets that must be set before adding the widgets to the list (otherwise, you need to set the stylesheet after adding them, or request a style.unpolish()).

            self.setStyleSheet('''
                QWidget {
                    font-size: 15px;
                    background: rgb(150, 150, 150);
                }
                QListWidget::item:selected {
                    background: rgb(128,128,255);
                }
                QListWidget QWidget[widgetItem=true] {
                    background: transparent;
                }
            ''')
    
            # ...
            w1 = QWidget()
            w1.setProperty('widgetItem', True)
            # ...
            w2 = QWidget()
            w2.setProperty('widgetItem', True)
            # ...
    

    Another way to do so is to use an "empty" subclass for widgets that are going to be added to the item view:

    class CustomItemWidget(QWidget): pass
    
    class Window(QWidget):
        def __init__(self):
            super().__init__()
            self.setStyleSheet('''
                QWidget {
                    font-size: 15px;
                    background: rgb(150, 150, 150);
                }
                QListWidget::item:selected {
                    background: rgb(128,128,255);
                }
                CustomItemWidget {
                    background: transparent;
                }
            ''')
    
            # ...
            w1 = CustomItemWidget()
            # ...
            w2 = CustomItemWidget()
            # ...
    

    Consider that using universal selectors for colors is usually not a good idea, as it can result in inconsistent behavior, especially for complex widgets: for instance, the scroll bars of scroll areas (like QListWidget) might not be correctly styled and can even become unusable.
    If you plan to have a common background color for all widgets, it's better to set the Window role of the application palette:

        app = QApplication(sys.argv)
        palette = app.palette()
        palette.setColor(palette.Window, QColor(150, 150, 150))
        app.setPalette(palette)
        # ...
    

    In this way the current style will know exactly when to use that color as background, or as a component for other UI elements.