I have a QTreeView
with 5 columns and I set up a QComboBox
delegate for 2 of them (columns 1 and 2). Both of them have the same delegate and they must have the same drop down list. Let's say the list is always: ["Next", "Stop"].
So far so good: the delegate works perfectly.
Now the problem: I wanted items of column 1 and 2 to display text in different colors based on the text itself. For example: if the text is "Next", text's color should be green and if the text is "Stop", the color should be red.
After some searching I decided to use the delegate to set the color. I found different solutions but the only one almost working for me was this (see paint()
function):
class ComboBoxDelegate(QtWidgets.QStyledItemDelegate):
def paint(self, painter, option, index):
painter.save()
text = index.data(Qt.DisplayRole)
if text == 'Next':
color = GREEN
elif text == 'Stop':
color = RED
else:
color = BLACK
painter.setPen(QPen(color))
painter.drawText(option.rect, Qt.AlignLeft | Qt.AlignVCenter, text)
painter.restore()
def createEditor(self, parent, option, index):
editor = QtWidgets.QComboBox(parent)
editor.currentIndexChanged.connect(self.commitEditor)
return editor
# @QtCore.Slot
def commitEditor(self):
editor = self.sender()
self.commitData.emit(editor)
if isinstance(self.parent(), QtWidgets.QAbstractItemView):
self.parent().updateEditorGeometries()
self.closeEditor.emit(editor)
def setEditorData(self, editor, index):
try:
values = index.data(QtCore.Qt.UserRole + 100)
val = index.data(QtCore.Qt.UserRole + 101).strip('<>')
editor.clear()
for i, x in enumerate(values):
editor.addItem(x, x)
if val == x:
editor.setCurrentIndex(i)
except (TypeError, AttributeError):
LOG.warning(f"No values in drop-down list for item at row: {index.row()} and column: {index.column()}")
def setModelData(self, editor, model, index):
values = index.data(QtCore.Qt.UserRole + 100)
if values:
ix = editor.currentIndex()
model.setData(index, values[ix], QtCore.Qt.UserRole + 101)
model.setData(index, values[ix], QtCore.Qt.DisplayRole)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
As you can see in the image below, the selection and the hovering are not working properly:
What am I doing wrong?
Minimum reproducible example (remember to import ComboBoxDelegate):
import sys
from PySide2.QtGui import QStandardItemModel, QStandardItem
from PySide2.QtWidgets import QTreeView, QApplication, QMainWindow
from combo_box_delegate import ComboBoxDelegate
COLUMN_HEADER_LIST = ["0", "1", "2", "3", "4"]
class MyTreeView(QTreeView):
def __init__(self):
super(MyTreeView, self).__init__()
self.model = QStandardItemModel()
self.root = self.model.invisibleRootItem()
self.setModel(self.model)
delegate = ComboBoxDelegate(self)
self.setItemDelegateForColumn(1, delegate)
self.setItemDelegateForColumn(2, delegate)
self.data = {
"a": {
"b": {
"c": ["Next", "Stop", "1", "Hello World!"],
"d": ["Next", "Stop", "2", "Hello World!"],
"e": ["Next", "Stop", "3", "Hello World!"],
"f": ["Next", "Stop", "4", "Hello World!"]
}
}
}
self.populate_tree(self.root, self.data)
self._format_columns()
def populate_tree(self, parent, data):
for key, value in data.items():
if isinstance(value, dict):
child = QStandardItem(key)
parent.appendRow(child)
self.populate_tree(child, data[key])
elif isinstance(value, list):
items = [QStandardItem(key)] + [QStandardItem(str(val)) for val in value]
parent.appendRow(items)
def _format_columns(self):
self.model.setHorizontalHeaderLabels(COLUMN_HEADER_LIST)
self.expandAll()
class Main(QMainWindow):
def __init__(self):
super(Main, self).__init__()
tree = MyTreeView()
self.setCentralWidget(tree)
self.setMinimumWidth(600)
self.setMinimumHeight(400)
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = Main()
main_window.show()
app.exec_()
The painting of the items is special so in general it is not recommended to override the
paint()
method but to change the properties ofQStyleOptionViewItem
ininitStyleOption()
method that are used for painting, for example changing theQPalette
: