筛选CollectionViewSource并生成空结果将引发异常

本文关键字:异常 结果 CollectionViewSource 筛选 | 更新日期: 2023-09-27 18:00:34

你知道吗,它为什么会抛出

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

显示空列表。没错。但是,当IDoFirst()列表显示"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,则达到第二个代码的稳定性。IsSynchronizedWithCurrentItem从组合框中选择有效Item的属性仍然丢失。在下面的代码中,如果我调用DoFirst()DoThird(),则会选择"bbb"。将Item设置为空(之前调用DoSecond())后,不会选择"bbb":

第四个代码:

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

筛选CollectionViewSource并生成空结果将引发异常

由于某些原因,当您将IsSynchronizedWithCurrentItem设置为true,并且SelectedItem绑定的源对象实现INotifyPropertyChanged时,ICollectionView不允许您显式地将CurrentItem设置为null(例如,通过调用MoveCurrentToPosition(-1),否则效果良好)。我有一些想法为什么会这样,但我不想猜测。

我发现将CurrentItem设置为null的唯一方法是将null显式地附加到SelectedItem绑定的属性(在您的情况下是Item属性),并使用适当的属性名称引发PropertyChanged事件。使用调试器,您会注意到此时CurrentItem将在内部设置为null。然后你可以清楚地应用一个不会产生任何结果的过滤器。

至于您的另一个担忧,即在应用一个没有产生结果的过滤器,然后另一个产生一些结果的过滤器IsSynchronizedWithCurrentItem停止工作后,这实际上不是真的。同样,如果您使用调试器,您会注意到在应用第二个筛选器后,CurrentItem属性保持不变——它仍然产生null,因此SelectedItem仍然与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
  • 当您应用非空过滤器,并且当前项目为null时,将选择第一个可用项目