Search code examples
pythonpyqtpyqt5qitemdelegate

Using QItemDelegate to show icons in place of text in a table in PyQt5


With PyQt5 I am trying to use QItemDelegate to show an icon instead of a text string in a cell in a table. Essentially I construct a subclass of the QItemDelegate using:

de = MyDelegate(self.attribute_table_view)

Here dself.attribute_table_view is a `QTableView' object.

I try to draw an icon in every cell in a specific column using:

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

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

    painter.save()
    value = index.data(Qt.DisplayRole)

    line_1x = QPixmap('line_1x.png')

    painter.setBrush(Qt.gray)
    painter.setPen(Qt.black)
    painter.drawPixmap(QRectF(0, 0, 48, 24), line_1x, QRectF(0, 0, 48, 24))
    painter.restore()

With the painter.drawPixmap() how do I tell it to draw in each cell in the table like one achieves using painter.drawText(option.rect, Qt.AlignVCenter, value)?

Also, I have noticed that my current script does not report any errors if I enter a filename that doesn't exist for the .png file. Should an error by reported if the .png file does not exist?

My current model is a QgsAttributeTableModel and I want to render the current string value for all cells in one column with icon's where the icon used depends on the string value.


Solution

  • In this answer I will show several methods, and you can choose according to the complexity of the problem.

    1. The number of icons are fixed and one column will be reused.

    The logic is to load the icons once, and pass it as an attribute to the delegate, then depending on your logic you get the icon of the list for it modifies get_icon() method. and we paint the icon through the paint() method of QIcon.

    class MyDelegate(QtWidgets.QStyledItemDelegate):
        def __init__(self, icons, parent=None):
            super(MyDelegate, self).__init__(parent)
            self._icons = icons
    
        def get_icon(self, index):
            # get the icon according to the condition:
            # In this case, for example, 
            # the icon will be repeated periodically
            icon =  self._icons[ index.row() % len(self._icons) ]
            return icon
    
        def paint(self, painter, option, index):
            icon = self.get_icon(index)
            icon.paint(painter, option.rect, QtCore.Qt.AlignCenter)
    

    How to reuse a column you must use the setItemDelegateForColumn() method to set the delegate to a column

    self.attribute_table_view = QtWidgets.QTableView()
    self.attribute_table_view.setModel(your_model)
    
    column_icon = 1
    icons = [QtGui.QIcon(QtCore.QDir.current().absoluteFilePath(name)) for name in ["clear.png", "heart.png","marker.png", "pen.png"]]
    delegate = MyDelegate(icons, self.attribute_table_view)
    self.attribute_table_view.setItemDelegateForColumn(column_icon, delegate)
    

    enter image description here

    I have noticed that my current script does not report any errors if I enter a filename that doesn't exist for the .png file. Should an error by reported if the .png file does not exist?

    Qt will not notify if the file does not exist, you have to verify, for example with the isNull() function. There are 2 ways to notify:

      1. The first one is to return a boolean indicating if the data is loaded or not, but when using a constructor it only returns the constructed object and throws.

    1. Launch exceptions, this consumes many resources that Qt considers unnecessary so you will never use it.

    Another way that especially Qt notifies that there is an error is through signals but these are only for QObject and QIcon, QPixmap, QImage are not QObjects.

    So in conclusion the responsibility to verify or not falls on the developer.