Search code examples
c++qtqmlqt5.7

Adding and removing items from a C++ list in QML/QT 5.7


I'm working on a simple project to try and learn QT 5.7, QML, and C++. I want to create a simple interface that has a list of items that I can add and remove items from using a couple of buttons. I've been reading a bunch of different guides online trying to piece together something but I keep getting stuck. I've tried using QQmlListProperty<T> and QAbstractListModel but I have questions about both approaches:

  1. For my project is QQmlListProperty<T> the right thing to use or I should use QAbstractListModel?
  2. For either case how do I notify my QML view that the list has changed?
  3. If I use 'QAbstractListModel', do I just need to create Q_INVOKABLE methods for adding and removing items from my list?

Below is my code for both QQmlListProperty<T> and QAbstractListModel so far. I've left out most of the implementation of the classes to keep this post short but if the implementation is needed I'm happy to add it.

QQmlListProperty Item Class

class PlaylistItemModel : public QObject
{
        Q_OBJECT
        Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
    public:
        explicit PlaylistItemModel(QObject *parent = 0);
        QString getName();
        void setName(const QString &name);

    signals:
        void nameChanged();

    public slots:

    private:
        QString _name;
};

QQmlListProperty List Class

class PlaylistModel : public QObject
{
        Q_OBJECT
        Q_PROPERTY(QQmlListProperty<PlaylistItemModel> items READ getItems NOTIFY itemsChanged)
    public:
        explicit PlaylistModel(QObject *parent = 0);
        QQmlListProperty<PlaylistItemModel> getItems() const;
        Q_INVOKABLE void addItem(PlaylistItemModel *item);
        Q_INVOKABLE void removeItem(PlaylistItemModel *item);
        Q_INVOKABLE void clearItems();

        static void append(QQmlListProperty<PlaylistItemModel> *list, PlaylistItemModel *item);
        static PlaylistItemModel* at(QQmlListProperty<PlaylistItemModel> *list, int index);
        static int count(QQmlListProperty<PlaylistItemModel> *list);
        static void clear(QQmlListProperty<PlaylistItemModel> *list);

    signals:
        void itemsChanged();

    public slots:

    private:
        QList<PlaylistItemModel*> _items;
};

Implementation of getItms():

QQmlListProperty<PlaylistItemModel> PlaylistModel::getItems() const
{
    return QQmlListProperty<PlaylistItemModel>(this, _items, &append, &count, &at, &clear);
}

QAbstractListModel

class MyModel : public QAbstractListModel
{
    Q_OBJECT
    public:
        enum ModelRoles
        {
            ItemRole = Qt::UserRole + 1
        };

        MyModel(QObject *parent = 0);

        // QAbstractItemModel interface
        int rowCount(const QModelIndex &parent) const;
        QVariant data(const QModelIndex &index, int role) const;
        QHash<int, QByteArray> roleNames() const;

    private:
        QList<QString> _listData;
        QString itemAt(const QModelIndex &index) const;
};

Solution

  • I would generally suggest that QAbstractListModel is the right class to go with most of the time, unless you are sure you'll only be working with a simple list.

    For either case how do I notify my QML view that the list has changed?

    QAbstractItemModel (which QAbstractListModel inherits) has a number of different methods that you should call from your subclass to inform the view(s) connected to it that something has happened. When you're inserting items to it, you want QAbstractItemModel::beginInsertRows and QAbstractItemModel::endInsertRows.

    If your model is representing something simple, like a list of names for instance, your insertion might look something like this:

    Assuming:

    class MyModel : public QAbstractListModel
    {
    public:
        Q_INVOKABLE void addPerson(const QString &name);
    private:
        QVector<QString> m_names;
    };
    
    void MyModel::addPerson(const QString &name)
    {
        beginInsertRows(QModelIndex(), m_names.count(), m_names.count());
        m_names.append(name);
        endInsertRows();
    }
    

    You then need to implement QAbstractItemModel::rowCount, QAbstractItemModel::roleNames, and QAbstractItemModel::data at a minimum, but it looks like you've already got that handled. If you want to implement editing of data, you also want to implement QAbstractListModel::setData.

    Once you've done that, register the type (using qmlRegisterType):

    qmlRegisterType<MyModel>("MyModelImport", 1, 0, "MyModel");
    

    And then import & instantiate it from QML, and use it on your view:

    import MyModelImport 1.0
    import QtQuick 2.6
    
    ListView {
        id: listView
        anchors.fill: parent
        model: MyModel {
        }
        delegate: TextInput {
            text: model.text
            onEditingFinished: {
                // when the text is edited, save it back to the model (which will invoke the saveData we overloaded)
                model.text = text
            }
        }
    
        Rectangle {
            height: 100
            width: 400
            color: "red"
            Text {
                text: "Add item"
            }
            MouseArea {
                anchors.fill: parent
                onClicked: listView.model.addName("Some new name")
            }
        }
    }