1

Consider this example, modified from QStyledItemDelegate paint refresh issues :

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class MyElement(object):
  def __init__(self, numid):
    self.numid = numid
    self.strid = "Hello world {}".format(numid)
    self.param = 'a' if numid%2==0 else 'b'
  def __repr__(self):
    return "(numid {}, strid '{}', param '{}')".format(self.numid, self.strid, self.param)

elements = [ MyElement(i) for i in range(20) ]
print(elements)

class ElementListModel(QtCore.QAbstractListModel):
  def __init__(self, elements = [], parent = None):
    super(ElementListModel, self).__init__()
    self.__elements = elements

  def rowCount(self, parent):
    return len(self.__elements)

  def data(self, index, role):
    thiselement = self.__elements[index.row()]
    if role == QtCore.Qt.DisplayRole:
      return str( thiselement.strid )
    elif role == QtCore.Qt.DecorationRole:
      return QtGui.QColor(thiselement.numid*10,thiselement.numid,0)

class ElementThumbDelegate(QtWidgets.QStyledItemDelegate): #(QtGui.QStyledItemDelegate):
  def __init__(self, view, parent=None):
    super(ElementThumbDelegate, self).__init__(parent)

  def paint(self, painter, options, index):
    super(ElementThumbDelegate, self).paint(painter, options, index)
    #painter.setRenderHint(QtGui.QPainter.Antialiasing)
    #painter.setPen(QtGui.QColor(255, 255, 255))
    #painter.setBrush(QtGui.QColor(10, 10, 10))
    #painter.drawRect(options.rect)
    #painter.drawText(options.rect, QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter, str(index.data()))

  #def sizeHint(self, options, index):
  #  return QtCore.QSize(50, 50)

def main():
  app = QtWidgets.QApplication(sys.argv)
  viewer = QtWidgets.QListView()

  viewModel = ElementListModel(elements)
  viewer.setModel(viewModel)
  #viewer.setViewMode(QtWidgets.QListView.IconMode)
  viewer.setItemDelegate(ElementThumbDelegate(viewer))

  viewer.show()
  sys.exit(app.exec_())

if __name__ == '__main__':
  main()

It results with this:

pyqt5-listview.png

Note that there is a box by default to the left of the item, which you can "color" via DecorationRole in the data method of the ListModel (and apparently, you can also store an icon there, if you return a QIcon instead of QColor, but I've never tried it).

My question is:

  • How can I draw a border around that icon space/box, depending on some property? In the example above, if MyElement.param == 'a' of a given element in the list, then I would want a light blue (RGB: (38, 76, 100), or #62c2ff) border of width 2 pixels drawn around the "icon space/box" - just like I've manually done in the mockup image in the circled area; otherwise I would not want a border
  • How could I additionally draw a single letter in the center of that space/box, depending on some property? For instance, if a MyElement.param == 'b' of a given element in the list, then I'd like the letter 'b' written in white in the middle of the "icon space/box" - otherwise, I would not want an extra text written in that space.

The paint() method of ElementThumbDelegate should have otherwise been enough of a pointer on how to do this; but if you uncomment it, you'll see the entire item is changed - not just the left icon box/space.

sdbbs
  • 4,270
  • 5
  • 32
  • 87
  • 1
    your question is confusing, could you clearly explain what you want to get. – eyllanesc Jul 06 '20 at 13:52
  • Thanks @eyllanesc - I modified the code and the questions, hopefully it is clearer now; otherwise let me know, and I'll try again – sdbbs Jul 06 '20 at 14:23

1 Answers1

2

The class that performs the painting is the delegate who takes the information from the DecorationRole role to create the icon, so the solution is to create the custom icon depending on the item information. That creation can be done in the model or in the delegate, in this case I will use the second option but for this the item must be exposed through a custom role like Qt.UserRole:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets


class MyElement(object):
    def __init__(self, numid):
        self.numid = numid
        self.strid = "Hello world {}".format(numid)
        self.param = "a" if numid % 2 == 0 else "b"

    def __repr__(self):
        return "(numid {}, strid '{}', param '{}')".format(
            self.numid, self.strid, self.param
        )


elements = [MyElement(i) for i in range(20)]


class ElementListModel(QtCore.QAbstractListModel):
    def __init__(self, elements=[], parent=None):
        super(ElementListModel, self).__init__()
        self.__elements = elements

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

    def data(self, index, role):
        if not index.isValid() or not (0 <= index.row() < self.rowCount()):
            return
        thiselement = self.__elements[index.row()]
        if role == QtCore.Qt.DisplayRole:
            return str(thiselement.strid)
        if role == QtCore.Qt.UserRole:
            return thiselement


class ElementThumbDelegate(QtWidgets.QStyledItemDelegate):
    def initStyleOption(self, option, index):
        super().initStyleOption(option, index)
        thiselement = index.data(QtCore.Qt.UserRole)
        if isinstance(thiselement, MyElement):
            if thiselement.param == "a":
                option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
                pixmap = QtGui.QPixmap(option.decorationSize)
                pixmap.fill(QtGui.QColor("#62c2ff"))

                painter = QtGui.QPainter(pixmap)
                color = QtGui.QColor(thiselement.numid * 10, thiselement.numid, 0)
                painter.fillRect(pixmap.rect().adjusted(2, 2, -2, -2), color)
                painter.end()
                option.icon = QtGui.QIcon(pixmap)

            if thiselement.param == "b":
                option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
                pixmap = QtGui.QPixmap(option.decorationSize)
                color = QtGui.QColor(thiselement.numid * 10, thiselement.numid, 0)
                pixmap.fill(color)

                painter = QtGui.QPainter(pixmap)
                painter.setPen(QtGui.QColor("white"))
                painter.drawText(pixmap.rect(), QtCore.Qt.AlignCenter, "b")
                painter.end()
                option.icon = QtGui.QIcon(pixmap)


def main():
    app = QtWidgets.QApplication(sys.argv)
    viewer = QtWidgets.QListView()

    viewModel = ElementListModel(elements)
    viewer.setModel(viewModel)
    viewer.setItemDelegate(ElementThumbDelegate(viewer))

    viewer.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

enter image description here

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Many thanks, it works great - the most important thing for me to learn was the need to create a custom icon (I thought maybe otherwise there was a role that I could use for, say, the border - but I just couldn't parse it from the documentation), great to know this now! – sdbbs Jul 07 '20 at 06:15