1

Do You know, why does it throw

An unhandled exception of type 'System.NullReferenceException' occurred in PresentationFramework.dll

Additional information: Object reference not set to an instance of an object.

When I try to filter a CollectionViewSource that yields no valid rows?

The code is the following.

xaml:

<ComboBox SelectedItem="{Binding Item}" ItemsSource="{Binding Items}" IsSynchronizedWithCurrentItem="True" />

first code:

public class Model : INotifyPropertyChanged
    {
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
        public string Item { get; set; }
        public ICollectionView Items { get; set; }
        public Model()
        {
            Items = CollectionViewSource.GetDefaultView(new ObservableCollection<string>(new List<string> { "aaa", "bbb" }));
        }
        public void DoFirst()
        {
            Items.Filter = o => ((string)o).StartsWith("a");
        }
        public void DoSecond()
        {
            Items.Filter = o => false;
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

DoFirst() works. DoSecond() does not. Exception comes from the Items.Filter = o => false; line.

If I remove the notify property stuff, it will not throw exception, but another interesting bug happens:

second code:

public class Model
    {
        public string Item { get; set; }
        public ICollectionView Items { get; set; }
        public Model()
        {
            Items = CollectionViewSource.GetDefaultView(new ObservableCollection<string>(new List<string> { "aaa", "bbb" }));
        }
        public void DoFirst()
        {
            Items.Filter = o => ((string)o).StartsWith("a");
        }
        public void DoSecond()
        {
            Items.Filter = o => false;
        }
    }

Empty list is shown. That's right. But then, when I DoFirst() the list shows 'aaa' right, it is not selected by default. IsSynchronizedWithCurrentItem is not firing.

If I try to defend the filter from NRE third kind of behaviour happens.

third code:

public class Model : INotifyPropertyChanged
    {
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
        public string Item { get; set; }
        public ICollectionView Items { get; set; }
        public Model()
        {
            Items = CollectionViewSource.GetDefaultView(new ObservableCollection<string>(new List<string> { "aaa", "bbb" }));
        }
        public void DoFirst()
        {
            try
            {
                Items.Filter = o => ((string)o).StartsWith("a");
            } catch (NullReferenceException) { }
        }
        public void DoSecond()
        {
            try
            {
                Items.Filter = o => false;
            } catch (NullReferenceException) { }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

In that case, the selectable items in the combobox are right. After DoSecond() the list is empty, but the last selected item is still selected... After DoSecond() DoFirst() also throws NullReferenceException.

If we set the current item to null, and call an OnPropertyChanged on that, the second code's stability is reached. The IsSynchronizedWithCurrentItem's property of selecting a valid Item from the ComboBox is still lost. In the following code, if I call DoFirst(), DoThird(), then "bbb" will be selected. After setting the Item to null (call DoSecond() before), it will not select "bbb":

fourth code:

public class Model : INotifyPropertyChanged
{
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    public string Item { get; set; }

    public ICollectionView Items { get; set; }
    public Model()
    {
        Items = CollectionViewSource.GetDefaultView(new ObservableCollection<string>(new List<string> { "aaa", "bbb" }));
    }
    public void DoFirst()
    {
        Items.Filter = o => ((string)o).StartsWith("a");
    }

    public void DoSecond()
    {
        Item = null;
        OnPropertyChanged("Item");
        Items.Filter = o => false;
    }

    public void DoThird()
    {
        Items.Filter = o => ((string)o).StartsWith("b");
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Br, Márton

ntohl
  • 2,067
  • 1
  • 28
  • 32
  • [What is a `NullReferenceException` and how do I fix it?](http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Soner Gönül Jul 01 '15 at 09:30
  • The first version of the code does not use the `OnPropertyChanged` at any point. Did you remove something there? – Nitram Jul 01 '15 at 09:37
  • SonerGönül: Thanks, but I already know what is a null reference exception. The problem is not related to the post You quote. I kind of expect that the selected item will be null after filtering. But why is it happening only if the class implements INotifyPropertyChanged? @Nitram: You see correct. There is no OnPropertyChanged call. I have created a minimal example, and it doesn't need a call on OnPropertyChanged, but it still behaves differently. – ntohl Jul 02 '15 at 07:31
  • The thing is you have to track where the NRE is coming from. If it really happens in your example code, it can only be the filter (if `o` is `null`). But my best guess would be that there is a error in the code of the `Item` property (that is removed in your example) that fires the NRE if `null` is set as item. – Nitram Jul 02 '15 at 07:53
  • NRE coming from the `Items.Filter = o => false;`. And yes, most probably from the `o` is `null` case. If I write `try { Items.Filter = o => false; } catch (NullReferenceException) { }` 'aaa' remains selected, the combobox selection list is empty. Next time when I DoFirst() it will be again NRE. I haven't removed anything from `Item` property. It's simple `{ get; set; }`. – ntohl Jul 02 '15 at 08:33

1 Answers1

2

For some reason when you set IsSynchronizedWithCurrentItem to true and the source object for SelectedItem binding implements INotifyPropertyChanged, the ICollectionView does not let you explicitly set the CurrentItem to null (by, for example, calling MoveCurrentToPosition(-1), which otherwise works fine). I have a few ideas why that might be but I don't want to speculate.

The only way I've found to set the CurrentItem to null is to explicitly assing null to the property to which SelectedItem is bound (the Item property in your case) and raise the PropertyChanged event with appropriate property name. Using the debugger you'll notice that at that point CurrentItem will be set to null internally. Then you're clear to apply a filter which will yield no results.

As for your other concern, which is that after applying a filter that yields no results and then another one that yields some results IsSynchronizedWithCurrentItem stops working, it's actually not true. Again, if you use the debugger, you'll notice that after applying the second filter the CurrentItem property remains unchanged - it still yields null, so the SelectedItem is still in sync with CurrentItem. I'm afraid that in this case you'll need to select the first available item by yourself - for instance by assigning appropriate value to your Item property or by calling Items.MoveCurrentToFirst().

EDIT

In response to your comment and updated details in question - that's exactly what I was referring to in the previous paragraph (especially the last sentence). You may notice that upon applying a filter, whenever current value is still feasible, it's not automatically changed. Well, null (meaning "no current value") is always feasible, so it's never going to be automatically changed, that's why you have to do it by yourself.

As opposed to examples in your question, which are arbitrary cases with expected results (you know when you're applying an empty filter) I presume you'll end up in situation when you won't be able to tell upfront whether the filter is going to yield any results or not. The easiest (in my opinion) solution here is to write a simple, yet universal method to apply any filter to the collection:

public void SetFilter(Predicate<object> filter)
{
    if (Items.CurrentItem != null && !filter(Items.CurrentItem))
    {
        Item = null;
        OnPropertyChanged("Item");
    }
    Items.Filter = filter;
    if (Items.CurrentItem == null && !Items.IsEmpty)
        Items.MoveCurrentToFirst();
}

This will give you the following behavior:

  • When you apply a filter that preserves current item, it won't change
  • When you apply an empty filter, null will be selected
  • When you apply a non empty filter, and current item is null, first available item will be selected
Grx70
  • 10,041
  • 1
  • 40
  • 55
  • Thanks! There is a property of `IsSynchronizedWithCurrentItem`, that is currently broken. If the list looses the currently selected item, because the filter filters it out, than then there will be a `MoveCurrentToFirst()`. That is not working after an empty filter. The second code's stability is reached, but it is still not stable. – ntohl Jul 14 '15 at 08:57
  • I tried to preserve the default behavior, without much hacking. I thought it will be just call some `Refresh()` on some context. @Grx70 's wrap is a good workaround. I like the first bullet's conserving. – ntohl Jul 16 '15 at 08:20