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

QML TableView refresh in realtime only updates UI every 1 second



  • Hi there!
    I have a task of showing big amount of data in realtime (as close to that as possible) with UI update every 10 to 100ms (best to worst values), I've managed to create a test model generating random numbers and populating the table view with new set of random values with a Timer. I tried to set different time intervals from 1 to 100 ms, and I can see the timer fires and the new set of data is created, but the UI updates strictly in 1 second every time despite the timer interval value.
    Can you guide me on how to handle UI updates in less than 1 second.

    P.S.: I tried different amount of data from tables of 50x1000 to just 50x50. Every time I get UI update rate of 1 second.

    Update:

    It is based on the "Game Of Life" Qt example, so some of the elements are just not used and are obsolete, but as soon as they are "disabled" I think they don't make any influence on the rest of the code and the problem itself.
    You can see that in nextStep() method I invoke timestamp logging to console and I can see in the output that the method is invoked according to the timer, but the UI only updates visually every second.

    main.qml

    ApplicationWindow {
        id: root
        visible: true
        width: 760
        height: 810
        minimumWidth: 475
        minimumHeight: 300
    
        color: "#09102B"
        title: qsTr("Conway’s Game of Life")
    
        //! [tableview]
        TableView {
            id: tableView
            anchors.fill: parent
    
            rowSpacing: 1
            columnSpacing: 1
    
            ScrollBar.horizontal: ScrollBar {}
            ScrollBar.vertical: ScrollBar {}
    
            delegate: Rectangle {
                id: cell
                implicitWidth: 45
                implicitHeight: 15
    
                color: model.value > 100 ? "#f3f3f4" : "#b5b7bf"
                Label {
                    width: parent.width
                    height: parent.height
                    text: model.value
                }
            }
            //! [tableview]
    
            //! [model]
            model: GameOfLifeModel {
                id: gameOfLifeModel
            }
            //! [model]
    
            //! [scroll]
            contentX: 0;
            contentY: 0;
            //! [scroll]
        }
    
        footer: Rectangle {
            signal nextStep
    
            id: footer
            height: 50
            color: "#F3F3F4"
    
            RowLayout {
                anchors.centerIn: parent
    
                //! [next]
                Button {
                    text: qsTr("Next")
                    onClicked: gameOfLifeModel.nextStep()
                }
                //! [next]
    
                Item {
                    width: 50
                }
    
                Button {
                    text: timer.running ? "❙❙" : "▶️"
                    onClicked: timer.running = !timer.running
                }
            }
    
            FpsItem {
                    id: fpsItem
                    anchors.left: parent
                    color: "black"
            }
    
            Timer {
                id: timer
                interval: 10
                running: true
                repeat: true
    
                onTriggered: gameOfLifeModel.nextStep()
            }
        }
    }
    

    gameoflifemodel.cpp

    GameOfLifeModel::GameOfLifeModel(QObject *parent)
        : QAbstractTableModel(parent)
    {
    }
    
    //! [modelsize]
    int GameOfLifeModel::rowCount(const QModelIndex &parent) const
    {
        if (parent.isValid())
            return 0;
    
        return height;
    }
    
    int GameOfLifeModel::columnCount(const QModelIndex &parent) const
    {
        if (parent.isValid())
            return 0;
    
        return width;
    }
    //! [modelsize]
    
    //! [read]
    QVariant GameOfLifeModel::data(const QModelIndex &index, int role) const
    {
        if (!index.isValid() || role != CellRole)
            return QVariant();
    
        return m_state[index.column()][index.row()];
    }
    //! [read]
    
    //! [write / not used]
    bool GameOfLifeModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
        if (role != CellRole || data(index, role) == value)
            return false;
    
        m_state[index.column()][index.row()] = value.toBool();
        emit dataChanged(index, index, {role});
    
        return true;
    }
    //! [write]
    
    Qt::ItemFlags GameOfLifeModel::flags(const QModelIndex &index) const
    {
        if (!index.isValid())
            return Qt::NoItemFlags;
    
        return Qt::ItemIsEditable;
    }
    
    //! [update]
    void GameOfLifeModel::nextStep()
    {
        srand(time(NULL));
    
        qDebug() << QTime::currentTime().toString("yyyy/MM/dd hh:mm:ss,zzz");
    
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                m_state[j][i] = (rand() % 1000) + 1;
            }
        }
    
        emit dataChanged(index(0, 0), index(height - 1, width - 1), {CellRole});
    }
    //! [update]
    

    gameoflifemodel.h

    //! [modelclass]
    class GameOfLifeModel : public QAbstractTableModel
    {
        Q_OBJECT
    
        Q_ENUMS(Roles)
    public:
        enum Roles {
            CellRole
        };
    
        QHash<int, QByteArray> roleNames() const override {
            return {
                { CellRole, "value" }
            };
        }
    
        explicit GameOfLifeModel(QObject *parent = nullptr);
    
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
        int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
        bool setData(const QModelIndex &index, const QVariant &value,
                     int role = Qt::EditRole) override;
    
        Qt::ItemFlags flags(const QModelIndex &index) const override;
    
        Q_INVOKABLE void nextStep();
    
    private:
        static constexpr int width = 50;
        static constexpr int height = 50;
        static constexpr int size = width * height;
    
        template <class T, size_t ROW, size_t COL>
        using NativeMatrix = T[ROW][COL];
        NativeMatrix<int, height, width> m_state;
    };
    //! [modelclass]
    

    Thank you!



  • Okay, the problem is solved. The issue appeared to be in the random number generation process, not in the table view.
    In the code I used

    srand(time(NULL))
    

    The time(NULL) returns seconds and not milliseconds, thus I was getting the same numbers and they were updated only after 1 second. In order to solve this I switched to using this to generate random numbers and everything worked well:

        struct timespec ts;
        clock_gettime(CLOCK_MONOTONIC, &ts);
    
        /* using nano-seconds instead of seconds */
        srand((time_t)ts.tv_nsec);
    

  • Qt Champions 2018

    Please share a minimalist reproducible example so we can help you.



  • Sorry, updated my question with details.



  • @GrecKo Any ideas? Thanks.



  • Okay, the problem is solved. The issue appeared to be in the random number generation process, not in the table view.
    In the code I used

    srand(time(NULL))
    

    The time(NULL) returns seconds and not milliseconds, thus I was getting the same numbers and they were updated only after 1 second. In order to solve this I switched to using this to generate random numbers and everything worked well:

        struct timespec ts;
        clock_gettime(CLOCK_MONOTONIC, &ts);
    
        /* using nano-seconds instead of seconds */
        srand((time_t)ts.tv_nsec);
    

  • Qt Champions 2018

    Why do you use srand at each step?


  • Lifetime Qt Champion

    Hi
    just as a note.
    rand() is not very random. or considered not optimal.
    If you can use c++ 11 then look into the new random function

    #include <random>
    #include <iostream>
    
    int main() {
        std::random_device rd;
        std::mt19937 mt(rd());
        std::uniform_real_distribution<double> dist(1.0, 10.0);
    
        for (int i=0; i<16; ++i)
            std::cout << dist(mt) << "\n";
    }
    

    https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful

    Its not critical/important if your Life game works.


Log in to reply