Qt World Summit: Register Today!

QTableView item color to vary by value

  • I need to have the colors of items shown in a QTableView vary depending on the value in each cell. [Specifically, some values/columns are monetary amounts, if the amount in a cell is negative I want it in red, else the default black.]

    As some of you may know, I do research things, so you may be assured I have considered different approaches. I do not need detailed code for an answer, but I do need to know what approach you are recommending.

    Some of my constraints include:

    • The code has to be generic for all my (sub-classed) QTableViews. I may need for QTableWidgets too. Therefore I do not know in advance which columns will need this.

    • My tables can have any model type from: QSqlQueryModel, QSqlTableModel, QStandardItemModel. Solution must work for all 3.

    • I can recognise a "monetary value" by the fact that the value is "a floating point number". For me, all such values will be monetary.

    I suspect/presume it comes down to a choice between model.data(ForegroundRole) vs QStyledItemDelegate.
    My natural preference, for both simplicity & general applicability, is data(ForegroundRole), but I admit I'm finding it messy when that depends on the value.

    At this point I'd really like to put this question out there to see whether (a) anybody is going to answer(!) and (b) what they're going to suggest. Then I'll respond from there. TIA!

  • Lifetime Qt Champion


    What about a proxy model like QIdentityProxyModel ?

  • @SGaist
    You are a man of few words! :) I have read up about QIdentityProxyModel. Let's see if I understand what you are suggesting.

    For example, a proxy model could be created to define the font used, or the background colour, or the tooltip etc. This removes the need to implement all data handling in the same class that creates the structure of the model, and can also be used to create re-usable components.

    So, I think this could be used because I said I have a variety of model types. This would then allow me to put the logic for foreground color/role in one place, rather than in each of my 3 sub-classes of these. Is that what you had in mind?

    However, that itself is not my problem. My question revolves around the fact that in order to decide what color I want I need to look at the value of each item. This is the case whether I implement via data(ForegroundRole) or via QStyledItemDelegate (which represent very different ways of approaching setting the color).

    So, for example, if I go down my (preferred) route of doing it by role instead of render delegate, whether or not I use a proxy model I will end with code for sub-classed model.data() which has to look like:

    def data(self, index: QtCore.QModelIndex, role: QtCore.Qt.ItemDataRole=QtCore.Qt.DisplayRole) -> typing.Any:
        if role == Qt.ForegroundRole:
            dataVal = super().data(index, Qt.EditRole):
            if isinstance(dataVal, float):
                if dataVal < 0.0:
                    return QtGui.QColor(QtCore.Qt.red)

    The point here is that when called for data(index, ForegroundRole) my code has to in turn call data(index, EditRole) to retrieve the data value in order to decide what to return for the color. I would still need to do same access to data value if I did my color via QStyledItemDelegate::paint().

    That is what "worries" me. I don't know how "costly" that is, or whether there is any better way. If this is "fine" & "normal" for accessing the data value when deciding the color then please say so and I am good to go?

  • Lifetime Qt Champion

    I would say that this is the normal way but likely not the most efficient.

    One alternative would to store the foreground color when a value changes. It will cost memory in place of time spent to access each value when the role is requested.

  • @SGaist
    Thank you for replying.

    I have implemented the data() approach and it works.

    I did consider overriding setData() to store the color against the value at assignment time. I rejected it for 2 reasons:

    • When using QSqlTableModel, query result set population do not cause setData() to be called. So colors would not get set initially.

    • When using QSqlQueryModel, as above, plus as it's read-only, there is a data() but not a setData().

    Am I right about these? Did you mean that I could have written the code in slot for QAbstractItemModel::dataChanged signal instead of overriding setData()?

  • Lifetime Qt Champion

    dataChanged is only a signal, but yes, you could connect it to a slot to update the foreground color if you keep it somewhere.

  • @SGaist
    Yes, I re-phrased. Last question, promise: would query/table models emit it as result set is read back in, whereas setData() does not get called there? [Cannot test atm.]

  • Lifetime Qt Champion

    Sorry, I'm not sure I'm following that last question.

  • @SGaist
    When result set is received from db via something like QSqlQueryModel::setQuery() rows/columns in model are populated. I believe setData() is not called/involved. How would I recognise this (so as to populate colors for initial values received): will model emit dataChanged(), or would I need to handle rowsAboutToBeInserted() or beginInsertRows() or modelReset() or what?

  • Lifetime Qt Champion

    Since it's pretty heavy change, I would check for the modelReset signal in this case.

  • @SGaist
    OK, it was really for interest, thanks for your time. In my case as I said I have implemented via calculated return result from data() rather than setting the color by pre-storing it. It seems efficient enough for my purposes.

Log in to reply