Details
-
Bug
-
Resolution: Unresolved
-
P2: Important
-
None
-
6.9.0
-
None
Description
Changes to the Api of QSortFilterProxyModel in Qt 6.9.0 should resolve problems with missing signals after filtering if the invalidate() method was previously called. However, problems still occur with more complex model proxy hierarchies.
I modified the example from QTBUG-115717 to visualize the problem. The last model in the hierarchy does not get a signal that a row has been deleted. This problem affects the inherited and default QSortFilterProxyModel.
Additionally, I'm not sure if the new API with begin/end Filtering will really solve all the problems with updates. It can be very difficult to migrate existing code, where, for example, after changing some external repositories / dependencies, the model filtering has to be updated. If there are many such dependencies that additionally do not signal “about” only “done” it will be impossible to properly use the new API to create mapping at the “about” point. This works great for simple filtering but in more complex cases you observe many dependencies and call e.g. lazy filtering when all dependencies are ready. If the problems occur after invalidate / layoutChanged call, maybe we should create the mapping from scratch there, and leave the filtering API as it was before....
Back to the bug, here is the code:
#include <QCoreApplication> #include <QSortFilterProxyModel> #include <QStringListModel> #include <QTimer> #include <QDebug> class CustomProxyModel : public QSortFilterProxyModel { Q_OBJECT public: CustomProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} void setFilter(const QString& s) { if (m_matchString == s) return; beginFilterChange(); m_matchString = s; invalidateFilter(); } bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override { const auto index = sourceModel()->index(sourceRow, 0, sourceParent); if (!index.isValid()) return false; bool accepted = index.data().value<QString>() == m_matchString; qDebug() << '\t' << this << index.data() << "Accepted?" << accepted; return accepted; } private: QString m_matchString; }; int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); QStringListModel model({"1", "2", "3", "4", "5"}); // ============================================== qDebug() << "=== Testing CustomProxyModel ==="; qDebug() << "Initializing..."; CustomProxyModel proxyModel; proxyModel.setObjectName("proxyModel"); proxyModel.setFilter("X"); // Reject all source data at the start proxyModel.sort(0, Qt::AscendingOrder); // Trigger an evaluation proxyModel.setSourceModel(&model); CustomProxyModel proxyModel2; proxyModel2.setObjectName("proxyModel2"); proxyModel2.setFilter("3"); proxyModel2.setSourceModel(&proxyModel); qDebug() << "Invalidating..."; proxyModel.invalidate(); qDebug() << "Connnecting signal..."; QObject::connect(&proxyModel, &QSortFilterProxyModel::rowsInserted, &app, [&] { qDebug() << &proxyModel << "SIGNAL rowsInserted EMITTED! CustomProxyModel's new rowCount():" << proxyModel.rowCount(); }); QObject::connect(&proxyModel2, &QSortFilterProxyModel::rowsInserted, &app, [&] { qDebug() << &proxyModel2 << "SIGNAL rowsInserted EMITTED! CustomProxyModel's new rowCount():" << proxyModel2.rowCount(); }); QObject::connect(&proxyModel, &QSortFilterProxyModel::rowsRemoved, &app, [&] { qDebug() << &proxyModel << "SIGNAL rowsRemoved EMITTED! CustomProxyModel's new rowCount():" << proxyModel.rowCount(); }); QObject::connect(&proxyModel2, &QSortFilterProxyModel::rowsRemoved, &app, [&] { qDebug() << &proxyModel2 << "SIGNAL rowsRemoved EMITTED! CustomProxyModel's new rowCount():" << proxyModel2.rowCount(); }); qDebug() << "Changing filter..."; proxyModel.setFilter("3"); // We expect to see both signals emitted here to learn that both proxy models now have 1 row qDebug() << "Invalidating 2..."; proxyModel.invalidate(); // <----------------------- Comment out this line to make everything work properly qDebug() << "Changing filter 2..."; proxyModel.setFilter("X"); // ============================================== qDebug() << ""; qDebug() << "=== Testing QSortFilterProxyModel ==="; qDebug() << "Initializing..."; QSortFilterProxyModel qsfpm; qsfpm.setObjectName("qsfpm"); qsfpm.setFilterRegularExpression("X"); // Reject all source data at the start qsfpm.setSourceModel(&model); QSortFilterProxyModel qsfpm2; qsfpm2.setObjectName("qsfpm2"); qsfpm2.setFilterRegularExpression("3"); // Reject all source data at the start qsfpm2.setSourceModel(&qsfpm); qDebug() << "Invalidating..."; qsfpm.invalidate(); qDebug() << "Connnecting signal..."; QObject::connect(&qsfpm, &QSortFilterProxyModel::rowsInserted, &app, [&] { qDebug() << &qsfpm << "SIGNAL rowsInserted EMITTED! QSortFilterProxyModel's new rowCount():" << qsfpm.rowCount(); }); QObject::connect(&qsfpm2, &QSortFilterProxyModel::rowsInserted, &app, [&] { qDebug() << &qsfpm2 << "SIGNAL rowsInserted EMITTED! QSortFilterProxyModel's new rowCount():" << qsfpm2.rowCount(); }); QObject::connect(&qsfpm, &QSortFilterProxyModel::rowsRemoved, &app, [&] { qDebug() << &qsfpm << "SIGNAL rowsRemoved EMITTED! QSortFilterProxyModel's new rowCount():" << qsfpm.rowCount(); }); QObject::connect(&qsfpm2, &QSortFilterProxyModel::rowsRemoved, &app, [&] { qDebug() << &qsfpm2 << "SIGNAL rowsRemoved EMITTED! QSortFilterProxyModel's new rowCount():" << qsfpm2.rowCount(); }); qDebug() << "Changing filter..."; qsfpm.setFilterRegularExpression("3"); qDebug() << "Invalidating 2..."; qsfpm.invalidate(); // <----------------------- Comment out this line to make everything work properly qDebug() << "Changing filter 2..."; qsfpm.setFilterRegularExpression("X"); // ============================================== } #include "main.moc"
and output:
=== Testing CustomProxyModel === Initializing... CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "1") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "2") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "3") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "4") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "5") Accepted? false Invalidating... Connnecting signal... Changing filter... CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "1") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "2") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "3") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "4") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "5") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "1") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "2") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "3") Accepted? true CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "4") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "5") Accepted? false CustomProxyModel(0x1fa5d9f018, name = "proxyModel2") QVariant(QString, "3") Accepted? true CustomProxyModel(0x1fa5d9f018, name = "proxyModel2") SIGNAL rowsInserted EMITTED! CustomProxyModel's new rowCount(): 1 CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") SIGNAL rowsInserted EMITTED! CustomProxyModel's new rowCount(): 1 Invalidating 2... Changing filter 2... CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "1") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "2") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "3") Accepted? true CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "4") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "5") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "3") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "1") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "2") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "4") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") QVariant(QString, "5") Accepted? false CustomProxyModel(0x1fa5d9efd8, name = "proxyModel") SIGNAL rowsRemoved EMITTED! CustomProxyModel's new rowCount(): 0 === Testing QSortFilterProxyModel === Initializing... Invalidating... Connnecting signal... Changing filter... QSortFilterProxyModel(0x1fa5d9f088, name = "qsfpm2") SIGNAL rowsInserted EMITTED! QSortFilterProxyModel's new rowCount(): 1 QSortFilterProxyModel(0x1fa5d9f058, name = "qsfpm") SIGNAL rowsInserted EMITTED! QSortFilterProxyModel's new rowCount(): 1 Invalidating 2... Changing filter 2... QSortFilterProxyModel(0x1fa5d9f058, name = "qsfpm") SIGNAL rowsRemoved EMITTED! QSortFilterProxyModel's new rowCount(): 0
Attachments
Issue Links
- relates to
-
QTBUG-115717 QSortFilterProxyModel::invalidateFilter() sometimes does not trigger a reevaluation of the model
-
- Closed
-