Implementing ICollectionViewLiveShaping
All you need to do is add a property
in LiveFilteringProperties
for which you want the filter to call on property change and set IsLiveFiltering
to true
for your collection to enable live filtering
.
Make sure PropertyChanged
event gets raised whenever EmployerID
property changes i.e. your Worker
class should implement INotifyPropertyChangedEvent
.
This will work then -
public ICollectionViewLiveShaping WorkersEmployed { get; set; }
ICollectionView workersCV = new CollectionViewSource
{ Source = GameContainer.Game.Workers }.View;
ApplyFilter(workersCV);
WorkersEmployed = workersCV as ICollectionViewLiveShaping;
if (WorkersEmployed.CanChangeLiveFiltering)
{
WorkersEmployed.LiveFilteringProperties.Add("EmployerID");
WorkersEmployed.IsLiveFiltering = true;
}
We are using WPF + MVVM + Visual Studio 2017.
We want to convert this to add live filtering:
public ObservableCollection<RowViewModel> Rows { get; set; }
The method below has two key advantages:
- It's designed to work efficiently with the WPF runtime to minimise on-screen rendering using bulk updates.
- So it's fast.
- And because the boilerplate code is listed below, it's easier to follow compared to any other docs you will find on the web.
Please let me know if this worked for you, any issues and I'll update the instructions to make easier.
And the steps:
Step 1: Non-notifying Collection Wrapper
Create a special ObservableCollection that does not fire update events. This is a one-off. We want to fire the update bulk update event ourselves, which is faster.
public class NonNotifyingObservableCollection<T> : ObservableCollection<T>
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { /* Do nothing */ }
}
Step 2: Convert to NonNotifyingObservableCollection
Convert to a private variable which uses this new collection.
private NonNotifyingObservableCollection<RowViewModel> rows;
// ... and in constructor
rows = new NonNotifyingObservableCollection<RowViewModel>();
Step 3: Add Wrapper
Add these variables:
private ICollectionView rowsView;
public ICollectionViewLiveShaping RowsLiveView { get; set; }
And in the Initialise() call after the ViewModel is constructed (or perhaps in the constructor):
// Call on the dispatcher.
dispatcher.InvokeAsync(() =>
{
this.rowsView = CollectionViewSource.GetDefaultView(this.rows);
this.rowsView.Filter = o =>
{
// This condition must be true for the row to be visible on the grid.
return ((RowViewModel)o).IsVisible == true;
};
this.RowsLiveView = (ICollectionViewLiveShaping)this.rowsView;
this.RowsLiveView.IsLiveFiltering = true;
// For completeness. Changing these properties fires a change notification (although
// we bypass this and manually call a bulk update using Refresh() for speed).
this.RowsLiveView.LiveFilteringProperties.Add("IsVisible");
});
Step 4: Add items
Now we add items to the backing collection, then call .Refresh()
to refresh the view:
this.rowsView.Add(new RowViewModel( /* Set properties here. */ ));
We then bind the grid to RowsLiveView
, (instead of binding to Rows
in the original code).
Step 5: Update live filtering
Now we can update the IsVisible
property, then call .Refresh()
to redraw the grid.
rows[0].IsVisible=false;
this.rowsView.Refresh(); // Hides the first row.
Update
Update: This answer could be simplified. The whole point of ICollectionViewLiveShaping
is to autorefresh without the need to call .Refresh()
. Given that we have a NonNotifyingObservableCollection
and we are manually controlling everything with a .Refresh()
, could remove public ICollectionViewLiveShaping RowsLiveView { get; set; }
and, directly to RowsView
(make it a property with { get; set; }
, and use normal ObservableCollection<>
. In other words - ICollectionViewLiveShaping is great for a small amount of rows (e.g. <100), but for anything more, ICollectionView
in combination with a bulk update and a manual Refresh()
is better from a speed point of view.