How to populate a QTableView with asynchronous extracted data?

I want to populate QTableView with some received data (using, for example, a database or network request) after creating it. Since the request takes some time - thus blocking the GUI - I came to the conclusion that using another thread to retrieve.

My current setup looks like this (obviously simplified):

class MyTable : public QTableView {
    QFutureWatcher<QAbstractItemModel*>* watcher;

    void init() {
        watcher = new QFutureWatcher<QAbstractItemModel*>();
        connect(watcher, SIGNAL(finished()), this, SLOT(onResult()));
        watcher->setFuture(QtConcurrent::run(...))
    }

    void onResult() {
        setModel(watcher->result());
    }
}

Init () is called - methode after adding an object to the GUI. Since I am completely new to C ++ / Qt / Multithreading, I wanted to ask if this code works as expected, or if I might run into some kind of race condition or the like. I'm especially worried about the onResult () method, because I'm afraid that "setModel" might not be thread safe.

+2
1

, . .

Model/View , , . , .

onResult(), , setModel .

, setModel . QWidget , , . docs. onResult , ( ). , .,.

, , , . ( , , ), , . , . , QObject ( ) , .


QAbstractTableModel. :

screenshot

#include <QtWidgets>
#include <QtConcurrent>
#include <tuple>

class AsyncTableModel : public QAbstractTableModel{
    Q_OBJECT
    //type used to hold the model internal data in the variable m_rows
    using RowsList = QList<std::tuple<QString, QString, QString> >;
    //model data
    RowsList m_rows;
    QFutureWatcher<RowsList>* m_watcher;
public:
    explicit AsyncTableModel(QObject* parent= nullptr):QAbstractTableModel(parent){
        //start loading data in the thread pool as soon as the model is instantiated 
        m_watcher = new QFutureWatcher<RowsList>(this);
        connect(m_watcher, &QFutureWatcher<RowsList>::finished,
                this, &AsyncTableModel::updateData);
        QFuture<RowsList> future = QtConcurrent::run(&AsyncTableModel::retrieveData);
        m_watcher->setFuture(future);
    }
    ~AsyncTableModel() = default;

    //this is a heavy function that returns data you want the model to display
    //this is called in the thread pool using QtConcurrent::run
    static RowsList retrieveData(){
        //the function is heavy that it blocks the calling thread for 2 secs
        QThread::sleep(2);
        RowsList list;
        for(int i=0; i<10; i++){
            list.append(std::make_tuple(QString("A%0").arg(i),
                                        QString("B%0").arg(i),
                                        QString("C%0").arg(i)));
        }
        return list;
    }
    //this is the slot that is called when data is finished loading
    //it resets the model so that it displays new data
    Q_SLOT void updateData(){
        beginResetModel();
        m_rows = m_watcher->future().result();
        endResetModel();
    }

    int rowCount(const QModelIndex &parent) const {
        if(parent.isValid()) return 0;
        return m_rows.size(); 
    }
    int columnCount(const QModelIndex &parent) const {
        if(parent.isValid()) return 0;
        return 3; 
    }

    QVariant data(const QModelIndex &index, int role) const {
        QVariant value= QVariant();
        switch(role){
        case Qt::DisplayRole: case Qt::EditRole:
            switch(index.column()){
            case 0:
                value= std::get<0>(m_rows[index.row()]);
                break;
            case 1:
                value= std::get<1>(m_rows[index.row()]);
                break;
            case 2:
                value= std::get<2>(m_rows[index.row()]);
            }
            break;
        }
        return value;
    }
};

int main(int argc, char* argv[]){
    QApplication a(argc, argv);

    QTableView tv;
    AsyncTableModel model;
    tv.setModel(&model);
    tv.show();


    return a.exec();
}

#include "main.moc"

:

, , . , . - , API, QTcpSocket/QNetworkAccessManager, , .

+4

Source: https://habr.com/ru/post/1690066/


All Articles