注意: 在阅读主题并立即将其标记为重复之前,请阅读整个问题以了解我们的目标。描述获取
BindingExpression
,然后调用UpdateTarget()
方法的其他问题在我们的用例中不起作用。谢谢!的
使用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
/ Items
或ItemsSource
个集合,因此我们无法简单地将Members
属性添加到项目并绑定到那个。另外,这将是一个糟糕的设计,因为它会限制项目属于一个单一的成员资格。考虑其中10个控件的情况,这些控件都绑定到相同的IsMember
,但有10个不同的成员集合。
也就是说,考虑以下绑定(ItemsSource
是MembershipListItem
控件的容器)...
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
通知。
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变得混乱。也就是说,技术上它确实满足了要求。这样做只是感觉很脏。
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
事件,如果可能的话。我开始认为它不是。
答案 0 :(得分:1)
第二个选项,您的集合也实现INotifyPropertyChanged
,是解决此问题的好方法。这很容易理解,代码并不是真正“隐藏”在任何地方,它使用了所有XAML开发人员和团队熟悉的元素。
第一个解决方案也很不错,但如果它没有被评论或记录得很好,一些开发人员可能很难理解它的目的,或者甚至知道它在那里a)出现问题或者b)他们需要复制行为在别处控制。
由于两者都起作用,并且它确实是代码的“可读性”,因此除非其他因素(性能等)成为问题,否则请使用最易读的内容。
因此,请选择解决方案B,确保对其进行适当的评论/记录,并确保您的团队了解您已解决此问题的方向。