过滤CollectionViewSource产生空结果将抛出异常

时间:2015-07-01 09:29:36

标签: c# wpf filter inotifypropertychanged collectionviewsource

你知道吗,为什么会抛出

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

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

当我尝试过滤不产生有效行的CollectionViewSource时?

代码如下。

XAML:

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

第一个代码:

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 ()有效。 DoSecond ()没有。例外来自Items.Filter = o => false;行。

如果我删除了notify属性,它不会抛出异常,但会发生另一个有趣的错误:

第二个代码:

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;
        }
    }

显示空列表。那就对了。但是,当我 DoFirst ()时,列表显示'aaa'正确,默认情况下不会选中它。 IsSynchronizedWithCurrentItem未触发。

如果我试图从NRE中保护过滤器,则会发生第三种行为。

第三个代码:

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;
    }

在这种情况下,组合框中的可选项是正确的。在 DoSecond ()之后,列表为空,但仍然选择了最后一个选定的项目... DoSecond ()之后 DoFirst ()也会抛出NullReferenceException

如果我们将当前项设置为null,并在其上调用OnPropertyChanged,则会达到第二个代码的稳定性。从ComboBox中选择有效IsSynchronizedWithCurrentItem的{​​{1}}属性仍然丢失。在以下代码中,如果我拨打ItemDoFirst(),则会选择“bbb”。将DoThird()设置为null(之前调用Item)后,它不会选择“bbb”:

第四个代码:

DoSecond()

BR, 马顿

1 个答案:

答案 0 :(得分:1)

出于某种原因,当您将IsSynchronizedWithCurrentItem设置为true并且SelectedItem绑定实现INotifyPropertyChanged的源对象时,ICollectionView不允许您明确设置CurrentItemnull(例如,调用MoveCurrentToPosition(-1),否则可以正常工作)。我有一些想法,为什么会这样,但我不想推测。

我发现将CurrentItem设置为null的唯一方法是明确地将null分配给SelectedItem绑定到的属性({{1}您的案例中的属性)并使用适当的属性名称引发Item事件。使用调试器,您会注意到此时PropertyChanged将在内部设置为CurrentItem。然后你很清楚应用一个没有结果的过滤器。

至于你的另一个问题,那就是在应用一个不产生结果的过滤器然后另一个产生一些结果null的过滤器停止工作之后,它实际上不是真的。同样,如果您使用调试器,您会注意到在应用第二个过滤器后,IsSynchronizedWithCurrentItem属性保持不变 - 它仍然会产生CurrentItem,因此null仍然与SelectedItem保持同步{1}}。我担心在这种情况下,您需要自己选择第一个可用项目 - 例如为CurrentItem媒体资源分配适当的价值或致电Item

修改

回应你的评论和有关的更新细节 - 这正是我在前一段中提到的(特别是最后一句)。您可能会注意到,在应用过滤器时,只要当前值仍然可行,它就不会自动更改。好吧,Items.MoveCurrentToFirst()(意思是“没有当前价值”)总是可行的,所以它永远不会自动改变,这就是你必须自己做的原因。

与您的问题中的示例相反,这些示例是具有预期结果的任意情况(您知道何时应用空滤镜)我认为您将无法预先判断是否会出现这种情况过滤器将产生任何结果。这里最简单的(在我看来)解决方案是编写一个简单但通用的方法来将任何过滤器应用于集合:

null

这会给您以下行为:

  • 当您应用保留当前项目的过滤器时,它将不会更改
  • 当您应用空过滤器时,将选择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(); }
  • 当您应用非空过滤器,当前项目为null时,将选择第一个可用项目