如何在 DependencyProperty 中传播 PropertyChanged 更改

Posted

技术标签:

【中文标题】如何在 DependencyProperty 中传播 PropertyChanged 更改【英文标题】:How to propagate PropertyChanged changes in DependencyProperty 【发布时间】:2011-09-26 09:07:57 【问题描述】:

我有一个实现 INotifyPropertyChanged 的​​类。此类的实例在 Window 中声明为 DependencyProperty,例如,

    public IMyClass MyClass
    
        get  return (IMyClass)GetValue(MyClassProperty); 
        set  SetValue(MyClassProperty, value); 
    
    public static readonly DependencyProperty MyClassProperty=
        DependencyProperty.Register("MyClass", typeof(IMyClass), typeof(MainWindow), new UIPropertyMetadata(null));

在 XAML 中,我有一个使用绑定到此类的元素

Text="Binding MyClass, Converter=StaticResource someConverter

每当我更改 MyClass 中的属性时,我都希望触发 someConverter。但是,只有当我完全换掉 MyClass 时才会发生这种情况。有没有办法将 DependencyProperty 更新绑定到我的 MyClass PropertyChanged?

更新。本着 AresAvatar 解决方案的精神,这是我们目前所拥有的。剩下的问题是如何调用 InvalidateProperty(不让 MyClass 跟踪它......)

    public IMyClass MyClass
    
        get  return (IMyClass)GetValue(MyClassProperty); 
        set  SetValue(MyClassProperty, value); 
    
    public static readonly DependencyProperty MyClassProperty =
        DependencyProperty.Register("MyClass", typeof(IMyClass), typeof(MainWindow),
        new UIPropertyMetadata(null, new PropertyChangedCallback(OnMyClassChanged)));

    private static void OnMyClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    
        if (e.OldValue != null)
        
            ((IMyClass)e.OldValue).PropertyChanged -= ((MainWindow)d).MyClass_PropertyChanged;
        

        if (e.NewValue != null)
        
            ((IMyClass)e.NewValue).PropertyChanged += ((MainWindow)d).MyClass_PropertyChanged;
        
    

    private void MyClass_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    
        this.InvalidateProperty(MyClassProperty);  <----- still does not refresh binding, but called.
    

【问题讨论】:

我遇到了类似的问题,仍然没有解决:***.com/questions/6363883/… dthorpe,我在下面的回答也应该适用于您的问题。您想将 NotifyPropertyChanged 挂钩到绑定失效中,就像这里的 tofutim 一样。 @dthorpe - 你的问题很有帮助。 尝试使用这个:BindingExpressionBase exp = BindingOperations.GetBindingExpressionBase(this, Container.MyClassProperty); if (exp != null) exp.UpdateTarget(); 我尝试了 UpdateTarget 和 InvalidateProperty 选项,但没有成功。然而,他们正在被召唤。我认为底层 Binding 代码必须对对象是否相同进行一些检查,并且不能强制重新调用 Converter。 【参考方案1】:

转换器不应该比简单的转换做更多的工作,你的问题听起来像转换器使用对象的很多属性来创建一些组合值。使用MultiBinding 代替它挂钩到您需要的对象上的所有不同属性,这样MultiBinding 上的MultiValueConverter 将在任何这些属性发生变化时触发。

此外,由于您似乎在创建文本,因此您可能无需使用任何转换器就可以逃脱,因为 StringFormat 可能就足够了。

【讨论】:

实际上,我已经在使用 MultiBinding,并将 MyClass 作为绑定对象之一。您是否建议我 MultiBind 到 MyClass.Prop1、MyClass.Prop2 和 MyClass.Prop3?肯定有办法说这个对象已经改变了? @toutim:这就是我的建议,是的。我不知道有什么方法可以通知此类更改,如果有办法,我也不建议这样做。编辑:AresAvatar 说的可能就是这样。 总而言之,我永远无法让 InvalidateProperty 重新触发转换器,您的解决方案虽然我从美学的角度抗拒,但实际上有效!【参考方案2】:

在 MyClass 中,实现 NotifyPropertyChanged 事件。然后将属性更改回调添加到您的 MyClass DependencyProperty。在 DP 的属性更改回调中,将新的 MyClass NotifyPropertyChanged 事件挂钩到第二个回调函数(如果有的话,使用 -= 运算符取消挂钩之前的值)。在第二个回调函数中,调用DependencyObject.InvalidateProperty 以便更新绑定。

