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:
QQmlListProperty<T>
the right thing to use or I should use QAbstractListModel
?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;
};
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")
}
}
}