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

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 populated QSqlQueryModel.
    • 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?!


  • Moderators

    @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?



  • @raven-worx

    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 a QTableView?

    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 generic float. So when data() is called with Qt::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 return Qt::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


  • Moderators

    @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 via QTableView 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 a ItemDataRole 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 for DisplayRole. I'd be obliged if you'd enlighten me? Thanks.


  • Moderators

    @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
    };
    


  • @raven-worx

    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 the data() method which causes a conversion to happen (in this case to QString).

    What I am asking is: which ItemDataRole value passed to data() method will return the data as close as possible to whatever the underlying QVariant(?) is which QSqlQueryModel has (presumably) actually stored somewhere when it received the value from the database? It's not, say, DisplayRole or EditRole as these both convert to QString. So which ItemDataRole does return the underlying QVariant 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 existing ItemDataRole which will just return the stored QVariant? Can I access the stored data in some direct fashion to get this, e.g. not via QSqlQueryModel::data()?


  • Moderators

    @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 with Qt::DisplayRole for the ItemDataRole, will actually return a QVariant for a QString 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 with QVariant::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 to QVariant::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 role DisplayRole or EditRole are basically going to do that for me after all?


  • Moderators

    @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.


  • Lifetime Qt Champion

    Hi,

    To add to @raven-worx, QStyledItemDelegate::displayText will be used (unless a different delegate has been set)


  • Lifetime Qt Champion

    @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 the data() method itself was responsible for converting the underlying value and returning it is a QVariant with a type of QMetaType::QString. And similarly for all the other ItemDataRole cases as appropriate. If the docs had stated "the returned QVariant 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.


Log in to reply