Automatically refresh ICollectionView Filter
Asked Answered
M

2

19

Is there any way to automatically update a filter on an ICollectionView without having to call Refresh() when a relevant change has been made?

I have the following:

[Notify]
public ICollectionView Workers { get; set; }

The [Notify] attribute in this property just implements INotifyPropertyChanged but it doesn't seem to be doing anything in this situation.

Workers = new CollectionViewSource { Source = DataManager.Data.Workers }.View;

Workers.Filter = w =>
    {
        Worker worker = w as Worker;
        if (w == null)
            return false;
        return worker.Employer == this;
    };

In XAML:

<TextBlock x:Name="WorkersTextBlock"
           DataContext="{Binding PlayerGuild}"
           FontFamily="Pericles"
           Text="{Binding Workers.Count,
                          StringFormat=Workers : {0},
                          FallbackValue=Workers : 99}" />

Update: It looks like using ICollectionView is going to be necessary for me, so I'd like to revisit this topic. I'm adding a bounty to this question, the recipient of which will be any person who can provide some insight on how to implement a 'hands-off' ICollectionView that doesn't need to be manually refreshed. At this point I'm open to any ideas.

Maleficence answered 18/7, 2013 at 23:59 Comment(7)
Well, considering that attribute is not BCL, it isn't surprising that it doesn't cover this corner case. In fact, there isn't anything in WPF that covers this case, so you'll have to do it yourself. The filter property isn't a DP, and even it was, nothing in the filter would trigger an update event. Looks like you will have to trigger it manually. Perhaps in the body of the filter? You can self reference via the closure. Looking at the lambda, you'd have to offload the call to Refresh to the Dispatcher, so it would run after the filter is applied. Ergh, and add a bool "brb updating" blockColp
(no more chars) looks like there isn't any way around it, except for maybe extending CVS and updating during a filter, with checks to make sure you don't continually trigger an update. Makes sense why that isn't BCL, right? Filter triggers an update, which triggers a filter, and on and on...Colp
Thank you for the explanation. It looks like I'll be doing this manually.Maleficence
Well, hold out hope. Am going by history and my experience. If it isn't already wired in, then you have to wire it yourself, normally. And, if you do, add the answer below and give yourself some props! And, if you mange, I'll bump you +1 again.Colp
I'm fairly new to programming and at this point I wouldn't have a clue how to pull this off with the limited bit of knowledge I have. Someday, maybe, but not quite yet.Maleficence
@JasonD, could you please describe a bit what you trying to achieve with this code and may be there is another solution rather then using CollectionViewSource and community would suggest it instead?Crites
I'm just trying to filter an ObservableCollection based on certain conditions and bind the filtered results to the UI without having to manually refresh the collection each time changes are made. I don't really know how else to explain it. I've tried using LINQ but couldn't get it to work in this scenario. If there's another way to do this, I'd love to know what it is.Maleficence
A
22

AFAIK there is no inbuilt support in ICollectionView to refresh collection on any property change in underlying source collection.

But you can subclass ListCollectionView to give it your own implementation to refresh collection on any property changed. Sample -

public class MyCollectionView : ListCollectionView
{
    public MyCollectionView(IList sourceCollection) : base(sourceCollection)
    {
        foreach (var item in sourceCollection)
        {
            if (item is INotifyPropertyChanged)
            {
                ((INotifyPropertyChanged)item).PropertyChanged +=
                                                  (s, e) => Refresh();
            }
        }
    }
}

You can use this in your project like this -

Workers = new MyCollectionView(DataManager.Data.Workers);

This can be reused across your project without having to worry to refresh collection on every PropertyChanged. MyCollectionView will do that automatically for you.

OR

If you are using .Net4.5 you can go with ICollectionViewLiveShaping implementation as described here.

I have posted the implementation part for your problem here - Implementing ICollectionViewLiveShaping.

Working code from that post -

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;
}
Adorno answered 28/7, 2013 at 8:34 Comment(4)
Well, sort of. When a new item is added to the collection, it's not handled properly since the logic is in the constructor. I'll play around with this a bit and see what I can do.Maleficence
Actually, this implementation of ICollectionViewLiveShaping works as well. I don't know what I did wrong at first when I tried your answer. Thanks again!Maleficence
How to listen for nested properties?Kristiekristien
FYI: you don't need to expose ICollectionViewLiveShaping you can just keep ICollectionView and it will still work. Just cast to it and set the properties and it will work.Inhale
T
12

For .Net 4.5: There is a new interface which can help to achieve this feature, called : ICollectionViewLiveShaping.

From MSDN link:

When live sorting, grouping, or filtering is enabled, a CollectionView will rearrange the position of data in the CollectionView when the data is modified. For example, suppose that an application uses a DataGrid to list stocks in a stock market and the stocks are sorted by stock value. If live sorting is enabled on the stocks' CollectionView, a stock's position in the DataGrid moves when the value of the stock becomes greater or less than another stock's value.

More Info on above interface: http://www.jonathanantoine.com/2011/10/05/wpf-4-5-%E2%80%93-part-10-live-shaping/


For .Net 4 and lower: There is also another post on SO QA which might help you: CollectionViewSource Filter not refreshed when Source is changed

Tapeworm answered 25/7, 2013 at 7:23 Comment(1)
If you can help me get this working, the bounty will certainly go to you. I've posted a question about implementing ICollectionViewLiveShaping here: #17865702Maleficence

© 2022 - 2024 — McMap. All rights reserved.