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

Exposed model to QML does not Update



  • I use QSortFilterProxyModel to randomly select rows from the source model it is installed on.

    Then I expose the model to qml and call from there the method generateRandomQuestions. If I check mAcceptedRows it looks like there are indeed new valid indexes generated so the model should contain new data.

    However in QML the View does not update to the new data.

    the Model:

    #include <QSortFilterProxyModel>
    #include <QVector>
    
    class RandomQuestionFilterModel : public QSortFilterProxyModel
    {
        Q_OBJECT
    
        enum questionRoles {
            idRole = Qt::UserRole + 1,
            askedQuestionRole,
            answer1Role,
            answer2Role,
            answer3Role,
            answer4Role,
            correctAnswerRole,
            pictureRole
        };
    
    public:
        RandomQuestionFilterModel(QObject *parent = nullptr);
    
        QHash<int, QByteArray> roleNames() const override;
        QVariant data(const QModelIndex &index, int role) const override;
    
        Q_INVOKABLE void generateRandomQuestions(int count);
    protected:
        bool filterAcceptsRow(int source_row,
                              const QModelIndex &source_parent) const override;
    private:
        QVector<int> mAcceptedRows;
    };
    
    #include "../include/randomquestionfiltermodel.h"
    
    #include <algorithm>
    #include <random>
    
    #include <QDebug>
    
    RandomQuestionFilterModel::RandomQuestionFilterModel(QObject *parent)
        :QSortFilterProxyModel{parent}
    {
    }
    
    QHash<int, QByteArray> RandomQuestionFilterModel::roleNames() const
    {
        QHash <int,QByteArray> roles;
        roles[idRole] = "id";
        roles[askedQuestionRole] = "askedQuestion";
        roles[answer1Role] = "answer1";
        roles[answer2Role] = "answer2";
        roles[answer3Role] = "answer3";
        roles[answer4Role] = "answer4";
        roles[correctAnswerRole] = "correctAnswer";
        roles[pictureRole] = "picture";
    
        return roles;
    }
    
    QVariant RandomQuestionFilterModel::data(const QModelIndex &index, int role) const
    {
        switch(role) {
        case idRole:
            return index.sibling(index.row(), 0).data().toInt();
        case askedQuestionRole:
            return index.sibling(index.row(), 1).data().toString();
        case answer1Role:
            return index.sibling(index.row(), 2).data().toString();
        case answer2Role:
            return index.sibling(index.row(), 3).data().toString();
        case answer3Role:
            return index.sibling(index.row(), 4).data().toString();
        case answer4Role:
            return index.sibling(index.row(), 5).data().toString();
        case correctAnswerRole:
            return index.sibling(index.row(), 6).data().toInt();
        case pictureRole:
            return index.sibling(index.row(), 7).data().toByteArray().toBase64();
        }
        return QSortFilterProxyModel::data(index, role);
    }
    
    void RandomQuestionFilterModel::generateRandomQuestions(int count)
    {
        mAcceptedRows.resize(sourceModel()->rowCount());
        std::iota(std::begin(mAcceptedRows), std::end(mAcceptedRows), 0);
        std::shuffle(std::begin(mAcceptedRows), std::end(mAcceptedRows),
                     std::mt19937(std::random_device()()));
    
        mAcceptedRows.resize(count);
    
        auto start = createIndex(0,0);
        auto end = createIndex(rowCount(),columnCount());
        emit dataChanged(start, end);
    }
    
    bool RandomQuestionFilterModel::filterAcceptsRow(
            int source_row, const QModelIndex &source_parent) const
    {
        Q_UNUSED(source_parent)
        auto it = std::find(mAcceptedRows.begin(), mAcceptedRows.end(), source_row);
        return it != mAcceptedRows.end();
    }
    

    I thought emitting dataChanged in generateRandomQuestions would notify the view that the data changed but it does not.

    In QML I generate the random questions first and then open the page were the random questions are used.

    ToolButton {
                   id: newQuizButton
                   text: qsTr("New Quiz")
                   icon.name: "address-book-new"
                   onClicked: {
                       randomQuestionFilterModel.generateRandomQuestions(
                                   countOfQuestions)
                       loader.setSource(root.__newQuizPath)
                   }
               }
    

    On the page I pass the rows like this:

    SwipeView {
           id: quizSwipeView
           interactive: false
           anchors.fill: parent
    
           Repeater {
               id: quizPageRepeater
               model: randomQuestionFilterModel
               delegate: QuizPage {
                   questionId: model.id
                   askedQuestion: model.askedQuestion
                   answer1: model.answer1
                   answer2: model.answer2
                   answer3: model.answer3
                   answer4: model.answer4
                   correctAnswer: model.correctAnswer
                   picture: model.picture
                   onAnsweredCorrectly: quiz.answeredCorrectly()
                   onAnsweredWrong: quiz.answeredWrong()
               }
           }
       }
    

  • Lifetime Qt Champion

    Hi,

    I do not know for sure but in that method you are not merely changing data, the complet model content is changed. Sounds rather like a use case for beginResetModel and endResetModel.

    However it looks like a strange use of QSortFilterProxyModel which states usually between the model actually containing the data and the view.



  • @SGaist

    Your right that solved my Issue.

    I'm open for a better solution to get the questions.

    Currently I have the following:

    In C++:
    A questions model derrived from QSqlTableModel to store and fetch questions (with possible answers).

    The Filter model derrived from QSortFilterProxyModel to fetch n random questions from the questions model.

    These models get exposed to qml.

    In QML:

    Show all the questions in the database. -> I use the questions model for that.
    Add new questions to the database -> I use the questions model for that.

    Present random questions as a Quiz to the User. Here I use the filter model to filter random entries from the question model.

    If this is not a good approach I would love to here suggestions how else I could get the random questions.

    An even worst appraoch I used before was getting the questions directly from the question model by exposing them as

    QQmlListProperty <Question> getRandomQuestions(); member function.


  • Lifetime Qt Champion

    One way, you can use a QSqlQueryModel and make the randomization in the request itself.

    The proxy model is not a bad idea, you just need to separate it from the data themselves.



  • @SGaist What do you mean separate it from the data? The SQL Access is only done in the table model the proxy model filters.


  • Lifetime Qt Champion

    It was just a general statement. It happens from time to time that people try to use the proxy model as a standard model. This is indeed not your case :-)

    Sorry for the confusion !


Log in to reply