CheckBox Checked 事件在绑定集合更新之前触发
CheckBox Checked event fires before bound collection updates
我有一个自定义控件来显示 ComboBox
中带有复选框的项目。为了实现这一点,我使用了 DataTemplate
和 CheckBox
。 ComboBox
的 ItemSource
使用绑定到包含我的过滤器值的 ObserableCollection<FilterValue>
。 FilterValue
是自定义 class 实现 INotifyPropertyChanged
。 CheckBox
的属性 Content
和 IsChecked
也使用绑定来使用我的列表的值。此控件将在 Silverlight 中使用。
绑定本身工作正常,如下所示:
注册Checked
或Unchecked
事件时出现问题
只要其中一个复选框改变了状态,事件就会按预期触发,但此时绑定列表中的值仍未更新。
我在调试时看到的是 Checked
/Unchecked
事件在 FilterValue
的 PropertyChanged
事件之前触发。
这意味着在事件触发时,我无法向列表询问所有活动(选中)的过滤器。我可以做些什么来实现这一目标?
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;
在该事件处理程序中,您将正确地看到正确的行为。
我有一个自定义控件来显示 ComboBox
中带有复选框的项目。为了实现这一点,我使用了 DataTemplate
和 CheckBox
。 ComboBox
的 ItemSource
使用绑定到包含我的过滤器值的 ObserableCollection<FilterValue>
。 FilterValue
是自定义 class 实现 INotifyPropertyChanged
。 CheckBox
的属性 Content
和 IsChecked
也使用绑定来使用我的列表的值。此控件将在 Silverlight 中使用。
绑定本身工作正常,如下所示:
注册Checked
或Unchecked
事件时出现问题
只要其中一个复选框改变了状态,事件就会按预期触发,但此时绑定列表中的值仍未更新。
我在调试时看到的是 Checked
/Unchecked
事件在 FilterValue
的 PropertyChanged
事件之前触发。
这意味着在事件触发时,我无法向列表询问所有活动(选中)的过滤器。我可以做些什么来实现这一目标?
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;
在该事件处理程序中,您将正确地看到正确的行为。