Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-135605

QSortFilterProxyModel::invalidateFilter() after invalidate() does not emit all signals in proxy models

    XMLWordPrintable

Details

    • Bug
    • Resolution: Unresolved
    • P2: Important
    • None
    • 6.9.0
    • Core: Item Models
    • 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

          No reviews matched the request. Check your Options in the drop-down menu of this sections header.

          Activity

            People

              dfaure_kdab David Faure
              permotion88 Karol Polak
              Votes:
              1 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated:

                Gerrit Reviews

                  There are no open Gerrit changes