是否可以强制基于DependencyProperty的绑定以编程方式重新评估?

时间:2015-09-27 21:17:53

标签: c# wpf xaml dependency-properties inotifypropertychanged

  

注意: 在阅读主题并立即将其标记为重复之前,请阅读整个问题以了解我们的目标。描述获取BindingExpression,然后调用UpdateTarget()方法的其他问题在我们的用例中不起作用。谢谢!

TL:DR版本

使用INotifyPropertyChanged即使仅通过使用该属性的名称引发PropertyChanged事件而未关联相关属性,我也可以重新评估绑定。如果财产是DependencyProperty并且我无权访问目标,只有来源,我该怎么做?

概述

我们有一个名为ItemsControl的自定义MembershipList,它会公开名为Members的{​​{1}}类型的属性。这是与ObservableCollection<object>Items属性不同的属性,否则其他行为与任何其他ItemsSource相同。它被定义为......

ItemsControl

我们要做的是为来自public static readonly DependencyProperty MembersProperty = DependencyProperty.Register( "Members", typeof(ObservableCollection<object>), typeof(MembershipList), new PropertyMetadata(null)); public ObservableCollection<object> Members { get { return (ObservableCollection<object>)GetValue(MembersProperty); } set { SetValue(MembersProperty, value); } } / Items的所有成员设置样式,这些成员也会出现在ItemsSource中,与那些不同的成员不同。换句话说,我们正试图突出两个列表的交集。

请注意,Members可能包含根本不在Members / Items的项目。这就是为什么我们不能简单地使用多选ItemsSource,其中ListBox必须是SelectedItems / Items的子集。在我们的使用中,情况并非如此。

另请注意,我们不拥有ItemsSource / ItemsItemsSource个集合,因此我们无法简单地将Members属性添加到项目并绑定到那个。另外,这将是一个糟糕的设计,因为它会限制项目属于一个单一的成员资格。考虑其中10个控件的情况,这些控件都绑定到相同的IsMember,但有10个不同的成员集合。

也就是说,考虑以下绑定(ItemsSourceMembershipListItem控件的容器)...

MembershipList

这很直接。当<Style TargetType="{x:Type local:MembershipListItem}"> <Setter Property="IsMember"> <Setter.Value> <MultiBinding Converter="{StaticResource MembershipTest}"> <Binding /> <!-- Passes the DataContext to the converter --> <Binding Path="Members" RelativeSource="{RealtiveSource AncestorType={x:Type local:MembershipList}}" /> </MultiBinding> </Setter.Value> </Setter> </Style> 属性更改时,该值将通过Members转换器传递,结果将存储在目标对象的MembershipTest属性中。

但是,如果在IsMember集合中添加或删除了项目,则绑定当然更新,因为集合实例本身没有更改,只有其内容

在我们的案例中,我们确实希望它重新评估这些变化。

我们考虑过向Members添加额外的绑定......

Count

...由于现在跟踪了添加和删除,因此已经接近了,但如果您将一个项目替换为另一个项目,则这不起作用,因为计数没有变化。

我还尝试在返回实际绑定之前创建一个内部订阅<Style TargetType="{x:Type local:MembershipListItem}"> <Setter Property="IsMember"> <Setter.Value> <MultiBinding Converter="{StaticResource MembershipTest}"> <Binding /> <!-- Passes the DataContext to the converter --> <Binding Path="Members" RelativeSource="{RealtiveSource AncestorType={x:Type local:MembershipList}}" /> <Binding Path="Members.Count" FallbackValue="0" /> </MultiBinding> </Setter.Value> </Setter> </Style> 集合的MarkupExtension事件的CollectionChanged,以为我可以使用前面提到的Members在事件处理程序中调用方法,但问题是我没有目标对象从BindingExpression.UpdateTarget()覆盖中从BindingExpression调用UpdateTarget()。换句话说,我知道我必须告诉别人,但我不知道该告诉谁。

但即使我这样做,使用这种方法你很快会遇到一些问题,你会手动将容器作为监听器目标订阅到ProvideValue()事件,这会在容器开始虚拟化时引起问题,这就是为什么最好只使用一个绑定,当一个容器被回收时,该绑定会自动正确地重新应用。但是,您可以回到此问题的开头,即无法告知绑定更新以响应CollectionChanged通知。

解决方案A - 对CollectionChanged个事件使用第二个DependencyProperty

一个可行的解决方案是创建一个任意属性来表示CollectionChanged,将其添加到CollectionChanged,然后在您想要刷新绑定时更改它。

为此,我首先创建了一个名为MultiBinding的布尔DependencyProperty。然后在MembersCollectionChanged处理程序中,我订阅(或取消订阅)Members_PropertyChanged事件,并在该事件的处理程序中,切换CollectionChanged属性,刷新MembersCollectionChanged

这是代码......

MultiBinding
  