编辑:您可能需要触发绑定更新:

BindingExpressionBase exp = BindingOperations.GetBindingExpressionBase(this, Container.MyClassProperty);
if (exp != null)
    exp.UpdateTarget();

class MyClass : INotifyPropertyChanged

    /// <summary>
    /// Event raised when a property is changed
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises the property changed event
    /// </summary>
    /// <param name="e">The arguments to pass</param>
    protected void OnPropertyChanged(PropertyChangedEventArgs e)
    
        if (PropertyChanged != null)
            PropertyChanged(this, e);
    

    /// <summary>
    /// Notify for property changed
    /// </summary>
    /// <param name="name">Property name</param>
    protected void NotifyPropertyChanged(string name)
    
        OnPropertyChanged(new PropertyChangedEventArgs(name));
    


    /// <summary>
    /// The parent container object
    /// </summary>
    public Container Parent  get; set; 


    // Some data
    int x;



class Container : DependencyObject

    public static readonly DependencyProperty MyClassProperty = DependencyProperty.Register("MyClass", typeof(MyClass), typeof(Container), new FrameworkPropertyMetadata(MyClassPropChanged));

    public MyClass MyClass
    
        get  return (MyClass)GetValue(MyClassProperty); 
        set  SetValue(MyClassProperty, value); 
    

    void MyClassPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    
        Container ct = d as Container;
        if (ct == null)
            return;

        MyClass oldc = e.OldValue as MyClass;
        if (oldc != null)
        
            oldc.PropertyChanged -= new PropertyChangedEventHandler(MyClass_PropertyChanged);
            oldc.Parent = null;
        
        MyClass newc = e.NewValue as MyClass;
        if (newc != null)
        
            newc.Parent = ct;
            newc.PropertyChanged += new PropertyChangedEventHandler(MyClass_PropertyChanged);
        
    


    void MyClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
    
        MyClass mc = sender as MyClass;
        if (mc == null || mc.Parent == null)
            return;
        mc.Parent.InvalidateProperty(Container.MyClassProperty);
    

【讨论】:

为什么会有多个事件挂钩? 如果我对他的理解正确,他希望更改设置到 DP 中的 MyClass 实例,以导致 DP 无效。为此,您需要使用 MyClass 实例的事件,以及 DP 中的属性更改事件。 @AresAvatar 我设置了 PropertyChangedCallback(显然必须是静态的)以解开 e.OldValue PropertyChanged 并挂钩 e.NewValue PropertyChanged - 但是如何将 DependencyObject 传递给 PropertyChanged 回调?跨度> @tofutim:将 NotifyPropertyChanged 更改回调设置为包含您的 DependencyProperty 的类的成员函数。在 DP 属性更改回调中,您将获得 MyClass 的副本。 PropertyChangedCallback 坚持使用静态回调,它会一直传播。在 DP 回调中,您确实获得了 MyClass 的副本,但我还没有找到将其传递给 PropertyChanged 的​​方法(它确实有发送者 - 但这是 MyClass)。目前我唯一能想到的就是让 MyClass 跟踪 Dp,但这似乎很奇怪。【参考方案3】:

我发现的唯一技术是在策略性放置的事件处理程序(例如 LostFocus)中调用绑定的 UpdateSource 方法。

private void mycontrol_LostFocus(object sender, RoutedEventArgs e)

    if (mycontrol.IsModified)
    
        var binding = mycontrol.GetBindingExpression(MyControl.FooBarProperty);
        binding.UpdateSource();
    

如果您不关心聊天或者您的控件没有获取输入焦点,您可以在 mycontrol_PropertyChanged 事件或类似事件中执行此操作。但是,在每次属性更改或每次击键时强制执行转换周期可能会干扰验证。

【讨论】:

以上是关于如何在 DependencyProperty 中传播 PropertyChanged 更改的主要内容,如果未能解决你的问题,请参考以下文章

如何使 Binding 尊重 DependencyProperty 值强制?

ViewModel 中的 INotifyPropertyChanged 与 DependencyProperty

如何使 WPF UserControl 中的一个 DependencyProperty 更改另一个 [重复]

从 xaml 中清除 dependencyProperty 值

DependencyProperty 未在 PropertyChanged 上更新

在 DataTemplate 中使用时,行为 DependencyProperty 不更新 ViewModel