Assign data model elements to different pages of a SwipeView
-
Hello,
I have struggles to find a clean solution for my problem:
There is a datamodel (QAbstractListModel) that holds data of different categories.
Not I want to have a SwipeView to display all data that belongs to one category on a page 1 and the data for the next category on page 2 and so on.I created a simple application which showcases how it should be. But it comes with some flaws:
- The complete data of the model is fetched for each page of the SwipeView because on each page there is the full ListView and the irrelevant items are just hidden
- It seems a bit hacky to set the visibility and the height of the items based on the page index
Is there a better way to achieve this kind of use case?
I thought of using a TableModel instead of a ListModel in the backend and to display each column on a dedicated page of the SwipeView but could not get this running.Main.qml
import QtQuick 2.15 import QtQuick.Controls 2.15 import ColorListModel Window { width: 200 height: 200 visible: true ColorListModel { id: colorListModel } SwipeView { id: swipe anchors.fill: parent focus: true Repeater { model: 4 delegate: Item { id: delegateItem property int pageIndex: index Column { spacing: 5 ListView { id: lv model: colorListModel height: 150 width: 200 highlightFollowsCurrentItem: false delegate: Rectangle { visible: pageIndex === model.page height: visible ? 50 : 0 // hacky solution to glue the list to the top width: parent.width color: model.color border {color: "grey"; width: 3} Text { anchors.centerIn: parent text: "page: " + model.page + " index: " + index font.pixelSize: 20 } } } Text { id: bottomLabel anchors.horizontalCenter: parent.horizontalCenter text: "page: " + delegateItem.pageIndex font.pixelSize: 15 } } } } } PageIndicator { count: swipe.count currentIndex: swipe.currentIndex anchors.bottom: swipe.bottom anchors.horizontalCenter: parent.horizontalCenter } }
colorlistmodel.h
#ifndef COLORLISTMODEL_H #define COLORLISTMODEL_H #include <QAbstractListModel> #include <QString> #include <QList> class ColorListModel : public QAbstractListModel { Q_OBJECT public: static constexpr const int DATA_PER_PAGE = 3; ColorListModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QHash<int, QByteArray> roleNames() const override; enum Roles { PageRole = Qt::UserRole, ColorRole, }; struct DataElement { int page; QString color; }; int column() const; void setColumn(int newColumn); signals: void columnChanged(); private: QList<DataElement> m_data; }; #endif // COLORLISTMODEL_H
colorlistmodel.cpp
#include "colorlistmodel.h" #include <QString> #include <QList> ColorListModel::ColorListModel(QObject *parent) : QAbstractListModel(parent), m_data{{0, "red"}, {0, "green"}, {0, "blue"}, {1, "purple"}, {1, "lightgreen"}, {2, "brown"}, {2, "yellow"}, {2, "steelblue"}, {3, "orange"}, {3, "darkblue"}, {3, "darkgreen"}} { } int ColorListModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_data.size(); } QVariant ColorListModel::data(const QModelIndex &index, int role) const { //qInfo() << "data called for" << index.row(); switch (role) { case PageRole: return m_data[index.row()].page; break; case ColorRole: return m_data[index.row()].color; break; default: return QVariant(); break; } return QVariant(); } QHash<int, QByteArray> ColorListModel::roleNames() const { static const QHash<int, QByteArray> mapping { {ColorRole, "color"}, {PageRole, "page"}, }; return mapping; }
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "colorlistmodel.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); qmlRegisterType<ColorListModel>("ColorListModel", 1, 0, "ColorListModel"); QQmlApplicationEngine engine; QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.loadFromModule("ListModelExperimental", "Main"); return app.exec(); }
-
Hello,
I have struggles to find a clean solution for my problem:
There is a datamodel (QAbstractListModel) that holds data of different categories.
Not I want to have a SwipeView to display all data that belongs to one category on a page 1 and the data for the next category on page 2 and so on.I created a simple application which showcases how it should be. But it comes with some flaws:
- The complete data of the model is fetched for each page of the SwipeView because on each page there is the full ListView and the irrelevant items are just hidden
- It seems a bit hacky to set the visibility and the height of the items based on the page index
Is there a better way to achieve this kind of use case?
I thought of using a TableModel instead of a ListModel in the backend and to display each column on a dedicated page of the SwipeView but could not get this running.Main.qml
import QtQuick 2.15 import QtQuick.Controls 2.15 import ColorListModel Window { width: 200 height: 200 visible: true ColorListModel { id: colorListModel } SwipeView { id: swipe anchors.fill: parent focus: true Repeater { model: 4 delegate: Item { id: delegateItem property int pageIndex: index Column { spacing: 5 ListView { id: lv model: colorListModel height: 150 width: 200 highlightFollowsCurrentItem: false delegate: Rectangle { visible: pageIndex === model.page height: visible ? 50 : 0 // hacky solution to glue the list to the top width: parent.width color: model.color border {color: "grey"; width: 3} Text { anchors.centerIn: parent text: "page: " + model.page + " index: " + index font.pixelSize: 20 } } } Text { id: bottomLabel anchors.horizontalCenter: parent.horizontalCenter text: "page: " + delegateItem.pageIndex font.pixelSize: 15 } } } } } PageIndicator { count: swipe.count currentIndex: swipe.currentIndex anchors.bottom: swipe.bottom anchors.horizontalCenter: parent.horizontalCenter } }
colorlistmodel.h
#ifndef COLORLISTMODEL_H #define COLORLISTMODEL_H #include <QAbstractListModel> #include <QString> #include <QList> class ColorListModel : public QAbstractListModel { Q_OBJECT public: static constexpr const int DATA_PER_PAGE = 3; ColorListModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QHash<int, QByteArray> roleNames() const override; enum Roles { PageRole = Qt::UserRole, ColorRole, }; struct DataElement { int page; QString color; }; int column() const; void setColumn(int newColumn); signals: void columnChanged(); private: QList<DataElement> m_data; }; #endif // COLORLISTMODEL_H
colorlistmodel.cpp
#include "colorlistmodel.h" #include <QString> #include <QList> ColorListModel::ColorListModel(QObject *parent) : QAbstractListModel(parent), m_data{{0, "red"}, {0, "green"}, {0, "blue"}, {1, "purple"}, {1, "lightgreen"}, {2, "brown"}, {2, "yellow"}, {2, "steelblue"}, {3, "orange"}, {3, "darkblue"}, {3, "darkgreen"}} { } int ColorListModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_data.size(); } QVariant ColorListModel::data(const QModelIndex &index, int role) const { //qInfo() << "data called for" << index.row(); switch (role) { case PageRole: return m_data[index.row()].page; break; case ColorRole: return m_data[index.row()].color; break; default: return QVariant(); break; } return QVariant(); } QHash<int, QByteArray> ColorListModel::roleNames() const { static const QHash<int, QByteArray> mapping { {ColorRole, "color"}, {PageRole, "page"}, }; return mapping; }
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "colorlistmodel.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); qmlRegisterType<ColorListModel>("ColorListModel", 1, 0, "ColorListModel"); QQmlApplicationEngine engine; QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.loadFromModule("ListModelExperimental", "Main"); return app.exec(); }
@R_Johannes said in Assign data model elements to different pages of a SwipeView:
Hello,
I have struggles to find a clean solution for my problem:
There is a datamodel (QAbstractListModel) that holds data of different categories.
Not I want to have a SwipeView to display all data that belongs to one category on a page 1 and the data for the next category on page 2 and so on.I created a simple application which showcases how it should be. But it comes with some flaws:
- The complete data of the model is fetched for each page of the SwipeView because on each page there is the full ListView and the irrelevant items are just hidden
- It seems a bit hacky to set the visibility and the height of the items based on the page index
Is there a better way to achieve this kind of use case?
Create a proxy model for each view. I didn't read the the code (TLDR; There's no need to have multiple C++ files and header files for such an example), but if you have something to differentiate in QML, presumably the same test could be performed in C++.
-
Thanks for your reply and the hint with the proxy model.
A good working solution was now implemented using the following design:- One AbstractListModel holding all the data
- A SortFilterProxyModel to filter the data based on the page number is instanciated for each page, using the Repeater + Loader inside of the SwipeModel
- The source component of the Loader holds a ListView that is using the ProxyModel as a model
-