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:
True, a colored rectangle (will be replaced with an icon) is shown.False, no rectangle is shown, but the space should remain empty, instead of the text being moved to the leftfrom 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.
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),
)