【问题标题】:How to propagate PropertyChanged changes in DependencyProperty如何在 DependencyProperty 中传播 PropertyChanged 更改
【发布时间】: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.
    }

【问题讨论】:

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

标签: c# wpf xaml data-binding dependency-properties


【解决方案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 事件或类似事件中执行此操作。但是,在每次属性更改或每次击键时强制执行转换周期可能会干扰验证。

【讨论】:

    猜你喜欢
    • 2021-06-23
    • 1970-01-01
    • 2016-09-16
    • 2015-11-26
    • 1970-01-01
    • 2012-01-18
    • 2011-01-04
    • 2021-09-10
    • 1970-01-01
    相关资源
    最近更新 更多