Qt provides a built-in mechanism to sort and filter model data using QSortFilterProxyModel. This proxy model sits between your source model and the view, allowing you to reorder and filter rows without modifying the underlying data. In this tutorial, you'll learn how to use QSortFilterProxyModel to add sorting and searching to a QTableView in PyQt6.
Sorting a QTableView with QSortFilterProxyModel
To sort data in a QTableView, you create a QSortFilterProxyModel, set your source model on it, and then call .sort() with the column index and sort order. The proxy model handles reordering the rows for display while leaving your original data untouched.
import sys
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt, QSortFilterProxyModel
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def data(self, index, role):
if role == Qt.DisplayRole:
# See below for the nested-list data structure.
# .row() indexes into the outer list,
# .column() indexes into the sub-list
return self._data[index.row()][index.column()]
def rowCount(self, index):
# The length of the outer list.
return len(self._data)
def columnCount(self, index):
# The following takes the first sub-list, and returns
# the length (only works if all rows are an equal length)
return len(self._data[0])
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
data = [
[4, 9, 2],
[1, 0, 0],
[3, 5, 0],
[3, 3, 2],
[7, 8, 9],
]
self.model = TableModel(data)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setSourceModel(self.model)
self.proxy_model.sort(0, Qt.AscendingOrder)
self.table.setModel(self.proxy_model)
self.setCentralWidget(self.table)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Searching and Filtering with QSortFilterProxyModel
As the name suggests, QSortFilterProxyModel can also be used to filter and search through model data. With the proxy model in place, you can enable searching by setting a filter key column and then passing in a string to filter rows.
For example, to search for a specific, fixed string across all columns, set the key column to -1 and pass the search string to .setFilterFixedString().
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setFilterKeyColumn(-1) # Search all columns.
self.proxy_model.setSourceModel(self.model)
self.proxy_model.setFilterFixedString('<string to search>')
The proxy model supports a number of different filter types through its public slot methods — including a basic string search, regular expressions and wildcard searches.
| Filter function (slot) | Parameters |
|---|---|
setFilterFixedString |
str simple string to search |
setFilterRegularExpression |
str or QRegularExpression regular expression |
setFilterWildcard |
str wildcard expression |
These public slots allow you to connect other signals to the proxy filter model and have the table filtered automatically each time the signal fires. A common use case is to connect a QLineEdit.textChanged signal to setFilterFixedString — this lets users search and filter the table in real time by typing in a text field.
Complete Example: Filtering a QTableView with a Search Bar
Below is a complete working example using a fixed string search against sample table data stored in a Python list of lists. The QLineEdit search bar is connected directly to the proxy model's filter slot, so typing in the box immediately filters the QTableView.
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QTableView, QMainWindow, QVBoxLayout, QLineEdit
from PyQt6.QtCore import Qt, QSortFilterProxyModel, QAbstractTableModel
class TableModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def data(self, index, role):
if role == Qt.DisplayRole:
# See below for the nested-list data structure.
# .row() indexes into the outer list,
# .column() indexes into the sub-list
return self._data[index.row()][index.column()]
def rowCount(self, index):
# The length of the outer list.
return len(self._data)
def columnCount(self, index):
# The following takes the first sub-list, and returns
# the length (only works if all rows are an equal length)
return len(self._data[0])
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.table = QTableView()
data = [
[4, 9, 2],
[1, "hello", 0],
[3, 5, 0],
[3, 3, "what"],
["this", 8, 9],
]
self.model = TableModel(data)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setFilterKeyColumn(-1) # Search all columns.
self.proxy_model.setSourceModel(self.model)
self.proxy_model.sort(0, Qt.AscendingOrder)
self.table.setModel(self.proxy_model)
self.searchbar = QLineEdit()
# You can choose the type of search by connecting to a different slot here.
# see https://doc.qt.io/qt-5/qsortfilterproxymodel.html#public-slots
self.searchbar.textChanged.connect(self.proxy_model.setFilterFixedString)
layout = QVBoxLayout()
layout.addWidget(self.searchbar)
layout.addWidget(self.table)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
If you run this and start typing in the box, you'll see the table view being filtered automatically.
It doesn't matter what data structure you are using. As long as your model supports the standard interface the QSortFilterProxyModel will correctly search and filter it into the view.
Using Wildcard Filters
You can also replace the fixed string search with a wildcard search slot for more flexible pattern matching.
self.searchbar.textChanged.connect(self.proxy_model.setFilterWildcard)
This allows users to use * and ? wildcard characters in their search queries, making it easy to match partial patterns across your table data.
Packaging Python Applications with PyInstaller by Martin Fitzpatrick
This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.