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

Posted

技术标签:

【中文标题】如果过滤器变得严格变窄,请避免对 QSortFilterProxyModel::filterAcceptsRow() 进行冗余调用【英文标题】:Avoid redundant calls to QSortFilterProxyModel::filterAcceptsRow() if the filter has become strictly narrower 【发布时间】:2016-09-09 15:05:29 【问题描述】:

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

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

这是来自 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,则应在所有源行上调用过滤器,因为它已严格变窄。

【问题讨论】:

filterAcceptsRow( ) 是虚拟的,因此您可以重新实现它并调用 QSortFilterProxyModel::invalidateFilter() @deW1 谢谢,但我不确定这是否解决了我的问题。我更新了我的问题,以更清楚地说明我在追求什么。 不确定这是否有帮助,但this 问题建议链接QSortFilterProxyModels。但我认为应该有更好的方法来解决这个问题。 @JohannesSchaub-litb 如果您可以通过派生来指定行为,则没有理由对其进行修补(您也可以解决许可问题) 【参考方案1】:

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

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

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

#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()constreturn m_filterRegExp;
    int filterKeyColumn()constreturn m_filterKeyColumn;
    Qt::CaseSensitivity filterCaseSensitivity()constreturn m_filterCaseSensitivity;
    int filterRole()constreturn 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"

注意事项:

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

【讨论】:

谢谢,迈克。我奖励你赏金,因为我认为这是解决问题的正确方法。 Hyat 的解决方案也消除了大部分开销,并且非常适合实际用途,但它并不完整,因为它没有消除对filterAcceptsRows() 的冗余调用,它只是优化了它们。我认为使用链式QSortFilterProxyModel 不是问题 - 它使访问元素慢 N 倍,但这仅影响当前显示的元素,这些元素不是很大。我也喜欢进一步重用链式模型作为退格键的可能性。 嗯,这很奇怪。我尝试使用普通的QSortFilterProxyModel,随着它变窄,它变得更快。不仅如此,删除字符也很快,因此它们必须使用具有“撤消”功能的类似过滤器链接。那是出乎意料的。我认为现在只有当我们为自定义过滤器覆盖filterAcceptsRows(),然后我们使用invalidateFilter() 时才会出现问题。我在github.com/sashoalm/NarrowableFilterProxyModel做了一个测试项目。 其实我错了,它不做链接,加速完全不相关,对不起。 @sashoalm,没关系。为什么你注意到的加速会发生呢? 我相信这是因为填充列表框的项目较少。填充列表框本身很慢,而不是过滤行为。如果您粘贴更具体的过滤器,例如 1111,它会一样快。【参考方案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。

您还需要了解底层模型的变化并连接适当的插槽并在这些情况下使您的“缓存”无效。

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

【讨论】:

嗯,如果这一切都在filterAcceptsRow的代码中,它怎么可能对从0迭代到行数的qt基类中的二次时间复杂度产生影响? 基类中的迭代刚刚超过 indexdes 并将它们输入到filterAcceptsRow 所以是的,这些迭代仍然存在。但是您不再检查模型中所有元素的任何字符串比较,而只检查子集。根据您设置selectionNarrowed()filteredElements 的方式,这可能比检查每个字符串是否仍然匹配过滤器要快。因此,您无需修补 Qt 源代码并遇到免费许可问题即可获得性能提升。

以上是关于如果过滤器变得严格变窄,请避免对 QSortFilterProxyModel::filterAcceptsRow() 进行冗余调用的主要内容,如果未能解决你的问题,请参考以下文章

基于案例学数据挖掘项目实战

开发了一个让游戏变得卡顿的OpenWrt路由器插件

怎样才能尽量避免archlinux滚挂

mysql sql优化和sql执行计划

从 int 中获取字节以避免位移乐趣 - Java(中值过滤)

如果未登录,如何制作过滤器以避免访问页面?