QSortFilterProxyModel filterAcceptsRow being called unexpectedly
-
I have AbstractItemModel( Model A) that I want to filter based off some string filter parameter. To do so I have a QSortFilterProxyModel(Model B) that uses Model A as its source and filters it. When Model B is presented in the UI this it gets filtered further into Selected/Not selected proxy models which use Model B as their source. If the Delegate in the UI gets selected this item gets removed from a QSortFilterProxyModel(Model C) and added to another QSortFilterProxyModel( Model D).
My problem is this, when I am changing the filter string in Model B to a parameter that has many elements that satisfy the filter...this operation can be pretty slow(takes around 1 second to transition and update UI). This is because I am re-evaluating the filter through the entire list every time the filter parameter changes.
Since my filter parameters for Model B are known at boot I figured I could just create a QSortFilterProxyModel for each parameter and switch which proxy model Model C and Model D use as a source when the user changes the filter parameter. What I have done here is basically converted Model B into a QIdentityProxyModel (Model E) that has a QMap of <filterParam, QSortFilterProxyModel> and every time the user chooses another filter parameter, Model E grabs the appropriate QSortFitlerProxyModel(basically Model B) from its QMap and calls setSourceModel() with the appropriate Model B. The problem is that every time I do this, the Model B that I choose is re-evaluating its filter and actually taking the same amount of time that it did before I made the change. Is there a reason that Model B is going through and re-evaluating its filter? I have DynamicSortFilter set to false in the constructor of every Model B that gets created.
Another question I have is what could be making this filtering so slow? There really aren't that many elements in the list(~300).
DISCLAIMER: I am using an OLD version of Qt(4.8) in support of a legacy project.
I know I am missing some QT thing here but am at a loss. Any help would be greatly appreciated! Thanks
-
Hi,
Since model A is a custom model, I would start by benchmarking it. Is the data method implemented efficiently ?
Then, for your multiple proxy models, don't call setSourceModel every time. A model can have several proxy on top of it in the same fashion you can have several views looking at the same model. Switch the proxy used by the view.
-
Hi,
Since model A is a custom model, I would start by benchmarking it. Is the data method implemented efficiently ?
Then, for your multiple proxy models, don't call setSourceModel every time. A model can have several proxy on top of it in the same fashion you can have several views looking at the same model. Switch the proxy used by the view.
thanks for the response!
What would an inefficient data method look like? I basically just look at the role and return the appropriate data from the data source. My situation is a little different since my AbstractItemModel's Data() function calls out to a custom list object's data function:
QVariant CustomModel::data(const QModelIndex &index, int role) const { if(m_list != NULL) { if (index.row()<0 || index.row() >= m_list->items().size()) return QVariant(); return m_list->data(index.row(), role); } }
The list object's data function looks something like this:
QVariant CustomList::data(int index, int role) { QVariantMap entry = items_.at(index); switch (role) { case myRole1: return QVariant::fromValue(entry["myRole1"]); case myRole2: return QVariant::fromValue(entry["myRole2"]); default: // should be unreachable code return QVariant(); } }
The underlying datastore in the CustomList is a QList of QVariantMaps.
Going off of your second point:
I should have mentioned that I am using QML and using setContextProperty to allow qml to reference Model C and Model D. Is there a way I can change what the context QML sees without changing the way it is referenced in qml(does that question make sense? )? This is why I was using the QIdentityProxyModel. By setting up Model C and Model D to have Model E as a source then I can leave everything the same but just change what model Model E presents to the view. -
Your are accessing
items_
quite a lot of time for nothing. The data method is called many times because there are lots of roles. Since you only return data for Role1 and Role2, ensure first that it's one of them and then load the data. -
Your are accessing
items_
quite a lot of time for nothing. The data method is called many times because there are lots of roles. Since you only return data for Role1 and Role2, ensure first that it's one of them and then load the data.are you basically saying that I should change it to something like this?
QVariant CustomList::data(int index, int role) { QVariantMap entry; switch (role) { case myRole1: entry = items_.at(index); return QVariant::fromValue(entry["myRole1"]); case myRole2: entry = items_.at(index); return QVariant::fromValue(entry["myRole2"]); default: // should be unreachable code return QVariant(); } }
-
are you basically saying that I should change it to something like this?
QVariant CustomList::data(int index, int role) { QVariantMap entry; switch (role) { case myRole1: entry = items_.at(index); return QVariant::fromValue(entry["myRole1"]); case myRole2: entry = items_.at(index); return QVariant::fromValue(entry["myRole2"]); default: // should be unreachable code return QVariant(); } }
-
@wagner2x
Is that a question or an observation? Yes indeed it does, many more. Basically for most of the defined ones (what font? what color? what alignment?). @SGaist is suggesting therefore you only evaluateitems_.at(index)
when absolutely necessary.If you were to have a lot of roles required in the
switch
statement one could factor out theitems_.at(index)
retrieval to one place if you wish and some common expression forQVariant::fromValue(entry["myRole..."])
to cut down on code if that's what you mean. -
@wagner2x
Is that a question or an observation? Yes indeed it does, many more. Basically for most of the defined ones (what font? what color? what alignment?). @SGaist is suggesting therefore you only evaluateitems_.at(index)
when absolutely necessary.If you were to have a lot of roles required in the
switch
statement one could factor out theitems_.at(index)
retrieval to one place if you wish and some common expression forQVariant::fromValue(entry["myRole..."])
to cut down on code if that's what you mean.That was question :P I should have realized that! That makes sense. I made that minor change but still am seeing significant delays in evaluating the filter after the source model has changed.
In the original post I described Model C and Model D as QSortFilterProxyModels which basically are to distinguish Items that have been selected or not. After I set the new source, Model C and Model D both reevaluate their filter (which makes sense) but if there are many elements that pass the filter, this operations is taking a while. Any ideas why that may be? Like I meantioned...there are only 300 items in the list total.
Here is my filterAcceptsRow Function...It seems pretty straight forward but I could be doing something wrong:
bool UnselectedProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { QModelIndex itemIndex = sourceModel()->index(source_row, 0, source_parent); if(itemIndex.isValid() && !source_parent.isValid()) { return !itemIndex.data(selected).toBool(); } return false; }
Remember this takes Model E (a QIdentityProxyModel) as its source where the source of Model E is swapped in and out by user selections on the filter.
-
That was question :P I should have realized that! That makes sense. I made that minor change but still am seeing significant delays in evaluating the filter after the source model has changed.
In the original post I described Model C and Model D as QSortFilterProxyModels which basically are to distinguish Items that have been selected or not. After I set the new source, Model C and Model D both reevaluate their filter (which makes sense) but if there are many elements that pass the filter, this operations is taking a while. Any ideas why that may be? Like I meantioned...there are only 300 items in the list total.
Here is my filterAcceptsRow Function...It seems pretty straight forward but I could be doing something wrong:
bool UnselectedProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { QModelIndex itemIndex = sourceModel()->index(source_row, 0, source_parent); if(itemIndex.isValid() && !source_parent.isValid()) { return !itemIndex.data(selected).toBool(); } return false; }
Remember this takes Model E (a QIdentityProxyModel) as its source where the source of Model E is swapped in and out by user selections on the filter.
-
How does Lazy Loading work if you have a chain of models like I have? Do I need to reimplement canFetchmore and FetchMore for all of the models in the chain? Also, any ideas on what could make a ListView QML widget take forever to draw when its model gets switched?
-
I tried implementing canFetchMore and fetchMore on Models C and D but am seeing a QT error
QDeclarativeComponent: Cannot create new component instance before completing the previous
<Unknown File>: QML VisualDataModel: Error creating delegateI am trying to batch 10 delegates at a time:
bool UnselectedModel::canFetchMore(const QModelIndex &parent) const { if (parent.isValid()) return false; //I should be able to use rowCount here right? This should be executed after filteracceptsRow is finished? return (itemsFetched_ < rowCount()); } void UnselectedModel::fetchMore(const QModelIndex &parent) { if (parent.isValid()) return; int remainder = rowCount() - itemsFetched_; int itemsToFetch = qMin(10, remainder); if (itemsToFetch <= 0) return; beginInsertRows(QModelIndex(), itemsFetched_, itemsFetched_ + itemsToFetch - 1); itemsFetched_ += itemsToFetch; endInsertRows(); emit numberPopulated(itemsToFetch); }
I also clear the itemsFetched_ member every time we make a sourceModel change on Model E. Do I need to reset Model C and D when the source model changes if I am implementing fetchMore?
-
Can you provide a minimal compilable example of your code ?
That would help take a better look at your situation ?