Creating a Read‑Only Qt Model with QAbstractItemModel for QListView
Step‑by‑step guide to subclass QAbstractItemModel for a read‑only string list and attach it to a QListView, with verification and troubleshooting tips.
25 Mar 2026, 18:42 UTC

Desired outcome
\nYou want to expose a simple data source (e.g., a list of strings) to a Qt view such as QListView without allowing the view to modify the data. The goal is to subclass QAbstractItemModel so that the view displays each item correctly and stays in sync if the underlying data changes.
Prerequisites
\n- \n
- Qt 5.12 or later installed (the guide uses the public API that is stable across recent versions). \n
- Basic C++ knowledge and a Qt project set up (qmake or CMake). \n
- A data container you wish to expose; for the example we use
QStringList. \n
Implementation steps
\n- \n
- \nDeclare the model class\n
Create a header file, e.g.,
\nStringListModel.h, and inherit fromQAbstractItemModel. Since the model is read‑only and flat (no hierarchy), you only need to implement a few pure virtual functions.
\n#include <QAbstractItemModel>\n#include <QStringList>\n\nclass StringListModel : public QAbstractItemModel\n{\n Q_OBJECT\npublic:\n explicit StringListModel(const QStringList &data, QObject *parent = nullptr);\n ~StringListModel() override = default;\n\n // QAbstractItemModel interface\n int rowCount(const QModelIndex &parent = {}) const override;\n int columnCount(const QModelIndex &parent = {}) const override;\n QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override;\n QModelIndex parent(const QModelIndex &index) const override;\n QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;\n QHash roleNames() const override;\n\nprivate:\n QStringList m_data;\n};\n\n - \nImplement the constructor and basic counts\n
Store the data source and return the appropriate sizes. For a flat list,
\ncolumnCountis 1.
\nStringListModel::StringListModel(const QStringList &data, QObject *parent)\n : QAbstractItemModel(parent), m_data(data)\n{}\n\nint StringListModel::rowCount(const QModelIndex &parent) const\n{\n return parent.isValid() ? 0 : m_data.size();\n}\n\nint StringListModel::columnCount(const QModelIndex &parent) const\n{\n return parent.isValid() ? 0 : 1;\n}\n\n - \nCreate valid model indexes\n
Use
\nQModelIndex::createIndexwith the row and column. Because the model has no hierarchy, the internal pointer can benullptr.
\nQModelIndex StringListModel::index(int row, int column, const QModelIndex &parent) const\n{\n if (!hasIndex(row, column, parent))\n return {};\n return createIndex(row, column, nullptr);\n}\n\nQModelIndex StringListModel::parent(const QModelIndex &index) const\n{\n // No parent for top‑level items in a flat model\n return {};\n}\n\n - \nProvide data for the requested role\n
For a read‑only model we only need to handle
\nQt::DisplayRole(and optionallyQt::EditRoleif you want the same string). Return an invalidQVariantfor unsupported roles.
\nQVariant StringListModel::data(const QModelIndex &index, int role) const\n{\n if (!index.isValid())\n return {};\n if (role == Qt::DisplayRole || role == Qt::EditRole)\n return m_data.at(index.row());\n return {};\n}\n\nQHash StringListModel::roleNames() const\n{\n QHash roles;\n roles[Qt::DisplayRole] = \"display\";\n return roles;\n}\n\n - \nInstantiate and connect the model to a view\n
In your UI code (e.g., a
\nQMainWindowconstructor), create the model, populate it, and set it on aQListView.
\n#include <QListView>\n#include \"StringListModel.h\"\n\nMainWindow::MainWindow(QWidget *parent)\n : QMainWindow(parent)\n{\n QStringList list = {\"Alpha\", \"Beta\", \"Gamma\"};\n auto *model = new StringListModel(list, this);\n auto *view = new QListView(this);\n view->setModel(model);\n setCentralWidget(view);\n}\n\n
Expected checks
\n- \n
- Run the application; the
QListViewshould show three rows with the strings \"Alpha\", \"Beta\", \"Gamma\". \n - Use
qDebug()insidedata()to confirm it is called for each visible row when the view scrolls or resizes. \n - Run the program under
valgrindorAddressSanitizerto verify no invalid memory accesses arise from malformed indexes. \n - If you later modify
m_data(e.g., add an item), emitbeginInsertRows()/endInsertRows()around the change; the view should update automatically. \n
Troubleshooting and recovery options
\n- \n
- View shows nothing: Verify that
rowCount()returns a non‑zero value for an invalid parent and thatindex()returns a validQModelIndexfor the requested row/column. \n - Application crashes: Check that you never return an invalid index from
index()whenhasIndex(row, column, parent)is true, and thatparent()always returns an invalid index for this flat model. \n - Stale data after modification: Ensure you surround data changes with the appropriate begin/end signals; missing signals cause the view to retain the old state. \n
- Thread‑related glitches: If you update the model from a worker thread, post the change to the GUI thread using
QMetaObject::invokeMethodwithQt::QueuedConnectionor useQtConcurrentto run the update and then emit the signals from the GUI thread. \n
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.