QAbstractListModel: обновить имена ролей

Я пытаюсь создать модель списка на основе API асинхронной базы данных. Вот пример qml того, как я хочу его использовать:

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

очевидно, в какой-то момент мне понадобится больше, чем идентификатор в моей базе данных, и больше, чем метка для отображения этого элемента.

Чтобы иметь возможность использовать «id» в определении моей метки, я использую такие имена ролей:

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 — это сеанс базы данных для «toto.sqlite» в этом случае, а m_query — «SELECT id FROM test».

Дело в том, что моя сессия с базой данных асинхронна и m_database->currentRequestFields() недоступна сразу, однако я получаю сигнал, говорящий мне, когда это так, поэтому я хотел бы обновить список roleNames в этот момент, а не раньше.

Даже если m_database, вероятно, будет выглядеть как черный ящик, вот что я делаю для обновления модели:

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();
    }
}

person Nyashes    schedule 04.12.2015    source источник
comment
Имена ролей должны быть обновлены до endResetModel(). Я не уверен, что это будет работать надежно, в прошлый раз, когда я отлаживал это, представления QML не могли справиться с изменением имен ролей, даже если они изменились только во время сброса модели.   -  person Frank Osterfeld    schedule 06.12.2015
comment
Кроме того, я не вижу смысла в динамическом создании имен ролей. Как вы планируете обновлять делегат QML для изменения имен ролей? Не могли бы вы использовать роли от 001 до 999 и поместить изменяющиеся данные столбца SQL в эти фиксированные имена ролей?   -  person Simon Warta    schedule 06.12.2015
comment
На самом деле имена ролей не обновляются, их инициализация задерживается, но выполняется только один раз. Мне удалось заставить вещи работать, вызвав beginResetModel ПОСЛЕ того, как currentRequestFields() станет доступным. Как только я смогу удалить что-то вроде предыдущего предложения, я опубликую рабочий код.   -  person Nyashes    schedule 07.12.2015


Ответы (2)


Одна идея, которая может соответствовать вашим потребностям (предполагая, что вам нужен хороший API для пользователей этой модели и иметь только запросы SELECT), состоит в том, чтобы структурировать ваш запрос следующим образом:

  • Строковая таблица
  • Строка [] столбцы
  • Выбор строки
  • Строка [] selectionArgs
  • Строковая группаПо
  • Строка, имеющая
  • Строка orderBy
  • Ограничение строки

(Идея украдена из http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#query%28java.lang.String,%20java.lang.String[],%20java.lang.String,%20java.lang.String[],%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String%29)

Итак, ваш простой пример может выглядеть так

DatabaseModel {
    id: dmodel

    table: "test"
    colums: ["id"]
    database: "toto.sqlite"
}

Таким образом, имена столбцов будут доступны достаточно рано, чтобы использовать их для имен ролей.

person Simon Warta    schedule 06.12.2015

Хорошо, наконец-то я получил то поведение, которое хотел, которое позволяет отложить первую инициализацию имен ролей моей itemModel. Код по существу тот же, но с некоторым изменением порядка. В частности, имена ролей ДОЛЖНЫ быть доступны ДО вызова beginResetModel. вы можете сравнить этот фрагмент с фрагментом в моем вопросе

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();
    }
}
person Nyashes    schedule 08.12.2015