PyQt5: setting QListWidget selection color when the list loses focus

1.5k views Asked by At

I have written a small PyQt5 application with two QListWidgets as shown in the picture. I have set the "fusion" style in order to get comboboxes with background color, and as an undesired result I got this issue with QListWidget selection colors: selection has blue background when it has the focus, which is perfectly fine, but gets a light grey background when the list loses its focus (as in the list on the left), making it hard to read.

I tried different combinations of CSS styling over the widget based on similar snippets for QTableWidgets, but without success.

Any idea of how to change this background color?

enter image description here

EDIT: Given that proposed solution is not working, I have looked for possible differences against your test. It is likely due to the usage of a custom QStyledItemDelegate I got from How to display partially bold text in QListWidgetItem with QtCore.Qt.UserRole

from PyQt5 import QtCore, QtGui, QtWidgets


class HTMLDelegate(QtWidgets.QStyledItemDelegate):
    '''
    The roles >= Qt::UserRole are not used by Qt by default so it can be used for any purpose,
    for example to save additional information, in this case it is not the solution.
    One possible solution is to use a delegate to render the HTML.
    (Extracted from https://stackoverflow.com/questions/53569768/how-to-display-partially-bold-text-in-qlistwidgetitem-with-qtcore-qt-userrole)
    '''
    def __init__(self, parent=None):
        super(HTMLDelegate, self).__init__(parent)
        self.doc = QtGui.QTextDocument(self)

    def paint(self, painter, option, index):
        painter.save()
        options = QtWidgets.QStyleOptionViewItem(option)
        self.initStyleOption(options, index)
        self.doc.setHtml(options.text)
        options.text = ""
        style = QtWidgets.QApplication.style() if options.widget is None \
            else options.widget.style()
        style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)

        ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
        if option.state & QtWidgets.QStyle.State_Selected:
            ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
                QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
        else:
            ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
                QtGui.QPalette.Active, QtGui.QPalette.Text))
        textRect = style.subElementRect(QtWidgets.QStyle.SE_ItemViewItemText, options, None)
        if index.column() != 0:
            textRect.adjust(5, 0, 0, 0)
        constant = 4
        margin = (option.rect.height() - options.fontMetrics.height()) // 2
        margin = margin - constant
        textRect.setTop(textRect.top() + margin)

        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        self.doc.documentLayout().draw(painter, ctx)
        painter.restore()

    def sizeHint(self, option, index):
        return QtCore.QSize(self.doc.idealWidth(), self.doc.size().height())

Hence, I guess that I should modify the part where colors are set for the QPalette. Nevertheless, QStyle::State_HasFocus is not being used here, so I don't understand why it is not working. Any idea of how to fix it now?

As a parallel question: Where is the definition for all the CSS possibilities for QT widgets and its subelements? I would like to be able to explore it by myself instead of bothering stackoverflow users with this kind of questions for simple CSS code in the future :)

2

There are 2 answers

2
musicamante On BEST ANSWER

You cannot rely on stylesheets whenever there is custom drawing involved using QPalette colors as a reference: stylesheets override the styling, and whenever colors are specified the palette is also ignored (at least for the states/properties specified in the stylesheet).

The palette knows nothing about the stylesheets, and it's actually the other way around: the stylesheets use the palette (in fact, you can use palette roles in stylesheets).
There's no way to programmatically access the colors used in stylesheets.

You have two possibilities:

  1. Change the background color for highlighted items when there is no focus, before drawing the item:
        if not option.state & QtWidgets.QStyle.State_HasFocus:
            options.palette.setColor(QtGui.QPalette.Highlight, QtCore.Qt.darkGray)
        style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
        # ...
  1. Set the text color only if the item is selected and there is focus:
        if (option.state & QtWidgets.QStyle.State_Selected and 
            option.state & QtWidgets.QStyle.State_HasFocus):
                ctx.palette.setColor(QtGui.QPalette.Text, option.palette.color(
                    QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
        else:
            # ...
1
alec On

It can be set in the ::item sub-control, :selected pseudo-state, and :!active pseudo-state.

QListWidget::item:selected:!active {
    background: #17d;
}

Test example:

import sys
from PyQt5.QtWidgets import *

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle('fusion')
    app.setStyleSheet('''
    QListWidget::item:selected:!active {
        background: lightBlue;
        color: black;
    }''')
    items = ['2020-11-27'] * 6
    x = QListWidget(); x.addItems(items)
    y = QListWidget(); y.addItems(items)
    window = QWidget()
    hbox = QHBoxLayout(window)
    hbox.addWidget(x); hbox.addWidget(y)
    window.show()
    sys.exit(app.exec_())