NOTE: Before reading the subject and instantly marking this as a duplicate, please read the entire question to understand what we are after. The other questions which describe getting the
BindingExpression
, then callingUpdateTarget()
method does not work in our use-case. Thanks!
TL:DR版本
使用
INotifyPropertyChanged
,即使关联的属性没有发生变化,我也可以简单地通过引发带有该属性名称的PropertyChanged
事件来重新评估绑定(bind)。如果该属性是DependencyProperty
而我无权访问目标,而只能访问源,我该怎么做?概述
我们有一个名为
ItemsControl
的自定义MembershipList
,它公开了一个类型为Members
的名为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
集合,因此我们不能简单地将IsMember
属性添加到项并绑定(bind)到该项。另外,无论如何这将是一个糟糕的设计,因为它将项目限制为属于一个单一成员。考虑其中十个控件的情况,所有控件都绑定(bind)到相同的ItemsSource
,但具有十个不同的成员资格集合。也就是说,请考虑以下绑定(bind)(
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
集合中或从中删除,则绑定(bind)当然不会更新,因为集合实例本身并未更改,仅其内容已更改。就我们而言,我们确实希望它重新评估这种变化。
我们考虑这样向
Count
添加额外的绑定(bind)。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。<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
,在返回实际绑定(bind)之前内部订阅了CollectionChanged
集合的Members
事件,以为我可以在事件处理程序中使用前面提到的BindingExpression.UpdateTarget()
方法调用,但是问题是我没有目标从中获取BindingExpression
的对象,以便从UpdateTarget()
覆盖内调用ProvideValue()
。换句话说,我知道我必须告诉某人,但我不知道该告诉谁。但是,即使我这样做了,使用这种方法也会很快遇到问题,您将手动预订容器作为
CollectionChanged
事件的监听器目标,这会在容器开始虚拟化时引起问题,这就是为什么最好只使用绑定(bind)的原因当回收容器时,它将自动正确地重新应用。但是,您又回到了这个问题的开始,即无法告诉绑定(bind)更新以响应CollectionChanged
通知。解决方案A-对
DependencyProperty
事件使用第二个CollectionChanged
一个可行的可行解决方案是创建一个代表
CollectionChanged
的任意属性,将其添加到MultiBinding
中,然后在每次您想要刷新绑定(bind)时进行更改。为此,我首先在此处创建了一个名为
DependencyProperty
的 bool 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;
}
Note: To avoid a memory leak, the code here really should use a
WeakEventManager
for theCollectionChanged
event. However I left it out because of brevity in an already long post.
这是使用它的绑定(bind)...
<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>
这确实有效,但是对于阅读该代码的人而言,其意图并不完全清楚。此外,它还需要为每种类似的用法类型在控件上创建一个新的任意属性(此处为
MembersCollectionChanged
),从而使您的API困惑。也就是说,从技术上讲,它确实满足要求。这样子感觉很脏。解决方案B-使用
INotifyPropertyChanged
下面显示了另一个使用
INotifyPropertyChanged
的解决方案。这使得MembershipList
也支持INotifyPropertyChanged
。我将Members
更改为标准CLR类型的属性,而不是DependencyProperty
。然后,我在setter中订阅其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));
}
Again, this should be changed to use the
WeakEventManager
.
这似乎可以与页面顶部的第一个绑定(bind)一起很好地工作,并且很清楚它的意图是什么。
但是,问题仍然存在,首先让
DependencyObject
也支持INotifyPropertyChanged
接口(interface)是一个好主意。我不确定。我还没有发现任何表示不允许的内容,我的理解是DependencyProperty
实际上会引发自己的更改通知,而不是应用/附加的DependencyObject
,因此它们不应冲突。巧合的是,这就是为什么您不能简单地实现
INotifyCollectionChanged
接口(interface)并引发PropertyChanged
的DependencyProperty
事件的原因。如果在DependencyProperty
上设置了绑定(bind),则它根本不会监听对象的PropertyChanged
通知。没发生什么事。它落在耳朵上。要使用INotifyPropertyChanged
,您必须将该属性实现为标准CLR属性。这就是我在上面的代码中所做的,再次可以正常工作。我只想找出如何做就相当于可以为
PropertyChanged
引发DependencyProperty
事件而实际上不更改值(如果可能的话)。我开始认为不是。
最佳答案
第二种选择是您的集合还实现INotifyPropertyChanged
,这是解决此问题的好方法。这很容易理解,代码并没有真正地“隐藏”在任何地方,它使用了所有XAML开发人员和您的团队熟悉的元素。
第一个解决方案也很不错,但是如果未对其进行很好的注释或记录,则某些开发人员可能会难以理解它的目的,或者甚至在以下情况时就知道它的存在:a)出现问题或b)他们需要将该控件的行为复制到其他地方。
由于这两者都起作用,而实际上是代码的“可读性”成为您的问题,因此,除非其他因素(性能等)成为问题,否则应以最具可读性的方式进行。
因此,选择解决方案B,请确保对其进行了适当的注释/记录,并确保您的团队知道解决此问题的方向。
关于c# - 是否可以强制基于DependencyProperty的绑定(bind)以编程方式重新评估?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32812948/