.NET (WPF) CollectionViews and Filtering



  • I am in a weird situation.

    I created some code earlier today and everything worked fine, then I did some other stuff and now the code doesn't work.

    The thing I am confused by is the fact that everything that isn't happening should be taken care of by the framework and I shouldn't be doing anything.

    So, I have a collection that exists like so (note this is using BindableBase from PRISM, which is basically just handling INotifyPropertyChanged):

    private ObservableCollection<Thing> _things;
    
        public ObservableCollection<Thing> AllThings
        {
            get
            {
                return _things; 
            }
            set 
            { 
                SetProperty<ObservableCollection<Thing>>(ref _things, value);
                SubsetOfThings.Refresh();
                OnPropertyChanged("SubsetOfThings");
            }
        }
    

    Next, I have a View of this set up as SubsetOfThings (as hinted by the setter above) which just gives me some others based on a filter.

    private CollectionViewSource _subsetOfThings;
    
        public ICollectionView SubsetOfThings 
        {
            get 
            {
                if(_subsetOfThings== null)
                {
                    _subsetOfThings= new CollectionViewSource();
                    _subsetOfThings.Source = AllThings;
                    _subsetOfThings.View.Filter = item =>
                    {
                        var thing= item as Thing;
                        if (thing== null) return false;
                        return (thing.prop == null || thing.nav.prop == "None");
                    };
                }
                _subsetOfThings.View.Refresh();                
                return _subsetOfThings.View;
            }
        }
    

    I have then bound to the SubsetOfThings property in a viewmodel in various ways. Everything appears to be set up correctly, but the Filter method is never called for some reason, and I just get an empty collection when called and I can't see any changes in my code that would cause it to happen, but there must be something.

    So I'm wondering if anyone knows any circumstances under which the filter is not called, or how I can resolve this?



  • Figured it out.

    It turns out that if you replace the source after creation stuff goes wrong.

    I populate the source at startup in another thread and then replace it wholesale, in doing so overwriting the source. This would be fine but the source is bound before it happens, I'm guessing it wasn't originally, not sure what I've changed there, possibly something in MEF has changed the loading order, anyway the long and short is I was losing the reference to the collection, and somehow this also invalidated the reference to the filter.



  • @algorythmics said:

    Figured it out.

    INB4 Coding Help fails again!


    Filed Under: Should have used jQuery



  • If you're using PRISM, you shouldn't need to OnPropertyChanged(string), because it should have some kind of [CallerMemberName] override in the base class, or at the very least an expression-based overload.

    Even if it doesn't, SetProperty should notify...



  • @algorythmics said:

    I populate the source at startup in another thread and then replace it wholesale, in doing so overwriting the source. This would be fine but the source is bound before it happens, I'm guessing it wasn't originally, not sure what I've changed there, possibly something in MEF has changed the loading order, anyway the long and short is I was losing the reference to the collection, and somehow this also invalidated the reference to the filter.

    One of my favorite things about MEF is how it was built by the makers of PRISM, who later said (approximately), 'you know, you can do most of what PRISM does with just MEF. the whole region bit and event aggregator especially'



  • It is my experience that re-binding collection sources at runtime in a WPF/MVVM context is... tricky. When implementing filtering, I tend to expose an ObservableCollection of FilteredItems in the view model and rebuild it (using standard collection manipulation methods) whenever filtering is to be applied.

    In practice this means that the view model usually has some reference to the main data source (typically an ICollection<T>), an ObservableCollection<T> exposed as a simple get property and some kind of filtering method whose purpose is to populate the ObservableCollection from the main data store, depending on filter settings .

    This setup allows me to do whatever I please with the main data source (as long as I maintain a valid reference) and apply any kind of filtering required whilst ensuring that the ObservableCollection remains firmly bound to the view from the moment the data context is established.


  • sockdevs

    The only way I've found to rebind a datasource that's even close to reliable is to set the datasource property to null before setting it to the right thing.

    Of course it should really be done right through the classes designed for it :)



  • One thing I really like about WPF is that if you bind to {Binding Property.Collection}, if Property notifies when it's changed, the control gets the new collection instantly. This might not be what everyone is talking about, of course.



  • I've found that if I didn't have that, when I updated a record that changed the filter count, the filtered version just didn't update visually until I did that.

    It works for everything, but it still can't detect for calculated values that they are being updated unless BindableBase has features that I am missing



  • So, normally in the base class, I have these, even if I have to roll my own:

    protected virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
      var memberExpression = propertyExpression.Body as MemberExpression;
      if(memberExpression == null)
        throw new ArgumentException(@"The expression provided is not a valid member expression.", "propertyExpression");
    
      var propertyInfo = memberExpression.Member as PropertyInfo;
      if (propertyInfo == null)
        throw new ArgumentException(@"The member expression provided does not identify a property.", "propertyExpression");
    
      RaisePropertyChanged(propertyInfo.Name);
    }
    
    protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
      var handler = PropertyChanged;
      if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName));
    }
    

    If some calculation changes a get only property I bind to, I RaisePropertyChanged(() => ThatProperty);

    I believe that PRISM includes that second method, and both will become slightly less useful soon with the introduction of nameof()



  • @RaceProUK said:

    The only way I've found to rebind a datasource that's even close to reliable is to set the datasource property to null before setting it to the right thing.

    Apparently, this resets the ItemsSource to use Items (the default empty ItemCollection) which would probably ease the transition.

    @Magus said:

    if you bind to {Binding Property.Collection}, if Property notifies when it's changed, the control gets the new collection instantly.

    That sounds pretty cool. I've got to give it at try one of these days.



  • In my game solution, I also have my editor project, which I'm building as a WPF app. I end up with lots of cases where I have a listbox on the left, where the whole right side is bound to properties of it's selected item. In one screen, this is about four layers deep (i hate that screen), and the result is very good: you can select an item at the top level, next level, then reselect on the top level, and all you get is all the new objects in the list, with no effort.

    I feel like I should post some of my source here some time. After all, I'm never releasing my editor. And parts of it probably belong on this site.



  • For such scenarios, I tend to use the kind of collection rebuilding I mentioned above, usually combined with LINQ in the view model. However, your method intrigues me.


Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.