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

How to refresh data on horizontal header of QTableWidget?



  • Hi, all!

    I want to display cyclic data on horizontal header of QTableWidget, the cycle is 10ms.
    So I create a timer with 10ms interval, and set text in its timeout() slot:

    for (int32_t i = 0; i < 16; i++) {
        tableWidget->horizontalHeaderItem(i)->setText(QString::number(myData[i]));
    }
    

    But this causes the UI to be unsmooth.

    Then, I tried:

    for (int32_t i = 0; i < 16; i++) {
        tableWidget->item(0, i)->setText(QString::number(myData[i]));
    }
    

    It can refresh data smoothly.

    How to refresh data on horizontal header of QTableWidget, please?

    Best Regards!


  • Lifetime Qt Champion

    10ms - 100Hz - how should someone ever read this?

    Your way is correct, if you need speed use a custom model derived from QAbstractTableModel and QTableView.



  • @Christian-Ehrlicher
    Hi, thank you very much for your reply.
    "how should someone ever read this?", I think so, too.
    But my customer want to see the data refresh quickly...
    I have tried 100ms, unsmooth too.
    I will try to use QTableView, thanks again!


  • Lifetime Qt Champion

    Hi,

    They should also take into account the current refresh rates which are at 60fps on high end hardware.



  • @SGaist
    Hi,
    Thank you for your reply, it's very useful information, I have reduced the timer frequency to 10Hz.



  • @Christian-Ehrlicher
    I rewrite my code to use QAbstractTableModel and QTableView, but the UI still not smooth.
    The key code is "setHeaderData" in onPosCmd function´╝î when I block it, the UI run smoothly.
    Here is the relevant code:

    QVariant MotionSheetTableModel::headerData(int32_t section, Qt::Orientation orientation, int32_t role) const
    {
        if (role == Qt::DisplayRole) {
            if (orientation == Qt::Horizontal) {
                return horizontalHeaderList[section] + "\n" + posCmdList[section];
            } else if (orientation == Qt::Vertical) {
                return verticalHeaderList[section];
            } else {
                return QVariant();
            }
        }
        return QVariant();
    }
    
    bool MotionSheetTableModel::setHeaderData(int32_t section, Qt::Orientation orientation, const QVariant &value, int32_t role)
    {
        if (role == Qt::DisplayRole) {
            posCmdList[section] = QString::number(value.value<double>(), 'f', 2);
            emit headerDataChanged(orientation, section, section);
        }
        return true;
    }
    
    /* onPosCmd: triggered by timer(10Hz) */
    void MotionSheetWidget::onPosCmd(QVector<double> vector)
    {
        posCmd = vector;
    
        for (int32_t axisIndex = Axis::Index::Min; axisIndex <= Axis::Index::Max; axisIndex++) {
            tableModel->setHeaderData(axisIndex, Qt::Horizontal, posCmd.at(axisIndex), Qt::DisplayRole);
        }
    }
    

    Could you give me some suggestion, please?

    Best Regards!


  • Lifetime Qt Champion

    @tovax said in How to refresh data on horizontal header of QTableWidget?:

    Could you give me some suggestion, please?

    As above - don't try to update gui stuff at 100Hz.



  • @Christian-Ehrlicher
    Frequence has been modified to10Hz, and no difference from 100Hz.
    Best Regards!


  • Lifetime Qt Champion

    @tovax
    Hi
    For each setHeaderData, you emit a headerDataChanged
    so i would guess on lots of view repaints are issued. (for each axisIndex)
    Since you always want to refresh whole header, i was wondering if you could test with
    delaying the headerDataChanged signal until after the loop.

    for (int32_t axisIndex = Axis::Index::Min; axisIndex <= Axis::Index::Max; axisIndex++) {
            tableModel->setHeaderData(axisIndex, Qt::Horizontal, posCmd.at(axisIndex), Qt::DisplayRole);
        }
     
    tableModel->HeaderChange(Axis::Index::Min, Axis::Index::Max ); // this issues the signal with 
                      headerDataChanged(orientation,Min,Max );
    
    


  • @mrjj
    Hi,
    According to your suggestion, I blocked "headerDataChanged" signal, and add this public function to Model Class:

    void MotionSheetTableModel::HeaderChange(int32_t first, int32_t last)
    {
        emit headerDataChanged(Qt::Horizontal, first, last);
    }
    

    But no significant improvement in test results.
    Is my understanding right, please?

    Best Regards!


  • Lifetime Qt Champion

    @tovax
    If by blocked you mean
    // emit headerDataChanged(orientation, section, section);
    and call your new function (HeaderChange) after the loop then we are on same page.

    Ok so that didnt speed it up. :(
    Im not sure how altering the list
    posCmdList[section] = QString::number(value.value<double>(), 'f', 2);
    would be slow.

    I think i would insert some timing functions
    (https://doc.qt.io/qt-5/qelapsedtimer.html is handy for that)
    and see where the time is spend.
    Now we know its not from pure updates so we need to find the spot where the time is used.

    Also 10 times pr second does not sound that massive but I would try other values to see
    where the sweet spot it.



  • @mrjj
    I add this debug code:

    bool MotionSheetTableModel::setHeaderData(int32_t section, Qt::Orientation orientation, const QVariant &value, int32_t role)
    {
        Q_UNUSED(orientation);
        if (role == Qt::DisplayRole) {
            QElapsedTimer timer;
            timer.start();
            posCmdList[section] = QString::number(value.value<double>(), 'f', 2);
            qDebug() << "MotionSheetTableModel::setHeaderData" << timer.nsecsElapsed();
            // emit headerDataChanged(orientation, section, section);
        }
    
        return true;
    }
    

    Result as follow:

    MotionSheetTableModel::setHeaderData 28158
    MotionSheetTableModel::setHeaderData 27646
    MotionSheetTableModel::setHeaderData 28158
    MotionSheetTableModel::setHeaderData 27646
    MotionSheetTableModel::setHeaderData 28158
    MotionSheetTableModel::setHeaderData 28670
    MotionSheetTableModel::setHeaderData 53244
    MotionSheetTableModel::setHeaderData 31741
    MotionSheetTableModel::setHeaderData 28158
    MotionSheetTableModel::setHeaderData 28158
    MotionSheetTableModel::setHeaderData 28670
    MotionSheetTableModel::setHeaderData 28158
    MotionSheetTableModel::setHeaderData 28158
    MotionSheetTableModel::setHeaderData 27646
    MotionSheetTableModel::setHeaderData 28158
    MotionSheetTableModel::setHeaderData 28158
    MotionSheetTableModel::setHeaderData 28670
    MotionSheetTableModel::setHeaderData 27646
    MotionSheetTableModel::setHeaderData 28670
    MotionSheetTableModel::setHeaderData 28670
    

    Nanoseconds, It looks fast enough.

    Best Regards!


  • Lifetime Qt Champion

    Hi
    Yes it does indeed look ok fast.
    I would then disable the new function to check if app still feels
    laggy and its related to View redrawing.
    ( not send the headerDataChanged signal)

    i know you wont see the updates then but its a ok test to see if you are stressing
    the event loop anyways.

    If app still laggy with View not repainting it means the event loop is stressed.



  • @mrjj
    Hi,
    When not send the headerDataChanged signal, the app run smoothly.
    By the way, if the data display in the table cell, the app run smoothly too.



  • @mrjj
    Here is the system time in relevent function:

    onPosCmd - start "22:06:33:908"
    setHeaderData 0 "22:06:33:908"
    setHeaderData 1 "22:06:33:908"
    setHeaderData 2 "22:06:33:909"
    setHeaderData 3 "22:06:33:909"
    setHeaderData 4 "22:06:33:909"
    setHeaderData 5 "22:06:33:910"
    setHeaderData 6 "22:06:33:910"
    setHeaderData 7 "22:06:33:910"
    setHeaderData 8 "22:06:33:911"
    setHeaderData 9 "22:06:33:911"
    setHeaderData 10 "22:06:33:911"
    setHeaderData 11 "22:06:33:911"
    setHeaderData 12 "22:06:33:912"
    setHeaderData 13 "22:06:33:912"
    setHeaderData 14 "22:06:33:912"
    setHeaderData 15 "22:06:33:913"
    onPosCmd - end "22:06:33:913"
    headerData 0 "22:06:33:953"
    headerData 1 "22:06:33:954"
    headerData 8 "22:06:33:956"
    headerData 9 "22:06:33:957"
    headerData 10 "22:06:33:958"
    headerData 11 "22:06:33:959"
    headerData 12 "22:06:33:960"
    headerData 13 "22:06:33:961"
    headerData 14 "22:06:33:962"
    headerData 15 "22:06:33:963"
    headerData 0 "22:06:33:976"
    headerData 1 "22:06:33:978"
    headerData 8 "22:06:33:980"
    headerData 9 "22:06:33:982"
    headerData 10 "22:06:33:984"
    headerData 11 "22:06:33:985"
    headerData 12 "22:06:33:987"
    headerData 13 "22:06:33:989"
    headerData 14 "22:06:33:990"
    headerData 15 "22:06:33:992"
    

    It doesn't seem to waste much time on signaling.


  • Lifetime Qt Champion

    What resize mode do you use? And can you provide a small test example so we can run it e.g. with callgrind to see where the time is spend.



  • @mrjj
    From "setHeaderData" to "headerData" waste 40ms. Will this have an impact, please?



  • @Christian-Ehrlicher
    Hi,
    The resize mode is QHeaderView::Stretch, this is the config of tableView:

        tableView = new QTableView(this);
        tableView->setModel(tableModel);
    
        tableView->setFrameShape(QFrame::NoFrame);
    
        tableView->horizontalHeader()->setVisible(true); 
        tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    
        tableView->verticalHeader()->setVisible(true); 
        tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    
        tableView->setSelectionBehavior(QAbstractItemView::SelectItems);
        tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
        tableView->setAlternatingRowColors(true);
    

  • Lifetime Qt Champion

    @tovax said in How to refresh data on horizontal header of QTableWidget?:

    The resize mode is QHeaderView::Stretch

    Ok, this is at least not a problem for the update. So please provide a small testcase.



  • This post is deleted!


  • @Christian-Ehrlicher
    Here is the github link:
    TableView
    Best Regards!


  • Lifetime Qt Champion

    @tovax: I'll give it a try today, thx


  • Lifetime Qt Champion

    Looks like the culprit is that a header data change triggers a complete repaint through QAbstractItemView::updateGeometries() which is very expensive. It doesn't matter if you call headerDataChanged() once per section or completely - it's a delayed trigger.



  • @Christian-Ehrlicher
    Hi,
    In other words, can't I update the data on the horizontal header, please?
    Best Regards!


  • Lifetime Qt Champion

    @tovax
    Hi
    Nope, not as fast as you wish.
    On my (albeit old) gamer pc, it uses 28% CPU to refresh it.
    So for less powerful system, you will have to find another way to do the same
    than using the headers. Its too expensive to be used in this way.
    ( even 'just' 10 times pr second )

    So basically its not a supported use case with Qt to have high-speed header refresh. That said you might be able to fix it with a custom HeaderView
    but if there is enough virtual function to override this behavior, i cannot say currently.


  • Lifetime Qt Champion

    Or, as we said already more times - don't try to update at 100Hz - noone can ever read this...



  • OK, Thanks for both of you! I'll think about other ways.
    Best Regards!



  • Solved:

    1. Derived from QAbstractTableModel + QTableView
    2. Create two QTableView, one for cell's data, another for header data.
    3. Frozen the first row of the QTableView used for header data, and refresh data in it (100Hz).
    4. (Refer to official demo: Frozen Column Example)

Log in to reply