注意:为避免内存泄漏,此处的代码确实应该使用public static readonly DependencyProperty MembersCollectionChangedProperty = DependencyProperty.Register( "MembersCollectionChanged", typeof(bool), typeof(MembershipList), new PropertyMetadata(false)); public bool MembersCollectionChanged { get { return (bool)GetValue(MembersCollectionChangedProperty); } set { SetValue(MembersCollectionChangedProperty, value); } } public static readonly DependencyProperty MembersProperty = DependencyProperty.Register( "Members", typeof(ObservableCollection<object>), typeof(MembershipList), new PropertyMetadata(null, Members_PropertyChanged)); // Added the change handler public int Members { get { return (int)GetValue(MembersProperty); } set { SetValue(MembersProperty, value); } } private static void Members_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var oldMembers = e.OldValue as ObservableCollection<object>; var newMembers = e.NewValue as ObservableCollection<object>; if(oldMembers != null) oldMembers.CollectionChanged -= Members_CollectionChanged; if(newMembers != null) oldMembers.CollectionChanged += Members_CollectionChanged; } private static void Members_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { // 'Toggle' the property to refresh the binding MembersCollectionChanged = !MembersCollectionChanged; } WeakEventManager事件。然而,由于在已经很长的帖子中简洁,我把它排除了。

这是使用它的绑定......

CollectionChanged

这确实有效,但对于阅读代码的人来说,并没有完全明确其意图。此外,它需要在控件(此处<Style TargetType="{x:Type local:MembershipListItem}"> <Setter Property="IsMember"> <Setter.Value> <MultiBinding Converter="{StaticResource MembershipTest}"> <Binding /> <!-- Passes in the DataContext --> <Binding Path="Members" RelativeSource="{RealtiveSource AncestorType={x:Type local:MembershipList}}" /> <Binding Path="MembersCollectionChanged" RelativeSource="{RealtiveSource AncestorType={x:Type local:MembershipList}}" /> </MultiBinding> </Setter.Value> </Setter> </Style> )上为每种类似的使用类型创建一个新的任意属性,从而使您的API变得混乱。也就是说,技术上它确实满足了要求。这样做只是感觉很脏。

解决方案B - 使用MembersCollectionChanged

使用INotifyPropertyChanged的另一种解决方案如下所示。这使INotifyPropertyChanged也支持MembershipList。我将INotifyPropertyChanged更改为标准CLR类型的属性,而不是Members。然后我在setter中订阅了它的DependencyProperty事件(如果存在,则取消订阅旧事件)。然后,当CollectionChanged事件触发时,只需为PropertyChanged举起Members事件即可。

这是代码......

CollectionChanged
  

同样,应将其更改为使用private ObservableCollection<object> _members; public ObservableCollection<object> Members { get { return _members; } set { if(_members == value) return; // Unsubscribe the old one if not null if(_members != null) _members.CollectionChanged -= Members_CollectionChanged; // Store the new value _members = value; // Wire up the new one if not null if(_members != null) _members.CollectionChanged += Members_CollectionChanged; RaisePropertyChanged(nameof(Members)); } } private void Members_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { RaisePropertyChanged(nameof(Members)); }

这似乎可以正常使用页面顶部的第一个绑定,并且非常清楚它的意图是什么。

但是,问题仍然存在,首先让WeakEventManager也支持DependencyObject界面是个好主意。我不确定。我还没有找到任何说不允许的内容,而我的理解是INotifyPropertyChanged实际上会提出自己的更改通知,而不是DependencyProperty它已应用/附加到所以他们不应该发生冲突。

巧合的是,这也是为什么你不能简单地实现DependencyObject界面并为INotifyCollectionChanged举起PropertyChanged事件的原因。如果在DependencyProperty上设置了绑定,则根本不会收听对象的DependencyProperty通知。什么都没发生。它充耳不闻。要使用PropertyChanged,您必须将该属性实现为标准CLR属性。这就是我在上面的代码中所做的,这也是有效的。

我想了解如何在不实际更改价值的情况下为INotifyPropertyChanged举办PropertyChanged事件,如果可能的话。我开始认为它不是。

1 个答案:

答案 0 :(得分:1)

第二个选项,您的集合也实现INotifyPropertyChanged,是解决此问题的好方法。这很容易理解,代码并不是真正“隐藏”在任何地方,它使用了所有XAML开发人员和团队熟悉的元素。

第一个解决方案也很不错,但如果它没有被评论或记录得很好,一些开发人员可能很难理解它的目的,或者甚至知道它在那里a)出现问题或者b)他们需要复制行为在别处控制。

由于两者都起作用,并且它确实是代码的“可读性”,因此除非其他因素(性能等)成为问题,否则请使用最易读的内容。

因此,请选择解决方案B,确保对其进行适当的评论/记录,并确保您的团队了解您已解决此问题的方向。