Solved 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)
QTableView
s. I may need forQTableWidget
s 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)
vsQStyledItemDelegate
.
My natural preference, for both simplicity & general applicability, isdata(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!
-
-
Hi,
What about a proxy model like QIdentityProxyModel ?
-
@SGaist
You are a man of few words! :) I have read up aboutQIdentityProxyModel
. 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 viaQStyledItemDelegate
(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 calldata(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 viaQStyledItemDelegate::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?
-
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 causesetData()
to be called. So colors would not get set initially. -
When using
QSqlQueryModel
, as above, plus as it's read-only, there is adata()
but not asetData()
.
Am I right about these? Did you mean that I could have written the code in slot for
QAbstractItemModel::dataChanged
signal instead of overridingsetData()
? -
-
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, whereassetData()
does not get called there? [Cannot test atm.] -
Sorry, I'm not sure I'm following that last question.
-
@SGaist
When result set is received from db via something likeQSqlQueryModel::setQuery()
rows/columns in model are populated. I believesetData()
is not called/involved. How would I recognise this (so as to populate colors for initial values received): will model emitdataChanged()
, or would I need to handlerowsAboutToBeInserted()
orbeginInsertRows()
ormodelReset()
or what? -
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 fromdata()
rather than setting the color by pre-storing it. It seems efficient enough for my purposes.