CheckBox Checked 事件在绑定集合更新之前触发

CheckBox Checked event fires before bound collection updates

我有一个自定义控件来显示 ComboBox 中带有复选框的项目。为了实现这一点,我使用了 DataTemplateCheckBoxComboBoxItemSource 使用绑定到包含我的过滤器值的 ObserableCollection<FilterValue>FilterValue 是自定义 class 实现 INotifyPropertyChangedCheckBox 的属性 ContentIsChecked 也使用绑定来使用我的列表的值。此控件将在 Silverlight 中使用。

绑定本身工作正常,如下所示:

注册CheckedUnchecked事件时出现问题

只要其中一个复选框改变了状态,事件就会按预期触发,但此时绑定列表中的值仍未更新。 我在调试时看到的是 Checked/Unchecked 事件在 FilterValuePropertyChanged 事件之前触发。 这意味着在事件触发时,我无法向列表询问所有活动(选中)的过滤器。我可以做些什么来实现这一目标?

FilterControl.xaml:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    xmlns:local="clr-namespace:Controls" x:Class="Controls.FilterControl"
    mc:Ignorable="d"
    d:DesignHeight="45" d:DesignWidth="140">

    <StackPanel x:Name="LayoutRoot">
        <sdk:Label x:Name="LblFilterDescription" Content="-" />
        <ComboBox x:Name="Filter"  Width="120" ItemsSource="{Binding AvailableFilters, RelativeSource={RelativeSource FindAncestor, AncestorType=local:FilterControl}}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <CheckBox Content="{Binding Path=Text}" IsChecked="{Binding Path=IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Checked="FilterChanged" Unchecked="FilterChanged" />
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </StackPanel>
</UserControl>

FilterControl.xaml.cs:

public partial class FilterControl : UserControl
{
    public delegate void FilterChangedHandler(object sender);
    public event FilterChangedHandler OnFilterChanged;

    public ObservableCollection<FilterValue> AvailableFilters { get; set; }
    public List<string> AppliedFilters
    {
        get
        {
            return new List<string>(AvailableFilters.Where(filter => filter.IsChecked).Select(filter => filter.Text));
        }
    }

    public FilterControl()
    {
        InitializeComponent();

        AvailableFilters = new ObservableCollection<FilterValue>();
    }

    public bool AddFilterValue(string filterValue)
    {
        bool found = false;

        foreach (FilterValue f in AvailableFilters)
        {
            if (f.Text == filterValue)
            {
                found = true;
                break;
            }
        }

        if (!found)
            AvailableFilters.Add(new FilterValue() { IsChecked = false, Text = filterValue });

        return found;
    }

    private void FilterChanged(object sender, RoutedEventArgs e)
    {
        //Here if I check AvailableFilters, the value is not changed yet.
        //PropertyChanged allways fires after this, what makes me unable to
        //get all currently applied filters (checked items)...
    }
}

过滤值:

public class FilterValue : INotifyPropertyChanged
{
    private bool _IsChecked;
    private string _Text;

    public bool IsChecked
    {
        get { return _IsChecked; }
        set
        {
            _IsChecked = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
        }
    }
    public string Text
    {
        get { return _Text; }
        set
        {
            _Text = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Text"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

因此,当我尝试重现此行为时,我意识到这似乎是一种只在 Silverlight 中出现的行为。如果您在 WPF 上尝试此示例,Changed 会在 更新绑定 属性 之后触发 。所以你可以在 FilterChanged 方法中访问你的 AppliedFilters 属性 它会反映实际当前情况。但在 Silverlight 上,情况并非如此。更糟糕的是,这种行为在我看来甚至都不一致。我确实遇到过 更新 属性 之后触发事件的情况(产生预期的输出)。

解决这个问题的一种方法是清理您的组件逻辑。如果您查看它,就会混合两个不同的概念:事件驱动 UI 逻辑和清晰的数据绑定。当然,“正确”执行它会产生多种效果,您可能无法仅在现有项目中确保这一点,但您至少可以尝试在此处找到正确的方向,这也应该可以解决这个问题。

所以您的逻辑现在使用数据绑定来为视图提供数据,并反映显示项的变化。但是您正在使用项目级别的事件来根据以前的更改执行其他逻辑。正如我们所见,执行顺序似乎无法跨平台保证,因此最好避免依赖它。

在这种情况下,您应该让数据成为事实来源,并在应用的过滤器发生变化时让数据变化告诉您。通过 ObservableCollection 和实现 INotifyPropertyChanged 的项目,您已经完成了一半。不幸的是,可观察集合只会通知您有关集合的更改,而不是包含项的更改。但是有多种解决方案可以扩展集合以查看集合中的项目。

This related question covers exactly that topic and there are multiple ideas on how to expand the observable collection for exactly that behavior. In my case, I have used the FullyObservableCollection implementation by Bob Sammers.

为此,您需要做的就是将您的 ObservableCollection<FilterValue> 更改为 FullyObservableCollection<FilterValue> 并订阅 ItemPropertyChanged 事件:

AvailableFilters = new FullyObservableCollection<FilterValue>();
AvailableFilters.ItemPropertyChanged += AvailableFilters_ItemPropertyChanged;

在该事件处理程序中,您将正确地看到正确的行为。