WPF 主动触发依赖属性的 PropertyChanged

版权声明:本文为博主原创文章,转载请保留原文链接。联系邮箱:[email protected] https://blog.csdn.net/Liwuqingxin/article/details/81141856

一、需求背景

需要显示 ViewModel 中的 Message/DpMessage,显示内容根据其某些属性来确定。代码结构抽象如下:

// Model
public class Message : INotifyPropertyChanged
{
    public string MSG;
    public string Stack;
}


// ViewModel
public class MessageViewModel : INotifyPropertyChanged
{
    public Message { get; set; }
    public static readonly DpMessageProperty = DependencyProperty.Register(...)
}
<TextBox Text="{Binding Message, Converter={x:static ShowDetailMessageConverter}}"/>
<TextBox Text="{Binding DpMessage, Converter={x:static ShowDetailMessageConverter}}"/>

以上代码,注意,两个 Text 绑定的目标都是 MessageViewModel,绑定的 Path 分别是 MessageDpMessage

Message 或者 DpMessage 变化(注意,变化指的是重新赋值,即,引用了新的 Message 实例),绑定的目标会收到通知,更新 UI。

问题来了:当 Message 或者 DpMessage 的属性变化了呢,Message 类是实现了 INotifyPropertyChanged 的,属性变化能触发自身的变化(MessageViewModel.INotifyPropertyChanged)通知吗?答案是,不能。即,Message .MSG Message .Stack 变化了,View 不能得到更新通知!这不符合需求。

  • 当然,这个问题可以通过更改绑定对象避过,即,将绑定对象直接设置为 Message 或者 DpMessage,然后使用 MultiBinding,将需要的属性都绑定过去,也可以同样实现需求,但是,这种方式的缺点多:繁琐(如果涉及的属性数量非常大呢)、不直观(目标是 Message 整体,却绑定了其属性)等。

二、Message 属性(实现 INotifyPropertyChanged)的解决方法

Message .PropertyChanged 中监测属性变化,变化时主动调用 MessageViewModel.OnPropertyChanged。这是很简单的。

三、DpMessage 依赖属性的解决方法

不同于 INotifyPropertyChanged,依赖属性无法通过 OnPropertyChanged 函数触发属性变更通知,这个函数仅作为回调函数使用。因此,查看 源码,看看.Net如何去触发的通知,找到函数如下:

/// <summary>
/// This is to enable some performance-motivated shortcuts in property
/// invalidation.  When this is called, it means the caller knows the
/// value of the property is pointing to the same object instance as
/// before, but the meaning has changed because something within that
/// object has changed.
/// </summary>
/// <remarks>
/// Clients who are unaware of this will still behave correctly, if not
///  particularly performant, by assuming that we have a new instance.
/// Since invalidation operations are synchronous, we can set a bit
///  to maintain this knowledge through the invalidation operation.
/// This would be problematic in cross-thread operations, but the only
///  time DependencyObject can be used across thread in today's design
///  is when it is a Freezable object that has been Frozen.  Frozen
///  means no more changes, which means no more invalidations.
///
/// This is being done as an internal method to enable the performance
///  bug #1114409.  This is candidate for a public API but we can't
///  do that kind of work at the moment.
/// </remarks>
[FriendAccessAllowed] // Built into Base, also used by Framework.
internal void InvalidateSubProperty(DependencyProperty dp)
{
	// when a sub property changes, send a Changed notification 
	// with old and new value being the same, and with 
        // IsASubPropertyChange set to true
	NotifyPropertyChange(new DependencyPropertyChangedEventArgs(dp, 
            dp.GetMetadata(DependencyObjectType), GetValue(dp)));
}

注意函数说明部分,when a sub property changes, send a Changed notification with old and new value being the same, and with IsASubPropertyChange set to true,这完全符合我们的需求!!!这个函数本意是作为 Public API 的,但是由于性能的 bug #1114409,将其内部化了。那么,我可以通过反射去调用它:

{
    this.InvokeInternal<DependencyObject>("NotifySubPropertyChange", new object[] { DpColorProperty });
}


/// <summary>
/// 反射调用指定类型的 Internal 方法。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="caller"></param>
/// <param name="method"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public static object InvokeInternal<T>(this T caller, string method, object[] parameters)
{
	MethodInfo methodInfo = typeof(T).GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic);
	return methodInfo?.Invoke(caller, parameters);
}

好了,完美解决!

猜你喜欢

转载自blog.csdn.net/Liwuqingxin/article/details/81141856