New in Qt 6.11: QRangeModel updates and QRangeModelAdapter
February 25, 2026 by Volker Hilsheimer | Comments
When introducing QRangeModel for Qt 6.10 I wrote that we'd try to tackle some limitations in future releases. In Qt 611, QRangeModel supports caching ranges like std::views::filter, and provides a customization point for reading from and writing role-data to items that are not gadgets, objects, or associative containers. The two biggest additions make it possible to safely operate on the underlying model data and structure without using QAbstractItemModel API.
For a complete overview of things we added for Qt 6.11's QRangeModel as well as some code snippets, head over to the post on the Qt Forum:
https://forum.qt.io/topic/164323/new-in-qt-6.11-qrangemodel-updates-and-qrangemodeladapter
Model updates when properties change
For a QRangeModel where all items or rows are backed by instances of the same QObject type, the model can now automatically emit the dataChanged() signal when properties of those objects change. This provides a convenient mechanism to keep things in sync without having to go through the QAbstractItemModel API for such changes. Let's say our data is backed by an item type like this:
class Person : public QObject
{
Q_OBJECT
Q_PROPERTY(QString firstName READ firstName WRITE setFirstName NOTIFY firstNameChanged)
Q_PROPERTY(QString lastName READ lastName WRITE setLastName NOTIFY lastNameChanged)
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
public:
explicit Person( /* ~~~ */ );
// ~~~
Q_SIGNALS:
// ~~~
void ageChanged(int age);
private:
int m_age = 0;
};
QRangeModel represents a list of such objects as a table with three columns, one for each property.
// backend code
QList<Person *> data = {
new Person("Max", "Mustermann", 37),
new Person("Eva", "Nordmann", 39),
new Person("Bob", "Mountainman", 42),
};
model = new QRangeModel(&data);
model->setAutoConnectPolicy(QRangeModel::AutoConnectPolicy::Full);
// frontend code
tableView = new QTableView(this);
tableView->setModel(model);
By setting the autoConnectPolicy of QRangeModel to either Full or OnRead, future changes to properties will make the model emit QAbstractItemModel::dataChanged() for the corresponding index and role. This in turn updates all views.
// backend code
data[0]->setAge(data[0]->age() + 1);
The only requirement is that the property has a notification signal that the setter emits when the value changes.
Using QObject instances as the data type backing a model is comparatively expensive, but this addition makes it easy to work with the data inside small models directly, without having to deal with QModelIndex or QVariant.
Modify a model with QRangeModelAdapter
Making it unnecessary to deal with those QAbstractItemModel'isms for trivial operations is also the idea behind the biggest item-models addition in Qt 6.11: QRangeModelAdapter is a new template class that makes it safe and convenient to work with the data structure a QRangeModel operates on. Item data and row/column structure of the underlying range can be modified and accessed through the adapter without having to deal with QModelIndex or QVariant, and the adapter makes sure that the model emits signals, invalidates and updates persistent indexes, and informs views about the changes.
struct Backend
{
QList<int> data { 1, 2, 3, 4 };
QRangeModelAdapter adapter(std::ref(data));
void updateData();
};
class Frontend : public QMainWindow
{
public:
Frontend()
{
// ~~~
listView->setModel(backend.adapter.model()); // the adapter creates and owns the model
}
};
// ~~~
void Backend::updateData()
{
adapter[0] = 23; // emits dataChanged()
adapter.insertRow(0, 78); // prepends 78, calls begin/endInsertRows
adapter.removeRows(2, 2); // removes two rows, calls begin/endRemoveRows
}
If the underlying data structure is a table, then an individual item can be modified like this:
void Backend::updateData()
{
adapter.at(1, 0) = 44; // works with C++17
adapter[0, 1] = 55; // with C++23's multidimensional subscript operator
}
For tree-shaped data structures, the row can be accessed using a path of row indexes:
void Backend::updateData()
{
adapter.at({0, 1}, 1) = "zero/one:one"; // second column in the second child of the first toplevel row
}
In addition to index-based access to rows and columns, QRangeModelAdapter provides an iterator API:
void Backend::storeData() const
{
for (const auto &item : adapter)
storage << item;
}
With a table, clearing all cells while maintaining the dimensions of the table could be done like this:
void Backend::clearData()
{
for (auto row : adapter) {
for (auto item : row) {
item = {};
}
}
}
QRangeModelAdapter is in technology preview for Qt 6.11; the API is a bit unorthodox, with different semantics depending on the underlying data structure. The heavy use of C++ 17 meta programming techniques also makes the documentation a bit unwieldy, which has given our qdoc developers a good reason to improve the rendering of template APIs. We'd very much like to hear your feedback to it!
Customizing item access
We added a new customization point for implementing custom item access. Specialize QRangeModel::ItemAccess for your type and implement static readRole and writeRole accessors to read and write role-specific values to an item:
template <>
struct QRangeModel::ItemAccess<Person>
{
static QVariant readRole(const Person &item, int role)
{
switch (role) {
case Qt::DisplayRole:
return item.firstName() + " " + item.lastName();
case Qt::UserRole:
return item.firstName();
case Qt::UserRole + 1:
return item.lastName();
case Qt::UserRole + 2:
return item.age();
}
return {};
}
static bool writeRole(Person &item, const QVariant &data, int role)
{
bool ok = true;
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole: {
const QStringList names = data.toString().split(u' ');
if (names.size() > 0)
item.setFirstName(names.at(0));
if (names.size() > 1)
item.setLastName(names.at(1));
break;
}
case Qt::UserRole:
item.setFirstName(data.toString());
break;
case Qt::UserRole + 1:
item.setLastName(data.toString());
break;
case Qt::UserRole + 2:
item.setAge(data.toInt(&ok));
break;
default:
ok = false;
break;
}
return ok;
}
};
A specialization of ItemAccess takes precedence over built-in access mechanisms, and also implies that QRangeModel will interpret the type as a multi-role item, even if it could be treated as a multi-column row. So you can use this technique to customize and optimize access to tuple-like types, gadgets, or completely custom structs.
Support for caching std::ranges
Last but not least, a minor improvement for users of std::ranges: QRangeModel no longer requires std::being/end on constant ranges, so you can use e.g. std::views::filter as input to your model:
const QDate today = QDate::currentDate();
model = new QRangeModel(std::views::iota(today.addYears(-100), today.addYears(100))
| std::views::filter([](QDate date) { return date.dayOfWeek() < 6; })
);
And if std::ranges is already old news for you and you want to read more about bleeding-edge C++ stuff, check the blog post about my hackathon project using C++26 reflections to eliminate more boiler plate for custom structs.
Conclusion
With Qt 6.11 we make it easier than ever to work with C++ data structures as sources of UI data. Modifying data with QRangeModelAdapter uses API concepts that are familiar for C++ developers, while making sure that QAbstractItemModel clients are informed about relevant changes. Automatically binding the dataChanged() to QObject properties, support for more C++20 ranges, and a new customization point round of the additions to Qt's modern item model handling for C++ developers.
Blog Topics:
Comments
Subscribe to our newsletter
Subscribe Newsletter
Try Qt 6.10 Now!
Download the latest release here: www.qt.io/download.
Qt 6.10 is now available, with new features and improvements for application developers and device creators.
We're Hiring
Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.