gpt4 book ai didi

c# - 是否可以强制基于DependencyProperty的绑定(bind)以编程方式重新评估?

转载 作者:行者123 更新时间:2023-12-02 00:11:07 26 4
gpt4 key购买 nike

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 calling UpdateTarget() method does not work in our use-case. Thanks!



TL:DR版本

使用 INotifyPropertyChanged,即使关联的属性没有发生变化,我也可以简单地通过引发带有该属性名称的 PropertyChanged事件来重新评估绑定(bind)。如果该属性是 DependencyProperty而我无权访问目标,而只能访问源,我该怎么做?

概述

我们有一个名为 ItemsControl的自定义 MembershipList,它公开了一个类型为 Members的名为 ObservableCollection<object>的属性。这是与 ItemsItemsSource属性分开的属性,这些属性的行为与任何其他 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)( MembershipListItemMembershipList控件的容器)...
<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 the CollectionChanged 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)并引发 PropertyChangedDependencyProperty事件的原因。如果在 DependencyProperty上设置了绑定(bind),则它根本不会监听对象的 PropertyChanged通知。没发生什么事。它落在耳朵上。要使用 INotifyPropertyChanged,您必须将该属性实现为标准CLR属性。这就是我在上面的代码中所做的,再次可以正常工作。

我只想找出如何做就相当于可以为 PropertyChanged引发 DependencyProperty事件而实际上不更改值(如果可能的话)。我开始认为不是。

最佳答案

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

第一个解决方案也很不错,但是如果未对其进行很好的注释或记录,则某些开发人员可能会难以理解它的目的,或者甚至在以下情况时就知道它的存在:a)出现问题或b)他们需要将该控件的行为复制到其他地方。

由于这两者都起作用,而实际上是代码的“可读性”成为您的问题,因此,除非其他因素(性能等)成为问题,否则应以最具可读性的方式进行。

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

关于c# - 是否可以强制基于DependencyProperty的绑定(bind)以编程方式重新评估?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32812948/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com