Filtering Large Tables in Qt with QSortFilterProxyModel
Learn how QSortFilterProxyModel provides live filtering and sorting for Qt item views without duplicating data, plus tips for batch updates and custom roles.
27 Dec 2025, 20:05 UTC

Problem: UI lag when filtering thousands of rows
Imagine a QTableView bound to a model that holds 10 000 rows of log entries. As the user types into a search box, the application calls setFilterFixedString on the model for each keystroke. If the model is a plain QStandardItemModel, every filter triggers a full scan and a reset of the view, causing noticeable stutter and high CPU usage.
Thesis: Use QSortFilterProxyModel for on‑the‑fly filtering and sorting
QSortFilterProxyModel sits between the source model and any item view. It intercepts data requests, applies a filter expression and a sort order, and forwards only the matching rows to the view. Because it never copies the underlying data, the operation stays O(n) in the number of rows and updates are limited to the signals the proxy emits when the filter or sort changes.
How the proxy works
- The proxy implements
QAbstractItemModelitself, delegatingdata(),index(), andparent()calls to the source model after translating coordinates withmapToSourceandmapFromSource. - Filtering is performed by
filterAcceptsRow, which by default uses aQRegularExpressionon the column or role you specify viafilterKeyColumnorfilterRole. - Sorting uses
lessThan, which compares the data returned by the source model for the given role (default isQt::DisplayRole). - Only
layoutChangedanddataChangedsignals are emitted when the filter or sort criteria change, keeping view repaints minimal.
Setting up filter and sort
The following snippet shows a typical setup in a Qt Widgets application. Place it in your MainWindow constructor after creating the UI.
// source model – could be any QAbstractItemModel subclass
QStandardItemModel *sourceModel = new QStandardItemModel(this);
// populate sourceModel with your data (omitted for brevity)
// proxy model
QSortFilterProxyModel *proxy = new QSortFilterProxyModel(this);
proxy->setSourceModel(sourceModel);
proxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
proxy->setFilterKeyColumn(1); // filter on the second column
// connect a QLineEdit for live filtering
connect(ui->searchBox, &QLineEdit::textChanged,
[proxy](const QString &text) {
proxy->setFilterFixedString(text);
});
// enable sorting on the header
ui->tableView->setSortingEnabled(true);
ui->tableView->setModel(proxy);
ui->tableView->sortByColumn(0, Qt::AscendingOrder);
Run the application with qmake && make (or your preferred build system) and execute the resulting binary. No special permissions are required.
Worked example: verifying the mapping
To confirm that the proxy correctly translates view indices to source indices, you can inspect the currently selected row:
void MainWindow::on_tableView_clicked(const QModelIndex &index)
{
if (!index.isValid()) return;
QModelIndex sourceIdx = proxy->mapToSource(index);
qDebug() << "View row:" << index.row()
<< "Source row:" << sourceIdx.row()
<< "Data:" << sourceModel->data(sourceIdx, Qt::DisplayRole).toString();
}
When you click a row, the console should show the same underlying data regardless of how the view is filtered or sorted. This demonstrates that the proxy maintains a correct mapping without duplicating items.
Trade‑off and limitation
The proxy is lightweight, but it relies on the source model to emit efficient change signals. If you insert or remove many rows individually, the proxy will forward each change, potentially causing view flicker. The recommended practice is to batch modifications:
sourceModel->beginResetModel(); // … perform many inserts/removes … sourceModel->endResetModel();Another limitation concerns custom data roles. By default,
filterAcceptsRowandlessThanonly considerQt::DisplayRole(andQt::EditRolefor editing). If your model stores relevant information in a custom role, you must subclassQSortFilterProxyModeland override those methods to use the desired role.Actionable closing
Start by wrapping your existing source model with a
QSortFilterProxyModel, connect a search box tosetFilterFixedString, and enable sorting on the view. Test with a dataset of at least a few thousand rows; you should see immediate filtering without perceptible delay. If you notice stutter, batch your model updates withbeginResetModel/endResetModel. For custom roles, subclass the proxy and implement the appropriate filtering and sorting logic. This approach gives you a responsive UI while keeping memory usage low.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.