I am working on a WPF desktop application using the MVVM pattern.
I am trying to filter some items out of a ListView
based on the text typed in a TextBox
. I want the ListView
items to be filtered as I change the text.
I want to know how to trigger the filter when the filter text changes.
The ListView
binds to a CollectionViewSource
, which binds to the ObservableCollection
on my ViewModel. The TextBox
for the filter text binds to a string on the ViewModel, with UpdateSourceTrigger=PropertyChanged
, as it should be.
<CollectionViewSource x:Key="ProjectsCollection"
Source="{Binding Path=AllProjects}"
Filter="CollectionViewSource_Filter" />
<TextBox Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" />
<ListView DataContext="{StaticResource ProjectsCollection}"
ItemsSource="{Binding}" />
The Filter="CollectionViewSource_Filter"
links to an event handler in the code behind, which simply calls a filter method on the ViewModel.
Filtering is done when the value of FilterText changes - the setter for the FilterText property calls a FilterList method that iterates over the ObservableCollection
in my ViewModel and sets a boolean
FilteredOut property on each item ViewModel.
I know the FilteredOut property is updated when the filter text changes, but the List does not refresh. The CollectionViewSource
filter event is only fired when I reload the UserControl by switching away from it and back again.
I've tried calling OnPropertyChanged("AllProjects")
after updating the filter info, but it did not solve my problem.
("AllProjects" is the ObservableCollection
property on my ViewModel to which the CollectionViewSource
binds.)
How can I get the CollectionViewSource
to refilter itself when the value of the FilterText TextBox
changes?
Many thanks
If I understood well what you are asking:
In the set part of your
FilterText
property just callRefresh()
to yourCollectionView
.CollectionViewSource.Filter is reevaluated in this way!
I just discovered a much more elegant solution to this issue. Instead of creating a
ICollectionView
in your ViewModel (as the accepted answer suggests) and setting your binding toThe better way is to create a
CollectionViewSource
property in your ViewModel. Then bind yourItemsSource
as followsNotice the addition of .View This way the
ItemsSource
binding is still notified whenever there is a change to theCollectionViewSource
and you never have to manually callRefresh()
on theICollectionView
Note: I can't determine why this is the case. If you bind directly to a
CollectionViewSource
property the binding fails. However, if you define aCollectionViewSource
in yourResources
element of a XAML file and you bind directly to the resource key, the binding works fine. The only thing I can guess is that when you do it completely in XAML it knows you really want to bind to the CollectionViewSource.View value and binds it for you acourdingly behind the scenes (how helpful! :/) .Perhaps you've simplified your View in your question, but as written, you don't really need a CollectionViewSource - you can bind to a filtered list directly in your ViewModel (mItemsToFilter is the collection that is being filtered, probably "AllProjects" in your example):
Your View would then simply be:
Some quick notes:
This eliminates the event in the code behind
It also eliminates the "FilterOut" property, which is an artificial, GUI-only property and thus really breaks MVVM. Unless you plan to serialize this, I wouldn't want it in my ViewModel, and certainly not in my Model.
In my example, I use a "Filter In" rather than a "Filter Out". It seems more logical to me (in most cases) that the filter I am applying are things I do want to see. If you really want to filter things out, just negate the Contains clause (i.e. item => ! Item.Text.Contains(...)).
You may have a more centralized way of doing your Sets in your ViewModel. The important thing to remember is that when you change the FilterText, you also need to notify your AllFilteredItems collection. I did it inline here, but you could also handle the PropertyChanged event and call PropertyChanged when the e.PropertyName is FilterText.
Please let me know if you need any clarifications.
Don't create a
CollectionViewSource
in your view. Instead, create a property of typeICollectionView
in your view model and bindListView.ItemsSource
to it.Once you've done this, you can put logic in the
FilterText
property's setter that callsRefresh()
on theICollectionView
whenever the user changes it.You'll find that this also simplifies the problem of sorting: you can build the sorting logic into the view model and then expose commands that the view can use.
EDIT
Here's a pretty straightforward demo of dynamic sorting and filtering of a collection view using MVVM. This demo doesn't implement
FilterText
, but once you understand how it all works, you shouldn't have any difficulty implementing aFilterText
property and a predicate that uses that property instead of the hard-coded filter that it's using now.(Note also that the view model classes here don't implement property-change notification. That's just to keep the code simple: as nothing in this demo actually changes property values, it doesn't need property-change notification.)
First a class for your items:
Now, a view model for the application. There are three things going on here: first, it creates and populates its own
ICollectionView
; second, it exposes anApplicationCommand
(see below) that the view will use to execute sorting and filtering commands, and finally, it implements anExecute
method that sorts or filters the view:Sorting kind of sucks; you need to implement an
IComparer
:To trigger the
Execute
method in the view model, this uses anApplicationCommand
class, which is a simple implementation ofICommand
that routes theCommandParameter
on buttons in the view to the view model'sExecute
method. I implemented it this way because I didn't want to create a bunch ofRelayCommand
properties in the application view model, and I wanted to keep all the sorting/filtering in one method so that it was easy to see how it's done.Finally, here's the
MainWindow
for the application:Nowadays, you often don't need to explicitly trigger refreshes.
CollectionViewSource
implementsICollectionViewLiveShaping
which updates automatically ifIsLiveFilteringRequested
is true, based upon the fields in itsLiveFilteringProperties
collection.An example in XAML: