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

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 :

    0_1552561597280_20190314_1158442.jpg

    Thank you for your help !


  • Moderators

    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.



  • @Gojir4
    Thank you,
    I'm looking at your solution !
    I'll tell you if I do have any problem


Log in to reply