WPF/Prism 中计算属性的通知机制详解 —— SetProperty 与 RaisePropertyChanged
WPF/Prism 中计算属性的通知机制详解 —— SetProperty 与 RaisePropertyChanged
在 WPF + MVVM 的开发中,我们经常会遇到 计算属性依赖其他属性 的场景。
比如下面这个例子,Key
是由 ModeFullPath
和 SerialNumber
拼接出来的:
public string Key
{get{var name = Path.GetFileNameWithoutExtension(ModeFullPath);return $"{name}_{SerialNumber}";}
}
前台 UI 绑定的是 Key
,但它并不是一个独立的字段,而是依赖 ModeFullPath
和 SerialNumber
。
问题来了:当依赖属性变化时,如何让绑定到 Key
的 UI 也自动刷新?
一、经典实现(INotifyPropertyChanged)
如果直接用 INotifyPropertyChanged
,代码会是这样的:
private string modeFullPath;
public string ModeFullPath
{get => modeFullPath;set{if (modeFullPath != value){modeFullPath = value;OnPropertyChanged(nameof(ModeFullPath));OnPropertyChanged(nameof(Key)); // 额外通知 Key 也变了}}
}private string serialNumber;
public string SerialNumber
{get => serialNumber;set{if (serialNumber != value){serialNumber = value;OnPropertyChanged(nameof(SerialNumber));OnPropertyChanged(nameof(Key));}}
}public string Key => $"{Path.GetFileNameWithoutExtension(ModeFullPath)}_{SerialNumber}";
思路很清晰:依赖属性变化时,额外调用一次 OnPropertyChanged(nameof(Key))
。
二、Prism 的写法(SetProperty + RaisePropertyChanged)
Prism 的 BindableBase
提供了 SetProperty
和 RaisePropertyChanged
两个辅助方法,让代码更简洁。
private string modeFullPath = string.Empty;
public string ModeFullPath
{get => modeFullPath;set{if (SetProperty(ref modeFullPath, value))RaisePropertyChanged(nameof(Key));}
}private string serialNumber = string.Empty;
public string SerialNumber
{get => serialNumber;set{if (SetProperty(ref serialNumber, value))RaisePropertyChanged(nameof(Key));}
}public string Key => $"{Path.GetFileNameWithoutExtension(ModeFullPath)}_{SerialNumber}";
这样就能确保:
ModeFullPath
和SerialNumber
更新时,UI 会刷新自身绑定;Key
作为计算属性,也会自动刷新。
三、SetProperty 和 RaisePropertyChanged 的区别
很多人刚接触 Prism 的时候会疑惑:这两个方法有什么不同?
方法 | 作用 | 是否修改字段 | 是否触发通知 |
---|---|---|---|
SetProperty | 比较新值和旧值,不同则赋值,并触发通知 | ✅ 是 | ✅ 是 |
RaisePropertyChanged | 只触发通知,不修改字段 | ❌ 否 | ✅ 是 |
内部关系可以理解为:
SetProperty(ref field, value)
// 等价于
if (!EqualityComparer<T>.Default.Equals(field, value))
{field = value;RaisePropertyChanged(nameof(Property));return true;
}
return false;
所以:
- 对于普通属性:用
SetProperty
就够了。 - 对于计算属性:在依赖属性的 setter 中额外调用
RaisePropertyChanged(nameof(计算属性))
。
四、总结
-
计算属性不会自己触发更新,必须在依赖属性改变时额外通知。
-
在 Prism 中,写法推荐是:
if (SetProperty(ref modeFullPath, value))RaisePropertyChanged(nameof(Key));
-
SetProperty
用来做字段赋值 + 自动通知;
RaisePropertyChanged
只负责通知,常用于计算属性的依赖更新。
这样,你的 WPF/Prism 项目里就能优雅地处理计算属性刷新问题,避免 UI 不更新的尴尬。
✨ 如果以后计算属性很多,想减少手写 RaisePropertyChanged(nameof(...))
的重复代码,还可以考虑 扩展基类,自动处理依赖关系。这个我可以另开一篇文章专门讲。
后续
SerialNumber 这个改变确实生效了,但是有个属性是YoloConfig.ModeFullPath,这个没有生效
SerialNumber
生效,而 yoloConfig.ModeFullPath
没生效,这里面有个关键点:
现在绑定的 Key
是这样依赖的:
public string Key => $"{Path.GetFileNameWithoutExtension(yoloConfig.ModeFullPath)}_{SerialNumber}";
区别在于:
SerialNumber
是 你这个 ViewModel 自己的属性,你在set
里写了RaisePropertyChanged(nameof(Key))
,所以 UI 会更新。yoloConfig.ModeFullPath
是 另一个对象的属性,当它变化时,你这个 ViewModel 并不会自动收到通知。
🔎 为什么会这样?
INotifyPropertyChanged
的机制只会通知 当前对象自己的属性。
你在 yoloConfig.ModeFullPath
改变时,并没有告诉外层的 ViewModel:“嘿,Key
也变了”。
所以 UI 不知道要刷新。
✅ 解决方案
直接把ModeFullPath放到外层!
🚀 总结
SerialNumber
更新能生效,是因为它在当前 VM 内,SetProperty
已经帮你 Raise 了。YoloConfig.ModeFullPath
更新没生效,是因为它是 嵌套对象的属性,你需直接把他放到外层!