Ejemplo de modelo de ordenación/filtrado personalizado
El ejemplo de modelo de ordenación/filtrado personalizado ilustra cómo subclasificar QSortFilterProxyModel para realizar una ordenación y filtrado avanzados.

La clase QSortFilterProxyModel proporciona soporte para ordenar y filtrar datos pasados entre otro modelo y una vista.
El modelo transforma la estructura de un modelo fuente mediante la asignación de los índices del modelo que suministra a nuevos índices, correspondientes a diferentes ubicaciones, para que los utilicen las vistas. Este enfoque permite reestructurar un modelo fuente dado en lo que respecta a las vistas, sin necesidad de realizar transformaciones en los datos subyacentes y sin duplicar los datos en memoria.
El ejemplo de modelo de clasificación/filtrado personalizado consta de dos clases:
- La clase
MySortFilterProxyModelproporciona un modelo proxy personalizado. - La clase
Windowproporciona la ventana principal de la aplicación, utilizando el modelo proxy personalizado para ordenar y filtrar un modelo de elementos estándar.
Primero echaremos un vistazo a la clase MySortFilterProxyModel para ver cómo se implementa el modelo proxy personalizado, después echaremos un vistazo a la clase Window para ver cómo se utiliza el modelo. Por último, echaremos un vistazo rápido a la función main().
Definición de la Clase MySortFilterProxyModel
La clase MySortFilterProxyModel hereda de la clase QSortFilterProxyModel.
Dado que QAbstractProxyModel y sus subclases derivan de QAbstractItemModel, muchos de los consejos sobre subclases de modelos normales también se aplican a los modelos proxy.
Por otra parte, cabe señalar que muchas de las implementaciones de funciones por defecto de QSortFilterProxyModel están escritas de forma que llaman a las funciones equivalentes en el modelo fuente correspondiente. Este sencillo mecanismo de proxy puede necesitar ser anulado para modelos fuente con un comportamiento más complejo. En este ejemplo derivamos de la clase QSortFilterProxyModel para asegurarnos de que nuestro filtro puede reconocer un rango válido de fechas, y para controlar el comportamiento de ordenación.
class MySortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: MySortFilterProxyModel(QObject *parent = nullptr); QDate filterMinimumDate() const { return minDate; } void setFilterMinimumDate(QDate date); QDate filterMaximumDate() const { return maxDate; } void setFilterMaximumDate(QDate date); protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; private: bool dateInRange(QDate date) const; QDate minDate; QDate maxDate; };
Queremos poder filtrar nuestros datos especificando un periodo de tiempo determinado. Por ese motivo, implementamos las funciones personalizadas setFilterMinimumDate() y setFilterMaximumDate(), así como las funciones correspondientes filterMinimumDate() y filterMaximumDate(). Reimplementamos la función filterAcceptsRow() de QSortFilterProxyModel para que sólo acepte filas con fechas válidas, y QSortFilterProxyModel::lessThan() para poder ordenar los remitentes por sus direcciones de correo electrónico. Por último, implementamos una función de conveniencia dateInRange() que utilizaremos para determinar si una fecha es válida.
Implementación de la clase MySortFilterProxyModel
El constructor de MySortFilterProxyModel es trivial, pasando el parámetro padre al constructor de la clase base:
MySortFilterProxyModel::MySortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { }
Las partes más interesantes de la implementación de MySortFilterProxyModel son las reimplementaciones de las funciones filterAcceptsRow() y lessThan() de QSortFilterProxyModel. Veamos primero nuestra función personalizada lessThan().
bool MySortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { QVariant leftData = sourceModel()->data(left); QVariant rightData = sourceModel()->data(right);
Queremos ordenar los remitentes por sus direcciones de correo electrónico. La función lessThan() se utiliza como operador < al ordenar. La implementación por defecto maneja una colección de tipos incluyendo QDateTime y String, pero para poder ordenar los remitentes por sus direcciones de correo electrónico primero debemos identificar la dirección dentro de la cadena dada:
if (leftData.userType() == QMetaType::QDateTime) return leftData.toDateTime() < rightData.toDateTime(); static const QRegularExpression emailPattern(u"[\\w\\.]*@[\\w\\.]*"_s); QString leftString = leftData.toString(); if (left.column() == 1) { const QRegularExpressionMatch match = emailPattern.match(leftString); if (match.hasMatch()) leftString = match.captured(0); } QString rightString = rightData.toString(); if (right.column() == 1) { const QRegularExpressionMatch match = emailPattern.match(rightString); if (match.hasMatch()) rightString = match.captured(0); } return QString::localeAwareCompare(leftString, rightString) < 0; }
Utilizamos QRegularExpression para definir un patrón para las direcciones que estamos buscando. La función match() devuelve un objeto QRegularExpressionMatch que contiene el resultado de la búsqueda. Si hay coincidencia, hasMatch() devuelve verdadero. El resultado de la coincidencia puede recuperarse con la función captured() de QRegularExpressionMatch. La coincidencia completa tiene índice 0 y las subexpresiones entre paréntesis tienen índices a partir de 1 (excluidos los paréntesis no capturadores).
bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent); QModelIndex index1 = sourceModel()->index(sourceRow, 1, sourceParent); QModelIndex index2 = sourceModel()->index(sourceRow, 2, sourceParent); return (sourceModel()->data(index0).toString().contains(filterRegularExpression()) || sourceModel()->data(index1).toString().contains(filterRegularExpression())) && dateInRange(sourceModel()->data(index2).toDate()); }
Por otra parte, se espera que la función filterAcceptsRow() devuelva verdadero si la fila dada debe incluirse en el modelo. En nuestro ejemplo, se acepta una línea si el asunto o el remitente contienen la expresión regular dada y la fecha es válida.
bool MySortFilterProxyModel::dateInRange(QDate date) const { return (!minDate.isValid() || date > minDate) && (!maxDate.isValid() || date < maxDate); }
Utilizamos nuestra función personalizada dateInRange() para determinar si una fecha es válida.
Para poder filtrar nuestros datos especificando un periodo de tiempo determinado, también implementamos funciones para obtener y establecer las fechas mínima y máxima:
void MySortFilterProxyModel::setFilterMinimumDate(QDate date) { beginFilterChange(); minDate = date; endFilterChange(QSortFilterProxyModel::Direction::Rows); } void MySortFilterProxyModel::setFilterMaximumDate(QDate date) { beginFilterChange(); maxDate = date; endFilterChange(QSortFilterProxyModel::Direction::Rows); }
Las funciones get, filterMinimumDate() y filterMaximumDate(), son triviales y se implementan como función inline en el fichero de cabecera.
Esto completa nuestro modelo proxy personalizado. Veamos cómo podemos utilizarlo en una aplicación.
Definición de la clase Window
La clase CustomFilter hereda de QWidget, y proporciona la ventana principal de la aplicación de este ejemplo:
class Window : public QWidget { Q_OBJECT public: Window(); void setSourceModel(QAbstractItemModel *model); private slots: void textFilterChanged(); void dateFilterChanged(); private: MySortFilterProxyModel *proxyModel; QTreeView *sourceView; QTreeView *proxyView; FilterWidget *filterWidget; QDateEdit *fromDateEdit; QDateEdit *toDateEdit; };
Implementamos dos ranuras privadas, textFilterChanged() y dateFilterChanged(), para responder al usuario que cambia el patrón de filtro, la sensibilidad a mayúsculas y minúsculas, o cualquiera de las fechas. Además, implementamos una función pública setSourceModel() para establecer la relación modelo/vista.
Implementación de la clase Window
En este ejemplo, hemos elegido crear y establecer el modelo fuente en la función main () (a la que volveremos más adelante). Así que cuando construimos la ventana principal de la aplicación, asumimos que ya existe un modelo fuente y empezamos creando una instancia de nuestro modelo proxy personalizado:
Window::Window() : proxyModel(new MySortFilterProxyModel(this)),
Establecemos la propiedad dynamicSortFilter que contiene si el modelo proxy está ordenado y filtrado dinámicamente. Al establecer esta propiedad en true, nos aseguramos de que el modelo se ordena y filtra cada vez que cambia el contenido del modelo fuente.
La ventana principal de la aplicación muestra vistas tanto del modelo fuente como del modelo proxy. La vista del modelo fuente es bastante sencilla:
sourceView->setRootIsDecorated(false); sourceView->setAlternatingRowColors(true);
La clase QTreeView proporciona una implementación modelo/vista por defecto de una vista de árbol. Nuestra vista implementa una representación en árbol de los elementos del modelo fuente de la aplicación.
auto *sourceGroupBox = new QGroupBox(tr("Original Model")); auto *sourceLayout = new QHBoxLayout(sourceGroupBox); sourceLayout->addWidget(sourceView);
La clase QTreeView proporciona una implementación modelo/vista por defecto de una vista en árbol; nuestra vista implementa una representación en árbol de los elementos del modelo fuente de la aplicación. Añadimos nuestro widget de vista a un diseño que instalamos en un cuadro de grupo correspondiente.
La vista del modelo proxy, por otro lado, contiene varios widgets que controlan los distintos aspectos de la transformación de la estructura de datos del modelo fuente:
filterWidget->setText(tr("Grace|Sports")); fromDateEdit->setDate(QDate(1970, 01, 01)); toDateEdit->setDate(QDate(2099, 12, 31));
Obsérvese que cada vez que el usuario cambia una de las opciones de filtrado, debemos volver a aplicar explícitamente el filtro. Esto se hace conectando los distintos editores a funciones que actualizan el modelo proxy.
connect(filterWidget, &FilterWidget::filterChanged, this, &Window::textFilterChanged); connect(filterWidget, &QLineEdit::textChanged, this, &Window::textFilterChanged); connect(fromDateEdit, &QDateTimeEdit::dateChanged, this, &Window::dateFilterChanged); connect(toDateEdit, &QDateTimeEdit::dateChanged, this, &Window::dateFilterChanged);
De la ordenación se encargará la vista. Todo lo que tenemos que hacer es habilitar la ordenación para nuestra vista proxy estableciendo la propiedad QTreeView::sortingEnabled (que es falsa por defecto). A continuación, añadimos todos los widgets de filtrado y la vista proxy a un diseño que instalamos en un cuadro de grupo correspondiente.
proxyView->setRootIsDecorated(false); proxyView->setAlternatingRowColors(true); proxyView->setModel(proxyModel); proxyView->setSortingEnabled(true); proxyView->sortByColumn(1, Qt::AscendingOrder); auto *proxyGroupBox = new QGroupBox(tr("Sorted/Filtered Model")); auto *proxyLayout = new QVBoxLayout(proxyGroupBox); proxyLayout->addWidget(proxyView); auto *formLayout = new QFormLayout; proxyLayout->addLayout(formLayout); formLayout->addRow(tr("&Filter pattern:"), filterWidget); formLayout->addRow(tr("F&rom:"), fromDateEdit); formLayout->addRow(tr("&To:"), toDateEdit);
Finalmente, después de poner nuestros dos cuadros de grupo en otro diseño que instalamos en nuestro widget de aplicación principal, personalizamos la ventana de la aplicación.
auto *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(sourceGroupBox); mainLayout->addWidget(proxyGroupBox); setWindowTitle(tr("Custom Sort/Filter Model")); auto screenGeometry = screen()->geometry(); resize(screenGeometry.width() / 2, screenGeometry.height() * 2 / 3); }
Como se ha mencionado anteriormente, creamos el modelo fuente en la función main (), llamando a la función Window::setSourceModel() para que la aplicación lo utilice:
void Window::setSourceModel(QAbstractItemModel *model) { proxyModel->setSourceModel(model); sourceView->setModel(model); for (int i = 0; i < proxyModel->columnCount(); ++i) proxyView->resizeColumnToContents(i); for (int i = 0; i < model->columnCount(); ++i) sourceView->resizeColumnToContents(i); }
La función QSortFilterProxyModel::setSourceModel() hace que el modelo proxy procese los datos del modelo dado, en este caso nuestro modelo de correo. La función setModel() que el widget de la vista hereda de la clase QAbstractItemModel, establece el modelo para que la vista lo presente. Nótese que esta última función también creará y establecerá un nuevo modelo de selección.
void Window::textFilterChanged() { FilterWidget::PatternSyntax s = filterWidget->patternSyntax(); QString pattern = filterWidget->text(); switch (s) { case FilterWidget::Wildcard: pattern = QRegularExpression::wildcardToRegularExpression(pattern); break; case FilterWidget::FixedString: pattern = QRegularExpression::escape(pattern); break; default: break; } QRegularExpression::PatternOptions options = QRegularExpression::NoPatternOption; if (filterWidget->caseSensitivity() == Qt::CaseInsensitive) options |= QRegularExpression::CaseInsensitiveOption; QRegularExpression regularExpression(pattern, options); proxyModel->setFilterRegularExpression(regularExpression); }
La función textFilterChanged() es llamada cada vez que el usuario cambia el patrón de filtrado o la sensibilidad a mayúsculas/minúsculas.
Primero recuperamos la sintaxis preferida (el enum FilterWidget::PatternSyntax se utiliza para interpretar el significado del patrón dado), luego determinamos la sensibilidad a mayúsculas preferida. Basándonos en estas preferencias y en el patrón de filtro actual, establecemos la propiedad filterRegularExpression del modelo proxy. La propiedad filterRegularExpression contiene la expresión regular utilizada para filtrar el contenido del modelo fuente. Tenga en cuenta que la llamada a la función setFilterRegularExpression() de QSortFilterProxyModel también actualiza el modelo.
void Window::dateFilterChanged() { proxyModel->setFilterMinimumDate(fromDateEdit->date()); proxyModel->setFilterMaximumDate(toDateEdit->date()); }
La función dateFilterChanged() es llamada cada vez que el usuario modifica el rango de fechas válidas. Recuperamos las nuevas fechas de la interfaz de usuario y llamamos a las funciones correspondientes (proporcionadas por nuestro modelo proxy personalizado) para establecer las fechas mínima y máxima del modelo proxy. Como hemos explicado anteriormente, la llamada a estas funciones también actualiza el modelo.
La función Main()
En este ejemplo, hemos separado la aplicación del modelo fuente creando el modelo en la función main (). Primero creamos la aplicación, luego creamos el modelo fuente:
int main(int argc, char *argv[]) { QApplication app(argc, argv); Window window; window.setSourceModel(new MailModel(&window)); window.show(); return QCoreApplication::exec(); }
Creamos una clase MailModel, que hereda de QRangeModel y reimplementa QAbstractItemView::headerData() para devolver los encabezados de sección correctos. Se rellena a partir de un array de struct MailHeader. MailHeader se declara como un Q_GADGET con propiedades para ser usadas por QRangeModel:
struct MailHeader { private: Q_GADGET Q_PROPERTY(QString subject MEMBER subject) Q_PROPERTY(QString sender MEMBER sender) Q_PROPERTY(QDateTime date MEMBER date) public: QString subject; QString sender; QDateTime date; }; class MailModel : public QRangeModel { public: explicit MailModel(QObject *parent = nullptr) : QRangeModel(mails, parent) {} QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case 0: return Window::tr("Subject"); case 1: return Window::tr("Sender"); case 2: return Window::tr("Date"); default: break; } } return QRangeModel::headerData(section, orientation, role); } private: static const std::array<MailHeader, 10> mails; }; const std::array<MailHeader, 10> MailModel::mails = { MailHeader{ u"RE: Sports"_s, u"Petra Schmidt <petras@nospam.com>"_s, QDateTime(QDate(2007, 01, 05), QTime(12, 01)) }, MailHeader{ u"AW: Sports"_s, u"Rolf Newschweinstein <rolfn@nospam.com>"_s, QDateTime(QDate(2007, 01, 05), QTime(12, 00)) }, MailHeader{ u"Sports"_s, u"Linda Smith <linda.smith@nospam.com>"_s, QDateTime(QDate(2007, 01, 05), QTime(11, 33)) }, MailHeader{ u"Re: Accounts"_s, u"Andy <andy@nospam.com>"_s, QDateTime(QDate(2007, 01, 03), QTime(14, 26)) }, MailHeader{ u"Re: Accounts"_s, u"Joe Bloggs <joe@bloggs.com>"_s, QDateTime(QDate(2007, 01, 03), QTime(14, 18)) }, MailHeader{ u"Re: Expenses"_s, u"Andy <andy@nospam.com>"_s, QDateTime(QDate(2007, 01, 02), QTime(16, 05)) }, MailHeader{ u"Expenses"_s, u"Joe Bloggs <joe@bloggs.com>"_s, QDateTime(QDate(2006, 12, 25), QTime(11, 39)) }, MailHeader{ u"Accounts"_s, u"pascale@nospam.com"_s, QDateTime(QDate(2006, 12, 31), QTime(12, 50)) }, MailHeader{ u"Radically new concept"_s, u"Grace K. <grace@software-inc.com>"_s, QDateTime(QDate(2006, 12, 22), QTime(9, 44)) }, MailHeader{ u"Happy New Year!"_s, u"Grace K. <grace@software-inc.com>"_s, QDateTime(QDate(2006, 12, 31), QTime(17, 03)) } };
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.