ViewModel 通过 DataContext 绑定到 View,并依赖 INotifyPropertyChanged 通知 UI 更新;列表需用 ObservableCollection 并确保嵌套对象也实现该接口;Command 应用 RelayCommand 并正确触发 CanExecuteChanged;数据操作应由 Service 层或 ViewModel 承担,Model 仅作数据契约。

ViewModel 怎么和 View 绑定(WPF)
绑定不是靠“写对名字”就行,而是靠 DataContext 和属性通知机制。WPF 的绑定默认只响应 INotifyPropertyChanged 触发的变更,直接改字段值或普通属性 setter 不会刷新界面。
常见错误现象:TextBlock 显示初始值,但后续改了 ViewModel 里的 UserName 属性,UI 就不动;或者用了 async 方法更新属性,结果 UI 更新延迟甚至不触发。
- 确保 ViewModel 类实现
INotifyPropertyChanged,且每个可绑定属性的 setter 里调用OnPropertyChanged() - 不要在构造函数里直接赋值后就认为绑定已生效——得等
DataContext被设到 View 上之后,再改值才有效 - 避免在异步回调里直接改属性:用
Application.Current.Dispatcher.Invoke()或把属性更新逻辑包进await Task.Run()后的同步上下文里(取决于你是否需要 UI 线程安全)
Command 怎么写才不会崩(ICommand + RelayCommand)
ICommand 接口看着简单,但漏掉 CanExecuteChanged 事件或没正确触发它,就会导致按钮一直灰着、点不动,或者点了没反应。
使用场景:登录按钮要等用户名密码非空才启用;保存按钮要在数据有修改时才可用;删除按钮需选中项才激活。
- 别手写裸
ICommand实现——用现成的RelayCommand(比如CommunityToolkit.Mvvm里的)更稳,它自动处理CanExecuteChanged的订阅和触发 -
CanExecute方法必须是纯函数:不能依赖外部状态变化而不通知,否则 UI 不会重算启用状态 - 如果命令执行的是异步操作(如调 API),别让
Execute返回Task——ICommand.Execute是 void,得自己用Task.Run包一层,或用支持 async 的扩展(如AsyncRelayCommand)
Model 和 ViewModel 到底谁该碰数据库(或 API)
Model 是数据契约,不是工具人。它不该包含 SaveToDb()、FetchFromApi() 这类行为方法,也不该引用 HttpClient 或 EF Core 的 DbContext。
容易踩的坑:把 Model 当成“实体类+业务逻辑”,结果导致单元测试没法 mock,ViewModel 里混着数据获取和转换逻辑,一改需求就得动三处。
- 数据获取统一交给 ViewModel 或独立的 Service 层(如
IUserService),ViewModel 只负责调用并把结果映射成可绑定属性 - Model 保持只读或仅含基本验证(如
[Required]),复杂校验逻辑放 ViewModel 或专门的 Validator 类 - 如果 Model 来自 API 响应(如
UserDto),别直接把它塞给 View 绑定——它可能缺INotifyPropertyChanged,也未必有 UI 需要的状态字段(比如IsLoading)
为什么改了属性 UI 没更新(INotifyCollectionChanged 陷阱)
单个属性通知了,列表却不动?大概率你用了 List 或数组,而不是 ObservableCollection。
性能影响:每次增删项都触发 UI 重绘,大数据量下卡顿明显;但不用它,绑定根本收不到变化通知。
-
ObservableCollection是 WPF 绑定列表的底线要求,BindingList在 WinForms 里行,在 WPF 里不被识别 - 别在 ViewModel 里暴露
public ObservableCollection然后在外面 add/remove——这会绕过通知机制;所有修改必须走这个实例本身的方法Users { get; } - 如果需要排序或过滤,别手动重赋值整个集合(比如
Users = new ObservableCollection),会丢失引用、打断绑定;改用(sorted) CollectionViewSource或第三方库如DynamicData
最常被忽略的一点:ViewModel 层级嵌套时,子对象也要实现 INotifyPropertyChanged,否则改了 User.Name,父级 Users[0].Name 绑定的 TextBlock 还是静止的。











