Best way to implement a list of option
-
Hello,
I would like to implement a list of option on a page. That page has a defined size and the amount of option is to big to fit the page.
So I wanted to make a Scrollview so I can make a list of those options. Those options are different types of qml object or component.But I'm not sure it is the best way to do it and it seems a bit tricky to make it work properly.
For you to know I'm stuck with QuickControls 1.4 can't move to QuickControls 2.x because my target is not compatible.
Here is an example of what I want :
Thank you for your help !
-
Keep your data in a model (subclass of QAbstractItemModel), use a ListView to display your data. In your delegate, use (for example) a Loader to load proper control per data type.
-
@sierdzio
Thank you for your reply,Do you have an exemple of this kind of implementation ? Because I don't really see how to handle multiple types of component.
(The option is usualy a text then some kind of component to get the associated value (button, combobox, switch, textInput...) -
@DavidM29 I tried to make something.
It works but I'm not sure this is the best way to do it according to your needs. I'm using QVariant to store property values as it is already compatible with Model-View system.
Model:
.h#ifndef GENERICPROPERTYLISTMODEL_H #define GENERICPROPERTYLISTMODEL_H #include <QAbstractListModel> struct GenericProperty{ GenericProperty(const QString &name = QString(), const QVariant &value = QVariant(), const QVariant &extra = QVariant()){ this->name = name; this->value = value; this->extra = extra; } QString name; QVariant value; QVariant extra; }; typedef QList<GenericProperty> GenericPropertyList; class GenericPropertyListModel : public QAbstractListModel { Q_OBJECT public: enum _roles{ ROLE_NAME = Qt::UserRole + 1, ROLE_VALUE, ROLE_TYPE, ROLE_EXTRA, }; explicit GenericPropertyListModel(QObject *parent = nullptr); // Basic functionality: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual QHash<int,QByteArray> roleNames() const Q_DECL_OVERRIDE; // Editable: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; GenericPropertyList genericPropertyList() const; void setGenericPropertyList(const GenericPropertyList &list); private: GenericPropertyList m_list; }; #endif // GENERICPROPERTYLISTMODEL_H
.cpp
#include "genericpropertylistmodel.h" GenericPropertyListModel::GenericPropertyListModel(QObject *parent) : QAbstractListModel(parent) { } int GenericPropertyListModel::rowCount(const QModelIndex &parent) const { // For list models only the root node (an invalid parent) should return the list's size. For all // other (valid) parents, rowCount() should return 0 so that it does not become a tree model. if (parent.isValid()) return 0; return m_list.count(); } QVariant GenericPropertyListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); switch(role){ case ROLE_NAME: return m_list.at(index.row()).name; case Qt::DisplayRole: case Qt::EditRole: case ROLE_VALUE: return m_list.at(index.row()).value; case ROLE_TYPE: return m_list.at(index.row()).value.typeName(); case ROLE_EXTRA: return m_list.at(index.row()).extra; } // FIXME: Implement me! return QVariant(); } QHash<int, QByteArray> GenericPropertyListModel::roleNames() const { QHash<int, QByteArray> roles; roles[ROLE_NAME] = "role_name"; roles[ROLE_VALUE] = "role_value"; roles[ROLE_TYPE] = "role_type"; roles[ROLE_EXTRA] = "role_extra"; return roles; } bool GenericPropertyListModel::setData(const QModelIndex &index, const QVariant &value, int role) { if(role == ROLE_TYPE || role == ROLE_NAME) return false; if (data(index, role) != value) { // FIXME: Implement me! GenericProperty gp = m_list.value(index.row()); if(role == ROLE_VALUE) gp.value = value; else if(role == ROLE_EXTRA) gp.extra = value; m_list.replace(index.row(), gp); emit dataChanged(index, index, QVector<int>() << role); return true; } return false; } Qt::ItemFlags GenericPropertyListModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsEditable; // FIXME: Implement me! } GenericPropertyList GenericPropertyListModel::genericPropertyList() const { return m_list; } void GenericPropertyListModel::setGenericPropertyList(const GenericPropertyList &list) { m_list = list; }
main.cpp:
#include "genericpropertylistmodel.h" #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); GenericPropertyListModel model; GenericPropertyList list; list << GenericProperty("String", QString("A string value")); list << GenericProperty("Bool", true); list << GenericProperty("Int", static_cast<quint32>(1234)); list << GenericProperty("Double", static_cast<double>(1234)); list << GenericProperty("List", QVariantList() << "One" << "Two" << "Three"); model.setGenericPropertyList(list); QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("propertyListModel", &model); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); }
Usage from QML:
import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 ApplicationWindow { visible: true width: 640 height: 480 title: qsTr("Hello World") ListView{ anchors.fill: parent model: propertyListModel delegate: RowLayout { spacing: 20 height: 50 width: parent.width Text{ Layout.preferredWidth: 80 text: role_name + "(" + (typeof role_value) + ") " + role_type } Loader{ property var _name : role_name property var _value : role_value property var _extra : role_extra on_ValueChanged: role_value = _value on_ExtraChanged: role_extra = _extra Layout.minimumWidth: 120 Layout.fillHeight: true sourceComponent: { console.log(role_name + " - " + typeof role_value) if(typeof role_value == "boolean"){ return boolComponent } else if(typeof role_value == "string"){ return stringComponent } else if(role_value instanceof Array){ return listComponent } else if(typeof role_value == "number"){ if(role_type === "double") return realComponent else return intComponent } else { return undefined } } } } } Component{ id: boolComponent Switch{ text: _name checked: _value onCheckedChanged: _value = checked } } Component{ id: realComponent DoubleSpinBox{ realFrom: 0 realTo: 10000 realValue: _value onRealValueChanged: if(_value !== realValue) _value = realValue } } Component{ id: intComponent SpinBox{ from: 0 to: 10000 value: _value onValueChanged: if(_value !== value) _value = value } } Component{ id: stringComponent TextEdit{ text: _value onTextChanged: _value = text } } Component{ id: listComponent ComboBox{ model: _value currentIndex: _extra onCurrentIndexChanged: _extra = currentIndex } } }
Note that I have added an "extra" property to be able to store the selected index with the list type. Note also I didn't handle the "button" type.
Another solution could be to have a list of QObject derived classes with some properties accessible from QML and then access these objects from QML.