Solved QTableView and redrawing on window up-fronting "efficiency"
-
- I have a standard Qt (5.7) GUI app.
- I am under Linux, if that makes any difference.
- I have a
QTableView
on a dialog/window. - The
QTableView
is linked to a populatedQSqlQueryModel
. - I have overridden the
QSqlQueryModel::data()
method. - I had placed a breakpoint in my debugger on that overridden method.
- To my surprise, I find that every time I click to bring the app window up-front during debugging,
QSqlQueryModel::data()
is being called. Hundreds of times, with all its different indexes & roles.
I did not think up-fronting would cause this. I can only imagine that in some shape or form the
QTableView
is being re-painted, as a result of some obscured part of the window now being uncovered, and its implementation is to re-execute all the code to get values to display for the data, colors, etc. etc., which would presumably have been called in the first place to draw the table.This scares me! I had intuitively assumed that, once having been drawn, up-fronting/unobscuration would be handled at some level by some kind of caching/retrieving of the required rectangle, probably by the native windowing system itself. But it appears not.
What's going on here, please?!
-
@JonB said in QTableView and redrawing on window up-fronting "efficiency":
What's going on here, please?!
your assumption is right - the calls are caused by the repaint.
Why did you override the data() method?
-
your assumption is right - the calls are caused by the repaint.
Hmm, interesting. I had not expected it to have redraw from scratch in this way. I had expected the windowing system to handle un-obscuration itself, having in effect saved in a buffer what the obscured area looked like. Presumably my expectations were wrong! Unfortunately, it sure makes debugging a pain :( Would this apply just as much to, say, a
QLineEdit
as to aQTableView
?Why did you override the data() method?
Well, since you're interested enough to ask... :) Here are my present reasons. Please feel free to suggest if you think any are the wrong way to do it:
Reason #1
I have inherited an existing project with a large body of code. It is "not well written". That means, it would not surprise me if there are bugs lurking all over the place, potentially serious ones.
My first "clean up" is to ensure all data accesses/updates are thoroughly checked for any hidden errors. http://doc.qt.io/qt-5/qsqlquerymodel.html#data states:
If item is out of bounds or if an error occurred, an invalid QVariant is returned.
That's no good at all! Thousands of calls to this function exist, without error checking on the result. I have to change that case to
throw
, else we'll never be aware of them....Reason #2
Nearly all the floating-point columns in the database are actually defined in MySQL as
DECIMAL(8, 2)
. That means, 2 decimal places, without any loss of precision (yes, they are money amounts). Unfortunately for me, the Qt SQL code converts all these database types to a genericfloat
. So whendata()
is called withQt::DisplayRole
to generate string representation, I do not get what I need for the format. I have to check for that by overriding and return"%.2f"
to get back what it should be.Reason #3
I now want to make numbers (such as above) to be right-justified. So I want to intercept
Qt::TextAlignmentRole
to returnQt::AlignRight
. (Having said that, I am presently investigating why it does not seem to be working for me as per https://forum.qt.io/topic/90777/qsqlquerymodel-data-not-called-with-qt-textalignmentrole)Over time I may add more cases, e.g. I may decide to display negative amounts in color red via
Qt::ForegroundRole
.All this as per, say, examples code in http://doc.qt.io/qt-5/modelview.html
-
@JonB said in QTableView and redrawing on window up-fronting "efficiency":
Would this apply just as much to, say, a QLineEdit as to a QTableView?
yes, this is the painting system of Qt.
That's no good at all! Thousands of calls to this function exist, without error checking on the result. I have to change that case to throw, else we'll never be aware of them....
When you only use the model in a QAbstractItemView the case of an invalid index is not very common. So don't worry too much about it.
Just a side note (for Qt 5.11). -
@raven-worx
Unfortunately, it is not the case that, say,QSqlQueryModel::data(QModelIndex, ...)
is only called viaQTableView
etc.There are thousands of occurrences of straight, explicit:
index = QModelIndex(naught_row, naughty_column) value = model.data(index, [role])
dotted through existing code, and similarly for
setData(index)
, which is even more worrying! So I need to trap those, don't I?Are you suggesting that user code should not explicitly call
QSqlQueryModel::data(QModelIndex, ...)
to get data value from a model, say outside of anything table-viewy? If so, how should it access the data? I have been meaning to ask this anyway, as I do not see aItemDataRole
value which means "just get the underlying data value, however the model read it from the database and stored it", I don't always want it converted to, say, a string forDisplayRole
. I'd be obliged if you'd enlighten me? Thanks. -
@JonB said in QTableView and redrawing on window up-fronting "efficiency":
There are thousands of occurrences of straight, explicit:
index = QModelIndex(naught_row, naughty_column)
value = model.data(index, [role])But anyway. If data is requested with an invalid index it's the responsibility of the caller. All you could do is to return an invalid QVariant. Would the code still run correct with such an invalid value?
I don't always want it converted to, say, a string for DisplayRole
are you sure it's always converted to a QString by the QSqlQueryModel? IIRC the returned variant follows the data type from the database as much as possible.
Beside that you can also add custom roles and return whatever you like:
enum CustomItemRole { MyItemRole = Qt::UserRole+1 };
-
But anyway. If data is requested with an invalid index it's the responsibility of the caller. All you could do is to return an invalid QVariant. Would the code still run correct with such an invalid value?
It may well be the responsibility of the caller. But unfortunately the hundreds of callers have not been written (by other people) to check that! The danger is they will proceed with an incorrect value. If I'm lucky they will later run into some error, but if I'm not they will proceed without error but not behave correctly, and I would never know about it. That's why I need to override it to
throw
, so that I can track them down!are you sure it's always converted to a QString by the QSqlQueryModel? IIRC the returned variant follows the data type from the database as much as possible.
It's the role of, say,
DisplayRole
(and most of the others) passed to thedata()
method which causes a conversion to happen (in this case toQString
).What I am asking is: which
ItemDataRole
value passed todata()
method will return the data as close as possible to whatever the underlyingQVariant
(?) is whichQSqlQueryModel
has (presumably) actually stored somewhere when it received the value from the database? It's not, say,DisplayRole
orEditRole
as these both convert toQString
. So whichItemDataRole
does return the underlyingQVariant
as-is?Beside that you can also add custom roles and return whatever you like:
I would be happy to do that. The problem is not defining an extra custom item role, it's what code to write to return the desired value for that role? How do I access the underlying data value stored without going via
QSqlQueryModel::data(some-ItemDataRole)
if there is no existingItemDataRole
which will just return the storedQVariant
? Can I access the stored data in some direct fashion to get this, e.g. not viaQSqlQueryModel::data()
? -
@JonB said in QTableView and redrawing on window up-fronting "efficiency":
It's the role of, say, DisplayRole (and most of the others) passed to the data() method which causes a conversion to happen (in this case to QString).
As i said, are you sure a conversion happens?!
Check the type of the variant returned from QSqlModel::data() for Qt::DisplayRole. -
@raven-worx
Hmm, this is very interesting after all, thanks....Look at the documentation page I have been following, http://doc.qt.io/qt-5/qt.html#ItemDataRole-enum. See for example how it states:
Qt::DisplayRole 0 The key data to be rendered in the form of text. (QString)
See that
QString
at the end? All of the enumerated values have some similar type annotation.Now, I took this to mean: the base
QSqlQueryModel::data()
, when called withQt::DisplayRole
for theItemDataRole
, will actually return aQVariant
for aQString
to be used for the data.However, debugging I see that the base returns, for example for an int received from the database, a
QVariant
withQVariant::type() == QMetaType::Int
. And presumably callers are required to convert that to string for display if required. I had assumed the docs meant it should return an already converted toQVariant::type() == QMetaType::QString
for, say, an int, which would have lost me further underlying information. But so far it seems not so....I am investigating further. If you have a comment on your understanding of the docs/behaviour that would be great. From what we are seeing, if I do want to return as close as possible to the underlying value
QVariant
, are you suggesting that roleDisplayRole
orEditRole
are basically going to do that for me after all? -
@JonB
yes the docs may be a bit confusing on that point.
But basically the view is responsible to display the value. Which in ~99% of the cases is a string. But this is done by the view/delegate by calling toString() on the variant. -
Hi,
To add to @raven-worx, QStyledItemDelegate::displayText will be used (unless a different delegate has been set)
-
@JonB said in QTableView and redrawing on window up-fronting "efficiency":
Qt::DisplayRole 0 The key data to be rendered in the form of text. (QString)
I don't see any problems here. It just says that the returned QVariant is converted into a string for displaying and not e.g. into a QImage (like .e.g. for the DecorationRole) or QRect for the SizeHintRole or anything else. If the returned QVariant can't be converted into a QString, nothing can be displayed.
-
@Christian-Ehrlicher
I can only say that I personally interpreted it as: if, say,Qt::DisplayRole
was documented with(QString)
at the end of line, it meant that thedata()
method itself was responsible for converting the underlying value and returning it is aQVariant
with a type ofQMetaType::QString
. And similarly for all the otherItemDataRole
cases as appropriate. If the docs had stated "the returnedQVariant
must be convertible to the specified type for each role" I would have understood. It's always the case that documentation can imply different things to different readers; I did not find it clear. But there you are.Anyway, in the light of this discovery I now understand better what is actually going on, thank you.