Implementing ICollectionViewLiveShaping
Asked Answered
B

3

15

How is ICollectionViewLiveShaping implemented for the purpose of filtering? Is it something like:

public ICollectionView WorkersEmployed { get; set; }

WorkersEmployed = new CollectionViewSource { Source = GameContainer.Game.Workers }.View;

I'm not using GetDefaultView because I need multiple instances of filters on this collection. If it matters, GameContainer.Game.Workers is an ObservableCollection.

ApplyFilter(WorkersEmployed);

private void ApplyFilter(ICollectionView collectionView)
{
    collectionView.Filter = IsWorkerEmployed;
}

public bool IsWorkerEmployed(object item)
{
    Worker w = item as Worker;
    return w.EmployerID == this.ID;
}

This all works, but of course it must be manually refreshed, which is why I'm trying to use ICollectionViewLiveShaping. How does live filtering working?

Update: It appears that the only way to add a property to ICollectionViewLiveShaping's LiveFilteringProperties collection is via a string. Given that limitation, is it even possible to filter by properties in another class (Workers' EmployerID in this case)?

Is what I'm trying to do in this situation is even a viable option?

Behaviorism answered 25/7, 2013 at 17:47 Comment(0)
M
14

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;
}
Magnetohydrodynamics answered 28/7, 2013 at 9:26 Comment(3)
Is INotifyPropertyChanged the only thing that is is listening for? ie would the re-filter be triggered by INotifyCollectionChangedVoyage
@Voyage - Yes since source collection itself is an ObservableCollection, filters will be triggered again on collection changed event as well.Magnetohydrodynamics
just want to make sure we are all on the same page, if you have OC<A> and A has OC<B> Bs as a property, a ICollectionViewLiveShaping targeted at the OC<A>, would refilter if you supplied Bs as a monitored field, if say you wanted to filter on where Bs count is greater than 0Voyage
G
2

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.

Grovergroves answered 14/6, 2019 at 8:26 Comment(2)
Are you saying the performance decreases a lot with more than 100 items when using ICollectionViewLiveShaping ?Radiosurgery
I'm currently using ObservableCollection with ICollectionViewLiveShaping. Hopefully, I won't experience any performance problems.Radiosurgery
B
0

I experimented with this and it looks like it is not designed for what you (and me) want: Automatic filtering when you change filtering conditions. It filters automatically when some properties of DataGrid's item source changes, but not when filter conditions change - you must call ICollectionViewSource.Refresh manually.

Brainwork answered 20/2, 2017 at 7:59 Comment(1)
right. and if you have a ton of filters, this is sort of annoying, but not the worst.Chortle

© 2022 - 2024 — McMap. All rights reserved.