Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QItemDelegate: colour all text in cells of a row



  • I am an amateur programmer learning to use PyQt5. I am currently coding an application to store and analyse physical training activity.

    One of my forms prints out a QTableView listing previous years results with the last cell indicating a percentage which compares this season with previous seasons. When the previous season's total is surpassed an "Ok" was added to the percentage displayed
    Image:
    0_1564840366290_bikeResults-img.png

    "Ok" was added because I didn't know how to colour the text to indicate that the target was surpassed.

    Since then I have learned about QItemDelegate and have successfully coloured the "%" column but can't seem to code the other cells in the same row. Using the sibling() method I can get the text from the two previous cells but can't find a way of getting the option.rect data need to paint the text red.
    Code to date:

    class MyDelegate(QItemDelegate):
        def __init__(self, parent=None, *args):
            QItemDelegate.__init__(self, parent, *args)
            
        def paint(self, painter, option, index):
            painter.save()
            
            # set text color
            painter.setPen(QPen(Qt.black))
            text = index.data(Qt.DisplayRole)
            
            #index1 = index.sibling(index.row(), 1)
            #text1 = index1.data(Qt.DisplayRole)
            
            if 'Ok' in text:
                painter.setPen(QPen(Qt.red))
            
            painter.drawText(option.rect, Qt.AlignCenter, text)
            painter.restore()
    

    Any help will be appreciated.



  • maybe try something like this which removes the need for Ok

    def paint(self, painter, option, index):
            if siblingAtColumn(INDEX_OF_%_COLUMN).data() > 100:
               # red text if surpassed
                painter.save()
                painter.setPen(QPen(Qt.red))     
                painter.drawText(option.rect, Qt.AlignCenter, text)
                painter.restore()
            else:
               # default black text if not surpassed
                super().paint(painter, option, index)
    


  • Thanks for the input.

    I tried the code but as written "if siblingAtColumn(INDEX_OF_%_COLUMN).data() > 100"
    siblingAtColumn is not identified. I changed the the code line to :

    if index.siblingAtColumn(INDEX_OF_%_COLUMN).data() > 100:

    but I get an error:
    Unhandled AttributeError "'QModelIndex' object has no attribute ' siblingAtColumn'"

    This error I don't understand because QModelIndex does have siblingAtColumn function.

    I don't understand the parameter "INDEX_OF_%_COLUMN". How is it initialize?



  • I didn't write the code properly I was just trying to give you the general idea of what to try. by INDEX_OF_%_COLUMN , i was referring to whatever your model's section value is for the '- % -' column which is the 3rd column in the table, judging from just the table I would assume it to be = 2.
    so something like :

    # where column 2 refers to the column that determines if your target is surpassed.
    if index.siblingAtColumn(2).data() > 100:
    # OR
    row = index.row()
    if index.sibling(row, 2).data(Qt.DisplayRole) > 100
    

    As for the error, I don't think PyQt4 has any siblingAtColumn method for model indexes, They are in PySide2. Since this looks empty idk if its in pyqt5 either..

    or maybe you're on an earlier version since they were implemented after 5.11?

    Another way to do it would be to directly access your model's data in the following way:

    def paint(self, painter, option, index):
            row = index.row()
            model = index.model() # Incase you don't have access to your model
            surpassed_column_data = model.data(model.index(row, 2), Qt.DisplayRole)
            # might have to cast this as a float if you're displaying as string and strip the % sign
            col3 = float(surpassed_column_data.strip('%'))
            if col3 > 100:
               # red text if surpassed
                painter.save()
                painter.setPen(QPen(Qt.red))     
                painter.drawText(option.rect, Qt.AlignCenter, text)
                painter.restore()
            else:
               # default black text if not surpassed
                super().paint(painter, option, index)
    


  • Thanks Erudite Monkey for the input,

    I tried to convert the original code you proposed to python string using QString because of the error:

    col3 = float(surpassed_column_data.strip('%'))
    AttributeError: 'QVariant' object has no attribute 'strip'
    

    The code I tried received an error QVariant' object has no attribute 'toString'.

    Printing the QVariant.Type came back as a QString type

    surpassed_column_data.canConvert(QMetaType.QString)
    

    came back as True.

    I am new at PyQt and a little stumped as to why.

    my current versions are:
    Qt version: 5.9.5
    SIP version: 4.19.7
    PyQt version: 5.10.1

    Do I need to upgrade my Qt version to 5.13 to solve the problem.

    In your first post you mentioned Pyside2. I made a quick research and found out that pyside2 is also called Qt for Python. I originally thought that this forum (Qt for Python) was for PyQt.

    Further information: My operating system is Mint cinnamon 19.1. I went to the mint Forum to question on how best to upgrade Qt to 5.13

    A couple of users informed me that mint requires Qt 5.9 to operate software correctly and that an upgrade could cause execution problems on my system.

    I have also remembered that I recently upgraded PyQt to version 5.10. I am wondering if my string conversion problems are related ???



  • You should be working with virtual environments so no package installations interfere with your system. some common ones for python are :

    with virtual environment managers like pipenv, updating pyqt is as simple as just typing :

    pipenv update pyqt
    or
    pipenv update pyside2
    

    I'm not familiar with mint so I can't really say much about that. But it looks like your PyQt version 5.10 doesn't have sibling methods etc. I am using pyside2 with conda since I work with math stuff a lot, I would suggest you to look into pipenv for environment management since anaconda seems unnecessary. I would suggest to install pyqt or pyside2 in a virtual environment along with all the rest of your packages for this project with pipenv. If that seems too much of a hassle then try to work with virtual environments for future projects and for now you can just cast it as a string first manually:

    col3 = str(surpassed_column_data).strip("%")
    col3 = float(col3)
    


  • I will try a virtual environment. In the past I have come across many references to it. Never go around to trying it. I guess the time is now. I've also installed PySide2 started the process of learning the differences which pyside which I had tried years ago.



  • @liamdale

    Erudite Monkey

    Your code suggestion worked with a minor modification. Before I post the code I must say that I downloaded and installed PySide2 and started to work with it. It has all the advantages of PyQt without the translation code to python. Python strings are python strings not QStrings.

    I took your first code suggestion of six days ago and removed from my code both the ' % ' and
    ' ok '.

    I added the line from my code to yours:
    text = index.data(Qt.DisplayRole)

    The final code is:

    class MyDelegate(QItemDelegate):
        def __init__(self, parent=None, *args):
            QItemDelegate.__init__(self, parent, *args)
            
        def paint(self, painter, option, index):
            text = index.data(Qt.DisplayRole)
            if float(index.siblingAtColumn(2).data().strip('%')) > 100:
               # red text if surpassed
                painter.save()
                painter.setPen(QPen(Qt.red))     
                painter.drawText(option.rect, Qt.AlignCenter, text)
                painter.restore()
            else:
               # default black text if not surpassed
                super().paint(painter, option, index)
    

    The image
    0_1565401060940_Colour foreground.png

    Final code (including % notation)
    Thanks for the help


Log in to reply