Implementing a Dynamic QAbstractListModel in Qt 6 with Proper Change Notifications
Learn how to create a dynamic QAbstractListModel in Qt 6, properly notify views of insertions and deletions, and avoid common pitfalls.
15 Aug 2025, 12:57 UTC

Useful answer
To show a list that updates automatically when items are added or removed, subclass QAbstractListModel, store the data in a QVector<QString>, and wrap every structural change with the begin/end row methods. This guarantees that attached views (QListView, QML ListView, etc.) receive the correct signals and stay in sync.
Worked configuration
Header (mylistmodel.h)
#ifndef MYLISTMODEL_H
#define MYLISTMODEL_H
#include
#include
class MyListModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit MyListModel(QObject *parent = nullptr);
// QAbstractItemModel interface
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash roleNames() const override;
public slots:
void appendItem(const QString &text);
void removeItem(int row);
private:
QVector m_items;
};
#endif // MYLISTMODEL_H
Implementation (mylistmodel.cpp)
#include "mylistmodel.h"
MyListModel::MyListModel(QObject *parent)
: QAbstractListModel(parent)
{
// optional initial data
m_items = {"Alpha", "Beta", "Gamma", "Delta", "Epsilon"};
}
int MyListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_items.size();
}
QVariant MyListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
return QVariant();
if (role == Qt::DisplayRole)
// Qt 6: returning QStringView avoids a copy when the backing vector owns the string
return QStringView{m_items[index.row()]};
return QVariant();
}
QHash MyListModel::roleNames() const
{
QHash roles;
roles[Qt::DisplayRole] = "display";
return roles;
}
void MyListModel::appendItem(const QString &text)
{
const int row = m_items.size();
beginInsertRows(QModelIndex(), row, row);
m_items.append(text);
endInsertRows();
}
void MyListModel::removeItem(int row)
{
if (row < 0 || row >= m_items.size())
return;
beginRemoveRows(QModelIndex(), row, row);
m_items.removeAt(row);
endRemoveRows();
}
Using the model in a Qt Widgets application
#include
#include
#include
#include
#include
#include "mylistmodel.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QVBoxLayout *layout = new QVBoxLayout(&w);
MyListModel *model = new MyListModel(&w);
QListView *view = new QListView(&w);
view->setModel(model);
layout->addWidget(view);
QPushButton *btn = new QPushButton("Add Item", &w);
layout->addWidget(btn);
QObject::connect(btn, &QPushButton::clicked, [model](){
static int counter = 0;
model->appendItem(QString("Item %1").arg(++counter));
});
w.resize(300, 200);
w.show();
return a.exec();
}
Using the model in QML
#include
#include
#include "mylistmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType("MyModels", 1, 0, "MyListModel");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
// main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import MyModels 1.0
ApplicationWindow {
visible: true
width: 400
height: 300
title: "QAbstractListModel Demo"
MyListModel {
id: myModel
}
ListView {
anchors.fill: parent
model: myModel
delegate: Text { text: display; padding: 8 }
}
Button {
text: "Add"
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
myModel.appendItem("Item " + (myModel.count + 1))
}
}
}
Limits and common mistakes
Performance limits
QAbstractListModel works well for up to roughly 100 k rows when each row has uniform height. Beyond that, consider lazy loading with canFetchMore/fetchMore or a custom QAbstractItemModel that supplies only visible items.
Thread safety
The model must live in the GUI thread. If background work produces new data, emit a queued signal or use QMetaObject::invokeMethod to call appendItem (or similar) on the main thread.
Typical errors
- Modifying the underlying
QVectorwithout callingbeginInsertRows/endInsertRows(or the remove/move equivalents). The view will show stale data, may crash when scrolling, or produce visual artifacts. - Omitting
roleNames()when using custom roles in QML. Delegates bound to those roles will receive empty values. - Using
layoutChanged()for simple insert/remove operations. This forces the view to rebuild everything, hurting performance and losing scroll position. - Storing raw
QObject*pointers in the model without ownership tracking. When the view releases an item, the pointer can dangle; useQPointerorQSharedPointerinstead.
Practical verification
Run the widget example, click the "Add Item" button, and watch the list grow instantly. Then, temporarily comment out the beginInsertRows/endInsertRows lines in appendItem and rebuild. You will see the view not update (or crash on scroll), confirming that the signals are required.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.