Building a Custom Qt Model When QStringListModel Isn't Enough
QStringListModel stores exactly what it shows. When your display format diverges from your data—like showing file names for full paths—you need a custom QAbstractItemModel. Here is how to implement the required functions and keep views synchronized.
12 Nov 2025, 18:52 UTC

You have a QStringList of absolute file paths and a QListView that should display only the file names. QStringListModel cannot do this: it stores exactly what it shows. Subclassing QAbstractItemModel lets you keep the full path as the source of truth while presenting a derived value to the view.
The model/view split in Qt exists so that data lives in one place and presentation lives in another. When your display logic diverges from your storage—formatting, filtering, or combining fields—a custom model is the idiomatic fix.
The five functions that define a flat model
QAbstractItemModel is abstract. For a simple, single-column list you must implement five virtual functions:
rowCount(): returns the number of items. Return0for any valid parent index so the model stays flat.columnCount(): returns1for a list. Tables and trees return more.index(): converts a row and column into aQModelIndex. UsehasIndex()to reject out-of-bounds requests, thencreateIndex()to build the handle.parent(): returns an invalidQModelIndexfor top-level items. Hierarchical models return the parent row here.data(): returns aQVariantfor a given index and role. This is where you map storage to presentation.
Worked example: a file-path model
The following model stores full paths but exposes only the base name. It assumes Qt 5.15 or Qt 6.x and a standard widget build.
#include <QAbstractItemModel>
#include <QStringList>
#include <QFileInfo>
class FilePathModel : public QAbstractItemModel {
Q_OBJECT
public:
explicit FilePathModel(QObject *parent = nullptr)
: QAbstractItemModel(parent) {}
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override {
if (!hasIndex(row, column, parent))
return QModelIndex();
return createIndex(row, column);
}
QModelIndex parent(const QModelIndex &index) const override {
Q_UNUSED(index);
return QModelIndex();
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
if (parent.isValid())
return 0;
return m_paths.size();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override {
if (parent.isValid())
return 0;
return 1;
}
QVariant data(const QModelIndex &index, int role) const override {
if (!index.isValid() || index.row() >= m_paths.size())
return QVariant();
if (role == Qt::DisplayRole || role == Qt::EditRole)
return QFileInfo(m_paths.at(index.row())).fileName();
return QVariant();
}
void setPaths(const QStringList &paths) {
beginResetModel();
m_paths = paths;
endResetModel();
}
private:
QStringList m_paths;
};
Attach it to a view like any other model:
auto *view = new QListView;
auto *model = new FilePathModel;
model->setPaths({"/usr/bin/foo", "/etc/bar.conf"});
view->setModel(model);
The view should show foo and bar.conf, not the full paths. If it shows the raw strings instead, data() is not being called or the role check is wrong.
Roles and keeping views in sync
data() is called with different roles. Qt::DisplayRole is what the view paints. Qt::EditRole is what an editor widget loads when the user starts editing. If you handle only DisplayRole, an editor may open empty or with stale text. Other roles such as Qt::ToolTipRole or Qt::DecorationRole are optional; return an invalid QVariant for roles you do not support.
Views cache what they draw. If you mutate the underlying QStringList directly, the view will not know. For a wholesale replacement, wrap the change in beginResetModel() and endResetModel() as shown above. For a single cell change, emit dataChanged() with the affected index and role. Forgetting these signals is the most common cause of stale or blank rows.
Trade-offs and limitations
A hand-written model is more code than dropping items into a QStandardItemModel. If your data is already a generic tree or table of strings and numbers, QStandardItemModel is faster to build and debug. Write a custom model when you need to avoid copying data into item objects, when the display value is computed from the stored value, or when the source of truth is an external structure you do not control.
Performance matters for large lists. data() runs on the GUI thread every time a row scrolls into view. Avoid database queries, file I/O, or heavy parsing inside it; precompute values or cache them. The example above is cheap because QFileInfo only parses a short string, but a model backed by thousands of image thumbnails would need a different strategy.
This example is read-only. Supporting in-place editing requires overriding setData() and flags() to return Qt::ItemIsEditable. It is also strictly flat; adding children means reimplementing parent() and index() with real hierarchy logic.
A practical way to check the result
Build the snippet against Qt 5.15 or Qt 6.x. Set a breakpoint inside data() and scroll the QListView. The debugger should stop each time a new row becomes visible, confirming the view is pulling values from your model. Then compare the visible text against QFileInfo(path).fileName() for each entry. If they match and edits are not required, the model is behaving correctly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.