如果过滤器严格变窄,则避免对QSortFilterProxyModel :: filterAcceptsRow()进行冗余调用

时间:2016-09-09 15:05:30

标签: c++ qt qsortfilterproxymodel

是否有任何方法使QSortFilterProxyModel中的过滤器无效,但是为了表明过滤器已缩小,因此应仅在当前可见的行上调用filterAcceptsRow()

目前Qt不这样做。当我调用QSortFilterProxyModel::invalidateFilter(),并且我的过滤器从“abcd”更改为“abcde”时,会创建一个全新的映射,并在所有源行上调用filterAcceptsRow(),即使很明显源行也是如此到目前为止隐藏的内容将保持隐藏状态。

这是来自QSortFilterProxyModelPrivate::create_mapping()的Qt来源的代码,它调用我的重写filterAcceptsRow(),并创建一个全新的Mapping并迭代所有源行:

Mapping *m = new Mapping;

int source_rows = model->rowCount(source_parent);
m->source_rows.reserve(source_rows);
for (int i = 0; i < source_rows; ++i) {
    if (q->filterAcceptsRow(i, source_parent))
        m->source_rows.append(i);
}

我想要的只是迭代映射中的可见行,并仅在它们上调用filterAcceptsRow()。如果已经隐藏了一行filterAcceptsRow(),则不应该对其进行调用,因为我们已经知道它会为它返回false(过滤器变得更加严格,它没有被松开)。

由于我已经覆盖filterAcceptsRow(),Qt无法知道过滤器的性质,但是当我调用QSortFilterProxyModel::invalidateFilter()时,我有关于过滤器是否变得严格变窄的信息,所以我可以如果它有办法接受它,就把这些信息传递给Qt。

另一方面,如果我已将过滤器从abcd更改为abce,则应在所有源行上调用过滤器,因为它已变得非常窄。

2 个答案:

答案 0 :(得分:6)

我写了一个QIdentityProxyModel子类,它存储了一个链式QSortFilterProxyModel列表。它提供了一个类似于QSortFilterProxyModel的接口,并接受一个narrowedDown布尔参数,该参数指示过滤器是否正在缩小。那样:

  • 当缩小过滤器时,新的QSortFilterProxyModel会附加到链中,而QIdentityProxyModel会切换到代理链末尾的新过滤器。
  • 否则,它会删除链中的所有过滤器,使用一个与当前过滤条件对应的过滤器构造一个新链。之后,QIdentityProxyModel切换为代理链中的新过滤器。

这是一个程序,它将类与使用普通QSortFilterProxyModel子类进行比较:

Demo program screenshot

#include <QtWidgets>

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<FilterProxyModel*> 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<QStandardItem*> 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"

注意:

  • 只有当过滤器缩小时,类才会提供某种优化,因为链末端新构建的过滤器不需要搜索所有源模型&#39; s行。
  • 该类取决于用户判断过滤器是否缩小。也就是说,当用户为参数true传递narrowedDown时,假定过滤器是当前过滤器的特殊情况(即使实际上并非如此)。否则,它的行为与正常的QSortFilterProxyModel完全相同,并且可能会产生一些额外的开销(因清理旧的过滤器链而产生)。
  • 当过滤器没有缩小时,可以进一步优化类,以便它在当前过滤器链中查找与当前过滤器类似的过滤器并立即切换到它(而不是删除整个链和开始一个新的)。当用户删除末尾过滤器QLineEdit处的某些字符时(例如,当过滤器从"abcd"更改为"abc"时,这可能特别有用,因为您应该已经有一个过滤器与"abc")的链。但目前,这还没有实现,因为我希望答案尽可能简洁明了。

答案 1 :(得分:2)

因为过滤器也可以是通用的(对于自定义过滤器排序,建议您覆盖filterAcceptsRow()),ProxyModel无法知道它是否会变窄。

如果您需要将它作为参数提供给代理,它将破坏封装,因为过滤器逻辑应该只包含在过滤器模型中。

您无法覆盖invalidateFilter,因为它未声明为虚拟。你可以做的是在你的派生代理中有一个结构,你在那里存储你最后过滤的值,并且当过滤器变得更窄时,只检查它们。你可以在filterAcceptsRow()中完成这两项工作。

invalidateFilter()仍然会调用rowCount()。因此,此功能需要在模型中具有较低的调用时间才能使其生效。

以下是一些伪代码filterAcceptsRow()的样子:

index // some index to refer to the element;

if(!selectionNarrowed()) //need search all elements
{
    m_filteredElements.clear(); //remove all previously filtered
    if(filterApplies(getFromSource(index))) //get element from sourceModel
    {
        m_filteredElements.add(index); //if applies add to "cache"
        return true;
    }
    return false;
}

//selection has only narrowed down    
if(!filterApplies(m_filteredElements(index)) //is in "cache"?
{
    m_filteredElements.remove(index); //if not anymore: remove from cache
    return false;
}
return true;

但有些事情需要注意。如果您想存储QModelIndex,请务必小心..您可以查看QPersistentModelIndex

您还需要了解底层模型的更改并连接相应的插槽,并在这些情况下使“缓存”无效。

虽然替代方案可能是“过滤堆叠”。我发现当你真的需要使所有过滤器无效时,这可能会让人感到困惑。