C# WPF MVVM模式下多ViewModel与共享Model双向同步方案咨询
推荐实现方案
1. 核心优化思路:让Model层自带变更通知能力
当前架构的核心问题是Model类为无通知的哑类,才需要额外维护ViewModel和Model的双向同步逻辑,以及多ViewModel间的同步逻辑。首先改造Model层,让其实现INotifyPropertyChanged接口:
using System.ComponentModel; using System.Runtime.CompilerServices; public class SampleModel : INotifyPropertyChanged { private string _material; public string Material { get => _material; set { if (_material == value) return; _material = value; OnPropertyChanged(); } } private double _weight; public double Weight { get => _weight; set { if (_weight == value) return; _weight = value; OnPropertyChanged(); } } private double _length; public double Length { get => _length; set { if (_length == value) return; _length = value; OnPropertyChanged(); } } private double _width; public double Width { get => _width; set { if (_width == value) return; _width = value; OnPropertyChanged(); } } private double _height; public double Height { get => _height; set { if (_height == value) return; _height = value; OnPropertyChanged(); } } private object _sharedProperty; public object SharedProperty { get => _sharedProperty; set { if (Equals(_sharedProperty, value)) return; _sharedProperty = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
2. 简化ViewModel实现,消除循环调用
在ViewModel基类中增加初始化状态标志,即可解决构造函数调用链的循环问题,不需要在每个同步方法中写重复的相等判断。同时统一订阅Model的变更事件,自动同步ViewModel属性:
using System.ComponentModel; using System.Runtime.CompilerServices; public abstract class AbstractViewModel : INotifyPropertyChanged { public SampleModel Instance { get; } private bool _isInitializing; public event PropertyChangedEventHandler PropertyChanged; public AbstractViewModel(SampleModel instance) { Instance = instance; _isInitializing = true; SyncViewModel(); _isInitializing = false; // 订阅Model的变更事件,Model更新后自动同步到当前ViewModel Instance.PropertyChanged += OnModelPropertyChanged; } private void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e) { // 只同步当前ViewModel关心的属性,可由子类重写实现自定义逻辑 SyncViewModel(e.PropertyName); } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); // 初始化阶段跳过同步到Model的逻辑,避免循环 if (!_isInitializing) { SyncModel(propertyName); } } // 改为按需同步单属性,不需要每次全量同步所有属性 protected abstract void SyncModel(string propertyName); protected abstract void SyncViewModel(string propertyName = null); }
3. 子类ViewModel简化实现
public class SampleViewModelA : AbstractViewModel { public string Material { get => _material; set { if (_material == value) return; _material = value; OnPropertyChanged(); } } public double Weight { get => _weight; set { if (_weight == value) return; _weight = value; OnPropertyChanged(); } } public object SharedProperty { get => _sharedProperty; set { if (Equals(_sharedProperty, value)) return; _sharedProperty = value; OnPropertyChanged(); } } public SampleViewModelA(SampleModel instance) : base(instance) { } protected override void SyncModel(string propertyName) { switch (propertyName) { case nameof(Material): Instance.Material = Material; break; case nameof(Weight): Instance.Weight = Weight; break; case nameof(SharedProperty): Instance.SharedProperty = SharedProperty; break; } } protected override void SyncViewModel(string propertyName = null) { if (propertyName is null or nameof(Material)) Material = Instance.Material; if (propertyName is null or nameof(Weight)) Weight = Instance.Weight; if (propertyName is null or nameof(SharedProperty)) SharedProperty = Instance.SharedProperty; } private string _material; private double _weight; private object _sharedProperty; }
SampleViewModelB按照同样逻辑实现自己关心的Length、Width、Height和SharedProperty即可。
4. 多ViewModel自动同步逻辑说明
所有ViewModel持有同一个Model实例,任意ViewModel修改属性后同步到Model,Model触发变更通知,其他订阅了该Model的ViewModel会自动同步对应属性,不需要额外的控制器、全局事件订阅逻辑,也不会出现触发源订阅自己触发的事件的问题。
如果存在跨多个Model的共享属性,可以单独实现一个全局状态单例类,同样实现INotifyPropertyChanged,需要用到该属性的ViewModel直接订阅这个全局状态类的变更事件即可。
额外简化方案(无特殊业务需求可直接使用)
如果ViewModel不需要对属性做额外的业务校验、转换处理,完全可以取消ViewModel的私有字段,直接包装Model的属性,连同步逻辑都可以完全省略:
public string Material { get => Instance.Material; set { Instance.Material = value; OnPropertyChanged(); } }
这种实现下,所有ViewModel的属性直接绑定到同一个Model的属性,修改会实时同步到所有关联的ViewModel,不需要任何额外的同步代码,维护成本极低。
内容的提问来源于stack exchange,提问作者user23623447

