Search code examples
c++qtmodel-view-controllerqmlqabstractitemmodel

QAbstractListModel: update role names


I'm trying to create a List model based on an asynchronous database api. Here is a qml example of how I want to be able to use it:

ListView {
    id: view;

    anchors.fill: parent;

    model: DatabaseModel {
        id: dmodel

        query: "SELECT id FROM test"
        database: "toto.sqlite"
    }

    delegate: Label {
        anchors.horizontalCenter: parent.horizontalCenter;
        width: view.width / 2;
        height: 30;
        text: id;

        color: "teal";
    }
}

obviously at some point I'll need more than an id in my database and more than a Label to display this item.

To be able to use "id" in the definition of my Label I use role names like that :

QHash<int, QByteArray> DatabaseListModel::roleNames() const
{
    QHash<int, QByteArray> b = this->QAbstractItemModel::roleNames();

    if (m_query != "" && m_database) {
        QStringList l = m_database->currentRequestFields();
        for (int i = 0; i < l.count(); ++i) {
            b.insert(Qt::UserRole + i + 1, l.at(i).toLocal8Bit());
        }
    }
    return b;
}

m_database being a database session to "toto.sqlite" in this case and m_query being "SELECT id FROM test".

The thing is that my database session is asynchronous and m_database->currentRequestFields() is not available immediately, However I receive a signal telling me when it is so I would like to update the roleNames list at this moment and not before.

Even if m_database will probably look like a blackbox, here is what I do to update the model:

void DatabaseListModel::updateModel()
{
    if (m_query != "" && m_database) {
        m_mutex.lock();
        beginResetModel();
        m_cache.clear();

        QObject::connect(m_database, &CollaoDatabase::databaseReady, this, [this] (CollaoDatabase* database) {
            database->setQueryStringi(m_query);
            database->executei(); //currentRequestFields() becomes available 
            database->fetchAlli();
            database->sendNotifierEventi(0); //when everything written before this line has been executed, ask the database to emit CollaoDatabase::notifierEventProcessed. It's not instant and might take a while depending on the query  
        });
        QObject::connect(m_database, &CollaoDatabase::resultReady, this, [this] (QVariantMap result) {
            if (m_cache.size() <= 0)
                m_cache.reserve(m_database->currentPendingFetches() + 1);
            m_cache.append(result.values());
        });

        QObject::connect(m_database, (void (CollaoDatabase::*)())&CollaoDatabase::notifierEventProcessed, this, [this](){
            endResetModel();
            //TODO: update roleNames here

            m_mutex.unlock();
            m_database = NULL; //as soon as stop() is called, we cannot assume the existance of this object anymore
            //it is therefore safer to make it null now
        });
        QObject::connect(m_database, SIGNAL(notifierEventProcessed()), m_database, SLOT(stop()));

        m_database->start();
    }
}

Solution

  • Okay I finally got the behavior I wanted, which is being able to delay the first initialization of my itemModel's role names. The code is essentially the same with some reordering. Especially role names MUST be available BEFORE the call to beginResetModel. you can compare this snippet to the one in my question

    void DatabaseListModel::updateModel()
    {
        if (m_query != "" && m_database) {
            m_mutex.lock();
    
            QObject::connect(m_database, &CollaoDatabase::databaseReady, this, [this] (CollaoDatabase* database) {
                database->setQueryStringi(m_query);
                database->executei();
                database->sendNotifierEventi(1);
                database->fetchAlli();
                database->sendNotifierEventi(0);
            });
            QObject::connect(m_database, &CollaoDatabase::resultReady, this, [this] (QVariantMap result) {
                if (m_cache.size() <= 0) {
                    m_fields = result.keys();
                    beginResetModel();
                    m_cache.reserve(m_database->currentPendingFetches() + 1);
                    m_numRows = m_database->currentPendingFetches() + 1;
                    emit numRowsChanged();
                    m_progress = 0;
                }
    
                m_cache.append(result.values());
                ++m_progress;
                if (m_progress % (m_numRows / 100 + 1) == 0)
                    emit progressChanged();
            });
    
            QObject::connect(m_database, (void (CollaoDatabase::*)(int))&CollaoDatabase::notifierEventProcessed, this, [this](int eventIndex){
                switch (eventIndex) {
                case 0: /*terminate*/
                    emit progressChanged();
                    endResetModel();
                    m_mutex.unlock();
                    m_database->stop();
                    m_database = NULL; //as soon as stop() is called, we cannot assume the existance of this object anymore
                    //it is therefore safer to make it null now
                    break;
    
                case 1: /*now able to reset the model*/
                    m_cache.clear();
                    break;
                }
            });
    
            m_database->start();
        }
    }