1

First of all, I'd like to point out the following from https://doc.qt.io/archives/qt-5.15/qitemdelegate.html#details :

We recommend the use of QStyledItemDelegate when creating new delegates.

So, this is what I'm seeing - in the example below, if you set DELEGATE_CHOICE = 0 (i.e. no use of delegate with QComboBox) and run the program, then you get a nice dark blue highlight color when you hover with the mouse pointer over the combo box popup list items:

app combobox popup hover OK

The same happens if you use DELEGATE_CHOICE = 3 (i.e. you use a subclass of QItemDelegate with the QComboBox)

However, if you set DELEGATE_CHOICE = 1 (i.e. you use a subclass of QStyledItemDelegate with the QComboBox) and run the program, then you get an, also nice, light blue highlight color when you hover with the mouse pointer over the combo box popup list items:

app combobox popup hover light

... however, this is not the default color highlight color, and I do not want it here!


So, assuming that I want to follow the documentation's recommendation of using QStyledItemDelegate - can I do that at all in the case of QComboBox (since as the example demonstrates, QStyledItemDelegate corrupts the default highlight colors of QComboBox)? If so, how can I use QStyledItemDelegate with QComboBox without changing the default highlight colors?

Otherwise, is QItemDelegate the right delegate base to use with QComboBox - and if so, how could I have known that? I only realized that after some hours of being puzzled about the color change, when I decided to print out comboBox.itemDelegate(), and subsequently found:

The example code:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QWidget, QComboBox, QVBoxLayout, QStyleOptionComboBox, QStyle, QAbstractItemView, QStyledItemDelegate, QStyleOptionButton, QAbstractItemDelegate, QItemDelegate

class SubComboBox(QComboBox):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setStyleSheet("text-align:left;")

DELEGATE_CHOICE = 1   # 0 # 1 # 2 # 3

if DELEGATE_CHOICE == 1:
    class SubDelegate(QStyledItemDelegate): # changes highlight color
        pass

elif DELEGATE_CHOICE == 2:
    class SubDelegate(QAbstractItemDelegate): # NotImplementedError: QAbstractItemDelegate.sizeHint() is abstract and must be overridden
        pass

elif DELEGATE_CHOICE == 3:
    class SubDelegate(QItemDelegate): # does not change highlight color; same use in https://stackoverflow.com/q/17615997
        pass

class ComboBoxExample(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('ComboBox Example')
        self.setGeometry(100, 100, 200, 100)

        # Create custom QComboBox
        self.comboBox = SubComboBox(self)
        #print(f"{self.comboBox.itemDelegate()=}") # PyQt5.QtWidgets.QItemDelegate object
        if DELEGATE_CHOICE > 0:
            self.comboBox.setItemDelegate(SubDelegate(self))

        data = [100, 200, 330, 400, 550]
        for num in data:
            self.comboBox.addItem(str(num))

        # Set up layout
        layout = QVBoxLayout()
        layout.addWidget(self.comboBox)
        layout.addStretch(1) # adds QSpacerItem
        self.setLayout(layout)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = ComboBoxExample()
    window.show()
    sys.exit(app.exec_())
6
  • 1
    The default delegate for QComboBox is based on QItemDelegate (print(self.comboBox.itemDelegate())). The same is for other complex item-based classes such as QCompleter (when using the popup) or QCalendarWidget (also shown in QDateTimeEdit with the popup enabled). Standard "public" item views (QListView, QTableView, QTreeView and their higher level classes) use QStyledItemDelegate, which, as the name suggests, follows the style. QItemDelegate only uses the palette colors, while styled widgets may consider the palette. So, the real question here is: what do you actually need to do? Commented Aug 12 at 21:53
  • 1
    I mean: if you need a custom delegate but you're worried about the appearance being consistent with the default behavior, then use QItemDelegate for QComboBox. Keep in mind, though, that that class has limited capabilities: style sheets have almost no effect except for the basic color properties (since it's not styled), and their implementation is somehow limited (for instance, it's not possible to override drawBackground(), since it's not virtual). Yet, being it a simpler class, it usually has better performances with large models. Commented Aug 12 at 22:02
  • Thanks @musicamante - I wanted exactly the summary you provide in your comment, of which delegate is appropriate/intended with which widget (otherwise this stems from trying to get QComboBox to right-align the text of everything, which requires delegates stackoverflow.com/a/79732286, and then I got irritated when I realized the highlight color got changed, and spent some hours trying to debug) Commented Aug 12 at 22:02
  • 1
    In reality, you don't really need a delegate for that, as there are other ways to achieve that (for instance, subclassing QListView and overriding viewOptions()). In any case, I can write a more comprehensive answer if you want, including related resources and source code pointers, as well as possible alternatives to alter the default behavior while trying to keep the expected appearance. Commented Aug 12 at 22:06
  • 1
    You don't really need to worry about that: answers to your own questions will always generate a notification at least on the website (and even through mail if you enabled them). For other people's posts, the same can be achieved using the related "Follow" link/button. Commented Aug 14 at 23:39

