如何在DependencyProperty中传播PropertyChanged更改

13

我有一个实现了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.
    }

我遇到了类似的问题,仍未解决:https://dev59.com/kFjUa4cB1Zd3GeqPP0mi - dthorpe
dthorpe,我的下面的回答也适用于你的问题。你想将NotifyPropertyChanged钩入到绑定无效化中,就像tofutim在这里所做的那样。 - Ed Bayiates
@dthorpe - 你的问题非常有帮助。 - tofutim
1
尝试使用以下代码:BindingExpressionBase exp = BindingOperations.GetBindingExpressionBase(this, Container.MyClassProperty); 如果 (exp != null) exp.UpdateTarget(); - Ed Bayiates
我尝试了UpdateTarget和InvalidateProperty选项,但都没有成功。它们确实被调用了。我认为底层的绑定代码必须有一些检查,以确定对象是否相同,不能强制重新调用转换器。 - tofutim
我认为答案在这里:https://dev59.com/UnE95IYBdhLWcg3wGqIH - G.Y
3个回答

7
转换器不应该做比简单转换更多的工作,您的问题听起来像是转换器使用了很多对象属性来创建一些组合值。相反,使用MultiBinding,它会连接到您需要的对象上的所有不同属性,这样那个MultiBinding上的MultiValueConverter将在任何这些属性更改时触发。
此外,由于您似乎正在创建文本,您可能可以完全不使用任何转换器,因为StringFormat可能已经足够了。

实际上,我已经在使用MultiBinding,并将MyClass作为其中一个绑定对象。您是建议我MultiBind到MyClass.Prop1、MyClass.Prop2和MyClass.Prop3吗?肯定有一种方法可以表明该对象已更改吧? - tofutim
@toutim:是的,那就是我提议的。我不知道有没有通知这种更改的方法,如果有的话,我不会建议这样做。编辑:AresAvatar所说的可能就是那种方式。 - H.B.
说了这么多,我从未能够让InvalidateProperty重新触发转换器,尽管从美学的角度来看,你的解决方案实际上是可行的! - tofutim

2
我找到的唯一技巧是在一个策略性地放置的事件处理程序中调用绑定的UpdateSource方法,例如LostFocus。
private void mycontrol_LostFocus(object sender, RoutedEventArgs e)
{
    if (mycontrol.IsModified)
    {
        var binding = mycontrol.GetBindingExpression(MyControl.FooBarProperty);
        binding.UpdateSource();
    }
}

如果您不关心聊天或者您的控件没有输入焦点,您可以在mycontrol_PropertyChanged事件或类似事件中执行此操作。然而,对于每个属性更改或每个按键操作强制进行转换可能会干扰验证。


1
在 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);
    }
}

为什么需要多个事件钩子? - dthorpe
如果我理解他的意思正确,他想要更改设置在DP中的MyClass实例,以导致DP无效。为此,您需要使用MyClass实例的事件和DP中的属性更改事件。 - Ed Bayiates
@AresAvatar 我设置了 PropertyChangedCallback(显然必须是静态的),以解除 e.OldValue 的 PropertyChanged 并连接 e.NewValue 的 PropertyChanged - 但是我如何将 DependencyObject 传递给 PropertyChanged 回调函数呢? - tofutim
我已经调用了InvalidateProperty..,但这并不会重新调用转换器。 - tofutim
我会检查一下,你的绝对是最优雅的。 - tofutim
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接