Solved 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 checkmAcceptedRows
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() } } }
-
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.
-
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 fromQSqlTableModel
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. -
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.
-
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 !