1 Answer 1

1

Basic overview of delegates in Qt

The three main item views in Qt inheriting from QAbstractItemView (QListView, QTableView, QTreeView, and their related higher level counterparts) all use QStyledItemDelegate by default. The other two are just exceptions: QHeaderView doesn't use delegates, QColumnView uses further QListViews in itself (and they do use QItemDelegate).

All other widgets that are related to models (eg: QCalendarWidget, QCompleter) often use QItemDelegate for its simplicity, or even custom subclasses of QAbstractItemDelegate.

The differences you're noticing are based on the "styled" nature of the delegate.

QItemDelegate is simpler, relies much less on QStyle capabilities (making it normally faster than its styled counterpart) and, most importantly, always respects the palette colors unless the model specifies a color for background/foreground roles.
This also means that it completely ignores style sheets, except for the basic color/font properties set on the combo or the popup (so, the ::item sub-control selector has no effect).

QStyledItemDelegate aims to have a more "native look" for common item views, but this also means that there is absolutely no guarantee that the palette will be used as it is, or even considered to begin with: while QStyle aspects and features are nice and cool when trying to follow a consistent appearance, they present some drawbacks when trying to "force" some of those aspects, specifically when dealing with colors, which are often shades or gradients of the related palette role they're intended to use.

When using QStyle functions to painting, we actually delegate all the painting to its whims, which is why QPalette related classes/functions usually warn about how the defined colors may be used (or even considered) to begin with.

QComboBox delegates

Specifically, QComboBox normally uses QItemDelegate by default on Qt5 and QStyledItemDelegate since Qt6, except when the style returns True when queried for the SH_ComboBox_Popup style hint, for which a custom QAbstractItemDelegate is used instead.

This can be verified in the qcombobox_p.h sources (latest Qt 5.15 and early Qt 6.0).

The usage of QItemDelegate was originally intentional for QComboBox, as it was more compliant with the appearance in most systems, resulting in the popups of combo boxes having a simpler appearance.

If you're on Qt5, you should probably keep using QItemDelegate, especially for program consistency if you have other QComboBoxes and don't want to set a delegate for each one of them (although there are ways to "hack" that even when using Designer files).

Forcing a highlight color for QStyledItemDelegate

Forcing a specific color for selected items on QStyledItemDelegate is a bit more complex.
If we exclude complete custom painting of the item (which should normally be avoided), there are basically three options, each one with their own benefits and limitations.

Override paint() and draw the selection background on your own

The paint() override method should simply check if the item is hovered/selected (the QStyleOptionViewItem state flag will include the State_Selected enum), fill the option's rect with the highlight color from the palette in that case, then unset the State_Selected flag before calling the default paint() implementation so that it won't draw the selection area.

Note, though, that the item may have its own background, and in that case the internal paint() will try to draw it since the item isn't selected anymore. To work around that, you should then do what paint() actually does (including creating a copy of the option (mandatory) and calling initStyleOption() with it), and clearing the possible backgroundBrush if the item was selected before calling the style drawControl() with CE_ItemViewItem. Yet, some styles may still ignore what you did, and do whatever they want.

Use a proxy style

The QProxyStyle approach relies on overriding drawPrimitive() and painting the background for PE_PanelItemViewItem.

While technically more appropriate, there's no absolute guarantee that it will always work as expected: first of all, the style may draw the item completely on its own, without ever calling drawPrimitive().

Also, if any style sheet affects the view in any way, that function will never be called to begin with, as the internal QStyleSheetStyle takes control over the inherited style, excluding proxy overrides.

Use Qt Style Sheets

The stylesheet option works by setting the background color of the QAbstractItemView::item:selected selector, but has the common limitation of QSS with complex widgets: whenever any property of a subcontrol is set, the widget falls back to the basic style appearance (the "windows" style).

If you migrate to Qt6 (which, as said, uses QStyledItemDelegate), the resulting appearance will be inconsistent.

Further notes

Remember that QComboBox supports separators, which completely relies on the delegate being able to draw them. If want separators and you also need custom delegates, you must implement that in the paint() and sizeHint() functions (both are mandatory for QAbstractItemView subclasses). The above links to the sources also contain the related parts in the implementations of the delegates.

Finally, always ensure that you're using custom delegates correctly and for the right reasons: in the comments you mentioned that you needed a delegate in order to override the text alignment of the items, and while that is a valid option for that requirement, it's also not the only one.

In fact, the simplest and most effective solution that also presents less ramifications is to use a custom model.
By default QComboBox uses a common (non subclassed) QStandardItemModel, which means that you could create a subclass, override its data(), and there return the required alignment for the TextAlignmentRole (or the default result from any other role), then set that model for the combo right after its creation. If you're already using a custom model and don't want to alter its behavior, then do the same with a QIdentityProxyModel and set your model as its source.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.