Sir Teddy the First
Sir Teddy the First

Reputation: 431

Custom ComboBox using custom model and delegate

I have a list of items in the form (string, integer, bool), like so:

[ ('Item 1', 12, True), ('Item 2', 156, True), ('Item 3', 19, False) ]

which I'd like to display in a QComboBox in the following way:

Custom QCombobox with a green rectangle, grey text to the left and a darker number to the right


This is what I have so far:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


class MyModel(QAbstractListModel):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.data = [
            ('Item 1', 12, True),
            ('Item 2', 156, True),
            ('Item 3', 19, False)
        ]

    def data(self, index, role=None):
        if role == Qt.DisplayRole:
            text = self.data[index.row()][0]
            return text

        if role == Qt.DecorationRole:
            status = self.data[index.row()][2]

            if status:
                return QColor('#22FF35')

    def rowCount(self, parent=None, *args, **kwargs):
        return len(self.data)


class MyDelegate(QStyledItemDelegate):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def paint(self, painter, option, index):
        painter.save()

        options = QStyleOptionViewItem(option)
        self.initStyleOption(options, index)

        style = options.widget.style()

        style.drawItemText(painter, options.widget.rect(), Qt.AlignLeft,
                           options.widget.palette(), True, options.text,
                           QPalette.Text)

        painter.restore()


class MyComboBox(QComboBox):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setFixedSize(200, 75)

        self.model = MyModel()
        self.setModel(self.model)

        self.setItemDelegate(MyDelegate())


if __name__ == '__main__':
    app = QApplication([])
    window = MyComboBox()
    window.show()
    app.exec()

The code obviously is incomplete and does not achieve what I have in mind.
A few questions arise, like:

How can I pass both the first and the second item to the delegate?
If the model returns a list or a tuple, options.text in the delegate will be empty.

Upvotes: 1

Views: 665

Answers (1)

eyllanesc
eyllanesc

Reputation: 244162

In this case it is only necessary to paint the text on the right over the default one:

TEXT_ROLE, VALUE_ROLE, STATUS_ROLE = ((Qt.UserRole + i + 1) for i in range(3))


class MyModel(QAbstractListModel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.data = [
            ("Item 1", 12, True),
            ("Item 2", 156, True),
            ("Item 3", 19, False),
            ("Item 4", 126, True),
            ("Item 5", 100, False),
        ]

    def data(self, index, role=None):
        if 0 <= index.row() < self.rowCount():
            item = self.data[index.row()]
            if role in (TEXT_ROLE, Qt.DisplayRole):
                return item[0]
            elif role == VALUE_ROLE:
                return item[1]
            elif role == STATUS_ROLE:
                return item[2]

    def rowCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        return len(self.data)


class MyDelegate(QStyledItemDelegate):
    RIGHT_MARGIN = 4

    def initStyleOption(self, option, index):
        super().initStyleOption(option, index)
        status = index.data(STATUS_ROLE)
        option.features |= QStyleOptionViewItem.HasDecoration
        pixmap = QPixmap(option.decorationSize)
        pixmap.fill(QColor("#22FF35" if status else "transparent"))
        option.icon = QIcon(pixmap)

    def paint(self, painter, option, index):
        value = index.data(VALUE_ROLE)
        super().paint(painter, option, index)
        painter.drawText(
            option.rect.adjusted(0, 0, -self.RIGHT_MARGIN, 0),
            Qt.AlignRight | Qt.AlignVCenter,
            str(value),
        )

Upvotes: 4

Related Questions