直接继承 INotifyPropertyChanged 不够用,因其仅声明接口而无自动触发机制,需手动调用 OnPropertyChanged 且易因拼写错误或遗漏导致 UI 不更新且编译器不报错;现代推荐使用 ObservableObject 或封装 SetProperty 等方法规避硬编码与线程问题。

为什么直接继承 INotifyPropertyChanged 不够用
因为光有接口声明没触发机制,PropertyChanged 事件必须手动触发,漏写或写错属性名(比如拼成 "Namee")会导致 UI 完全不更新,且编译器不报错。现代写法推荐用 ObservableObject(来自 CommunityToolkit.Mvvm)或自己封装基类,自动处理字符串硬编码风险。
- 别手写
OnPropertyChanged("PropertyName")—— 改用OnPropertyChanged()无参重载,靠[CallerMemberName]自动推导 - 属性 setter 里别只写
if (_name != value) {_name = value; OnPropertyChanged(); }—— 要加SetProperty(ref _name, value)这类封装,避免漏掉通知 - 第三方库如
CommunityToolkit.Mvvm的ObservableObject已内置线程安全检查,直接继承即可,不用再手写事件委托逻辑
RelayCommand 和 ICommand 绑定失败的常见原因
UI 上按钮点击没反应,大概率不是 XAML 写错了,而是命令对象生命周期或执行条件出问题。WPF/UWP/MAUI 中 ICommand 必须是属性(不能是字段),且初始化时机早于绑定发生,否则视图拿不到有效引用。
-
RelayCommand构造时传入的执行方法不能为null,否则绑定后点击直接静默失败 —— 加个空检查或用带 canExecute 参数的重载 - 如果命令依赖 ViewModel 状态(比如“保存”需
IsDirty == true),记得在状态变更时调用command.NotifyCanExecuteChanged(),否则按钮不会变灰 / 启用 - MAUI 中
RelayCommand默认不支持异步,要用AsyncRelayCommand;WPF 若在命令里 await,得确保没阻塞 UI 线程,否则界面卡死
View 和 ViewModel 之间怎么传参才不耦合
ViewModel 不该知道具体控件类型(比如 Button 或 ListView),也不该引用 System.Windows.Controls。传参只能靠数据驱动:通过绑定属性、命令参数(CommandParameter)、或者导航参数(MAUI 的 Shell.GoToAsync 或 WPF 的自定义导航服务)。
- XAML 中
CommandParameter="{Binding SelectedItem}"是安全的,但别写CommandParameter="{Binding ElementName=myList, Path=SelectedItem}"—— 这会让 ViewModel 间接依赖 View 结构 - 跨页面传参时,别在 ViewModel 构造函数里直接接收
Frame或NavigationService实例 —— 应通过接口抽象(如INavigationService)注入,方便单元测试和替换实现 - 如果需要响应 View 生命周期(如页面出现 / 消失),别监听
Loaded事件,改用 ViewModel 中的OnAppearing/OnDisappearing方法(MAUI)或自定义消息(WPF 用WeakReferenceMessenger)
调试时发现属性变了但 UI 没刷新,先查这三处
不是代码逻辑错,而是 MVVM 的“隐性契约”被破坏了:通知、线程、绑定路径三者缺一不可。尤其容易忽略的是线程上下文 —— 在后台线程修改属性后,即使触发了 PropertyChanged,WPF/MAUI 也不会自动调度到 UI 线程更新。
- 用 Visual Studio 的“数据绑定失败”输出窗口(启用
PresentationTraceSources.TraceLevel)看绑定路径是否解析成功,常见错误如System.Windows.Data Error: 40 : BindingExpression path error: 'UserName' property not found - 属性 setter 中打日志,确认赋值确实发生了;再在
OnPropertyChanged里加断点,看事件有没有被订阅(PropertyChanged?.Invoke(……)前先判空) - 后台线程更新属性时,WPF 要用
Application.Current.Dispatcher.Invoke(……),MAUI 要用MainThread.InvokeOnMainThreadAsync(……)—— 少这一句,UI 就永远卡在旧值
真正难的不是写对语法,而是让所有环节都在同一套上下文里协作:属性通知要准,命令状态要同步,线程切换要显式,绑定路径要可追溯。漏掉任意一环,MVVM 就退化成“看起来像”的手动刷新。