SaltyCrane Blog — Notes on JavaScript and web development

PyQt4 QItemDelegate example with QListView and QAbstractListModel

I am currently working on a mini project which uses a QListView to display items in a list box. I am happy with most of the default behavior in the list view, however, I want to change how the highlighting of selected items is displayed. Currently, in my Windows environment, selecting an item in the list highlights the item in dark blue. This is fine, however, when the list box loses focus, the highlight color turns to a light gray, which is hard for me to see. I would like the selection to have a red highlight, whether the widget has focus or not.

My solution is to add a custom delegate to my list view. Normally, a standard view uses a default delegate (QItemDelegate) to render and edit the model's data. To customize the way the data is displayed in the view, I subclass QItemDelegate and implement a custom paint() method to set the background color to red for selected items. (Note, it is possible to specify certain formatting (including background color) using ItemDataRoles in the QAbstractListModel subclass, however, using a custom delegate is more powerful, and I didn't want to mix appearance-related code with my data model.)

In the example below, I started with the simple QListView / QAbstractListModel example, and added MyDelegate, a subclass of QItemDelegate. This class reimplements the paint() method to highlight selected items in red.

See also: Qt 4.3 QItemDelegate documentation

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

####################################################################
def main():
    app = QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

####################################################################
class MyWindow(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)

        # create objects
        list_data = [1,2,3,4]
        lm = MyListModel(list_data, self)
        de = MyDelegate(self)
        lv = QListView()
        lv.setModel(lm)
        lv.setItemDelegate(de)

        # layout
        layout = QVBoxLayout()
        layout.addWidget(lv)
        self.setLayout(layout)

####################################################################
class MyDelegate(QItemDelegate):
    def __init__(self, parent=None, *args):
        QItemDelegate.__init__(self, parent, *args)

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

        # set background color
        painter.setPen(QPen(Qt.NoPen))
        if option.state & QStyle.State_Selected:
            painter.setBrush(QBrush(Qt.red))
        else:
            painter.setBrush(QBrush(Qt.white))
        painter.drawRect(option.rect)

        # set text color
        painter.setPen(QPen(Qt.black))
        value = index.data(Qt.DisplayRole)
        if value.isValid():
            text = value.toString()
            painter.drawText(option.rect, Qt.AlignLeft, text)

        painter.restore()

####################################################################
class MyListModel(QAbstractListModel):
    def __init__(self, datain, parent=None, *args):
        """ datain: a list where each item is a row
        """
        QAbstractTableModel.__init__(self, parent, *args)
        self.listdata = datain

    def rowCount(self, parent=QModelIndex()):
        return len(self.listdata)

    def data(self, index, role):
        if index.isValid() and role == Qt.DisplayRole:
            return QVariant(self.listdata[index.row()])
        else:
            return QVariant()

####################################################################
if __name__ == "__main__":
    main()

Comments


#1 tralawar commented on :

It is awesome! Can you do the code for type of font and size?

maybe getting rid of that selection box around txt.

I don't quite get how it all works....


#2 Taran commented on :

Very helpful thanks.

I adjusted your code to match the default black-on-white (unselected) and white-on-blue (selected) colouring I'm getting in my QTableWidget. I also removed the validation as my values are known to always be strings.

class RetainSelectionHighlightDelegate(QItemDelegate):
def __init__(self, parent=None, *args):
QItemDelegate.__init__(self, parent, *args)

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

set background color

painter.setPen(QPen(Qt.NoPen))
if option.state & QStyle.State_Selected:
painter.setBrush(QColor("#3399FF"))
else:
painter.setBrush(QBrush(Qt.white))
painter.drawRect(option.rect)

set text color

value = index.data(Qt.DisplayRole)
if option.state & QStyle.State_Selected:
painter.setPen(QPen(Qt.white))
else:
painter.setPen(QPen(Qt.black))

Left indent

painter.translate(3, 0)

painter.drawText(option.rect, Qt.AlignLeft, value)

painter.restore()

disqus:2251702897