Avoid redundant calls to QSortFilterProxyModel::filterAcceptsRow() if the filter has become strictly narrower

后端 未结 2 905
孤独总比滥情好
孤独总比滥情好 2021-02-14 05:25

Is there any way invalidate the filter in a QSortFilterProxyModel, but to indicate that the filter has been narrowed down so filterAcceptsRow() should be called onl

2条回答
  •  余生分开走
    2021-02-14 05:49

    I wrote a QIdentityProxyModel subclass that stores a list of chained QSortFilterProxyModel. It provides an interface similar to QSortFilterProxyModel and accepts a narrowedDown boolean parameter that indicates if the filter is being narrowed down. So that:

    • When the filter is being narrowed down, a new QSortFilterProxyModel is appended to the chain, and the QIdentityProxyModel switches to proxy the new filter at the end of the chain.
    • Otherwise, It deletes all the filters in the chain, constructs a new chain with one filter that corresponds to the current filtering criteria. After that, the QIdentityProxyModel switches to proxy the new filter in the chain.

    Here is a program that compares the class to using a normal QSortFilterProxyModel subclass:

    #include 
    
    class FilterProxyModel : public QSortFilterProxyModel{
    public:
        explicit FilterProxyModel(QObject* parent= nullptr):QSortFilterProxyModel(parent){}
        ~FilterProxyModel(){}
    
        //you can override filterAcceptsRow here if you want
    };
    
    //the class stores a list of chained FilterProxyModel and proxies the filter model
    
    class NarrowableFilterProxyModel : public QIdentityProxyModel{
        Q_OBJECT
        //filtering properties of QSortFilterProxyModel
        Q_PROPERTY(QRegExp filterRegExp READ filterRegExp WRITE setFilterRegExp)
        Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn)
        Q_PROPERTY(Qt::CaseSensitivity filterCaseSensitivity READ filterCaseSensitivity WRITE setFilterCaseSensitivity)
        Q_PROPERTY(int filterRole READ filterRole WRITE setFilterRole)
    public:
        explicit NarrowableFilterProxyModel(QObject* parent= nullptr):QIdentityProxyModel(parent), m_filterKeyColumn(0),
            m_filterCaseSensitivity(Qt::CaseSensitive), m_filterRole(Qt::DisplayRole), m_source(nullptr){
        }
    
        void setSourceModel(QAbstractItemModel* sourceModel){
            m_source= sourceModel;
            QIdentityProxyModel::setSourceModel(sourceModel);
            for(FilterProxyModel* proxyNode : m_filterProxyChain) delete proxyNode;
            m_filterProxyChain.clear();
            applyCurrentFilter();
        }
    
        QRegExp filterRegExp()const{return m_filterRegExp;}
        int filterKeyColumn()const{return m_filterKeyColumn;}
        Qt::CaseSensitivity filterCaseSensitivity()const{return m_filterCaseSensitivity;}
        int filterRole()const{return m_filterRole;}
    
        void setFilterKeyColumn(int filterKeyColumn, bool narrowedDown= false){
            m_filterKeyColumn= filterKeyColumn;
            applyCurrentFilter(narrowedDown);
        }
        void setFilterCaseSensitivity(Qt::CaseSensitivity filterCaseSensitivity, bool narrowedDown= false){
            m_filterCaseSensitivity= filterCaseSensitivity;
            applyCurrentFilter(narrowedDown);
        }
        void setFilterRole(int filterRole, bool narrowedDown= false){
            m_filterRole= filterRole;
            applyCurrentFilter(narrowedDown);
        }
        void setFilterRegExp(const QRegExp& filterRegExp, bool narrowedDown= false){
            m_filterRegExp= filterRegExp;
            applyCurrentFilter(narrowedDown);
        }
        void setFilterRegExp(const QString& filterRegExp, bool narrowedDown= false){
            m_filterRegExp.setPatternSyntax(QRegExp::RegExp);
            m_filterRegExp.setPattern(filterRegExp);
            applyCurrentFilter(narrowedDown);
        }
        void setFilterWildcard(const QString &pattern, bool narrowedDown= false){
            m_filterRegExp.setPatternSyntax(QRegExp::Wildcard);
            m_filterRegExp.setPattern(pattern);
            applyCurrentFilter(narrowedDown);
        }
        void setFilterFixedString(const QString &pattern, bool narrowedDown= false){
            m_filterRegExp.setPatternSyntax(QRegExp::FixedString);
            m_filterRegExp.setPattern(pattern);
            applyCurrentFilter(narrowedDown);
        }
    
    private:
        void applyCurrentFilter(bool narrowDown= false){
            if(!m_source) return;
            if(narrowDown){ //if the filter is being narrowed down
                //instantiate a new filter proxy model and add it to the end of the chain
                QAbstractItemModel* proxyNodeSource= m_filterProxyChain.empty()?
                            m_source : m_filterProxyChain.last();
                FilterProxyModel* proxyNode= newProxyNode();
                proxyNode->setSourceModel(proxyNodeSource);
                QIdentityProxyModel::setSourceModel(proxyNode);
                m_filterProxyChain.append(proxyNode);
            } else { //otherwise
                //delete all filters from the current chain
                //and construct a new chain with the new filter in it
                FilterProxyModel* proxyNode= newProxyNode();
                proxyNode->setSourceModel(m_source);
                QIdentityProxyModel::setSourceModel(proxyNode);
                for(FilterProxyModel* node : m_filterProxyChain) delete node;
                m_filterProxyChain.clear();
                m_filterProxyChain.append(proxyNode);
            }
        }
        FilterProxyModel* newProxyNode(){
            //return a new child FilterModel with the current properties
            FilterProxyModel* proxyNode= new FilterProxyModel(this);
            proxyNode->setFilterRegExp(filterRegExp());
            proxyNode->setFilterKeyColumn(filterKeyColumn());
            proxyNode->setFilterCaseSensitivity(filterCaseSensitivity());
            proxyNode->setFilterRole(filterRole());
            return proxyNode;
        }
        //filtering parameters for QSortFilterProxyModel
        QRegExp m_filterRegExp;
        int m_filterKeyColumn;
        Qt::CaseSensitivity m_filterCaseSensitivity;
        int m_filterRole;
    
        QAbstractItemModel* m_source;
        QList m_filterProxyChain;
    };
    
    //Demo program that uses the class
    
    //used to fill the table with dummy data
    std::string nextString(std::string str){
        int length= str.length();
        for(int i=length-1; i>=0; i--){
            if(str[i] < 'z'){
                str[i]++; return str;
            } else str[i]= 'a';
        }
        return std::string();
    }
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        //set up GUI
        QWidget w;
        QGridLayout layout(&w);
        QLineEdit lineEditFilter;
        lineEditFilter.setPlaceholderText("filter");
        QLabel titleTable1("NarrowableFilterProxyModel:");
        QTableView tableView1;
        QLabel labelTable1;
        QLabel titleTable2("FilterProxyModel:");
        QTableView tableView2;
        QLabel labelTable2;
        layout.addWidget(&lineEditFilter,0,0,1,2);
        layout.addWidget(&titleTable1,1,0);
        layout.addWidget(&tableView1,2,0);
        layout.addWidget(&labelTable1,3,0);
        layout.addWidget(&titleTable2,1,1);
        layout.addWidget(&tableView2,2,1);
        layout.addWidget(&labelTable2,3,1);
    
        //set up models
        QStandardItemModel sourceModel;
        NarrowableFilterProxyModel filterModel1;;
        tableView1.setModel(&filterModel1);
    
        FilterProxyModel filterModel2;
        tableView2.setModel(&filterModel2);
    
        QObject::connect(&lineEditFilter, &QLineEdit::textChanged, [&](QString newFilter){
            QTime stopWatch;
            newFilter.prepend("^"); //match from the beginning of the name
            bool narrowedDown= newFilter.startsWith(filterModel1.filterRegExp().pattern());
            stopWatch.start();
            filterModel1.setFilterRegExp(newFilter, narrowedDown);
            labelTable1.setText(QString("took: %1 msecs").arg(stopWatch.elapsed()));
            stopWatch.start();
            filterModel2.setFilterRegExp(newFilter);
            labelTable2.setText(QString("took: %1 msecs").arg(stopWatch.elapsed()));
        });
    
        //fill model with strings from "aaa" to "zzz" (17576 rows)
        std::string str("aaa");
        while(!str.empty()){
            QList row;
            row.append(new QStandardItem(QString::fromStdString(str)));
            sourceModel.appendRow(row);
            str= nextString(str);
        }
        filterModel1.setSourceModel(&sourceModel);
        filterModel2.setSourceModel(&sourceModel);
    
        w.show();
        return a.exec();
    }
    
    #include "main.moc"
    

    Notes:

    • The class provides some kind of optimization only when the filter is being narrowed down, since the newly constructed filter at the end of the chain does not need to search through all source model's rows.
    • The class depends on the user to tell if the filter is being narrowed down. That is when the user passes true for the argument narrowedDown, the filter is assumed to be a special case of the current filter (even if it is not really so). Otherwise, it behaves exactly the same as the normal QSortFilterProxyModel and possibly with some additional overhead (resulted from cleaning up the old filter chain).
    • The class can be further optimized when the filter is not being narrowed down, so that it looks in the current filter chain for a filter that is similar to the current filter and switch to it immediately (instead of deleting the whole chain and starting a new one). This can be particularly useful when the user is deleting some characters at the end filter QLineEdit (ie. when the filter changes back from "abcd" to "abc", since you should already have a filter in the chain with "abc"). But currently, this is not implemented as I want the answer to be as minimal and clear as possible.

提交回复
热议问题