MVVM模式下多Load实现类的NotifyPropertyChanged与访问器实现咨询
多类型载荷的MVVM规范实现方案
核心思路:抽象分层+泛型基类统一管理
要搞定这个问题,关键是把纯数据的Model和带交互/通知的ViewModel彻底分开,同时通过抽象基类统一处理多类型载荷的共性逻辑,避免重复代码。
1. 先给Model做减法:只保留纯数据
所有载荷类(SurfaceLoad、PointLoad等)只负责存储数据,绝对不碰任何交互或通知逻辑。先定义一个抽象基类统一共性属性:
public abstract class Load { // 通用数据属性:比如载荷大小、方向 public double Magnitude { get; set; } public LoadDirection Direction { get; set; } } // 具体载荷类只加专属数据 public class SurfaceLoad : Load { public double AppliedArea { get; set; } } public class PointLoad : Load { public double XPosition { get; set; } public double YPosition { get; set; } }
LoadContainer也保持原样,只负责管理Load实例的集合,不涉及任何UI相关逻辑。
2. 构建ViewModel抽象基类:封装共性通知逻辑
创建一个泛型抽象基类,继承INotifyPropertyChanged,把所有通用的属性访问器和通知逻辑都放在这里,具体ViewModel只需要实现专属属性:
public abstract class LoadViewModelBase<TLoad> : INotifyPropertyChanged where TLoad : Load, new() { protected readonly TLoad _model; public event PropertyChangedEventHandler PropertyChanged; // 支持传入已有Model或新建Model protected LoadViewModelBase() => _model = new TLoad(); protected LoadViewModelBase(TLoad existingModel) => _model = existingModel ?? new TLoad(); // 通用通知方法 protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } // 封装通用属性的访问器,带变更通知 public double Magnitude { get => _model.Magnitude; set { if (_model.Magnitude != value) { _model.Magnitude = value; OnPropertyChanged(); } } } public LoadDirection Direction { get => _model.Direction; set { if (_model.Direction != value) { _model.Direction = value; OnPropertyChanged(); } } } // 暴露底层Model,方便和LoadContainer同步 public TLoad Model => _model; }
3. 具体载荷ViewModel:实现专属属性
每个具体载荷对应一个ViewModel,继承抽象基类后,只需要实现自己的专属属性访问器即可:
public class SurfaceLoadViewModel : LoadViewModelBase<SurfaceLoad> { public SurfaceLoadViewModel() : base() { } public SurfaceLoadViewModel(SurfaceLoad model) : base(model) { } public double AppliedArea { get => _model.AppliedArea; set { if (_model.AppliedArea != value) { _model.AppliedArea = value; OnPropertyChanged(); } } } } public class PointLoadViewModel : LoadViewModelBase<PointLoad> { public PointLoadViewModel() : base() { } public PointLoadViewModel(PointLoad model) : base(model) { } public double XPosition { get => _model.XPosition; set { if (_model.XPosition != value) { _model.XPosition = value; OnPropertyChanged(); } } } public double YPosition { get => _model.YPosition; set { if (_model.YPosition != value) { _model.YPosition = value; OnPropertyChanged(); } } } }
4. LoadContainer的ViewModel适配:管理ViewModel集合
为LoadContainer创建对应的ViewModel,负责把Model集合转换成ViewModel集合,同时处理集合变化的同步:
public class LoadContainerViewModel : INotifyPropertyChanged { private readonly LoadContainer _modelContainer; private ObservableCollection<LoadViewModelBase<Load>> _loadViewModels; public event PropertyChangedEventHandler PropertyChanged; public LoadContainerViewModel(LoadContainer container) { _modelContainer = container; // 把现有Model批量转换成ViewModel _loadViewModels = new ObservableCollection<LoadViewModelBase<Load>>( _modelContainer.Loads.Select(ConvertModelToViewModel) ); // 监听ViewModel集合变化,同步回Model容器 _loadViewModels.CollectionChanged += LoadViewModels_CollectionChanged; } public ObservableCollection<LoadViewModelBase<Load>> LoadViewModels { get => _loadViewModels; set { if (_loadViewModels != value) { _loadViewModels = value; OnPropertyChanged(); } } } // Model转ViewModel的工厂方法,处理多类型匹配 private LoadViewModelBase<Load> ConvertModelToViewModel(Load model) { return model switch { SurfaceLoad sl => new SurfaceLoadViewModel(sl), PointLoad pl => new PointLoadViewModel(pl), AreaLoad al => new AreaLoadViewModel(al), _ => throw new ArgumentOutOfRangeException(nameof(model), "未知载荷类型") }; } private void LoadViewModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // 同步新增/删除的ViewModel到Model容器 if (e.NewItems != null) { foreach (var vm in e.NewItems.Cast<LoadViewModelBase<Load>>()) { _modelContainer.Loads.Add(vm.Model); } } if (e.OldItems != null) { foreach (var vm in e.OldItems.Cast<LoadViewModelBase<Load>>()) { _modelContainer.Loads.Remove(vm.Model); } } } protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
额外优化小技巧
- 加入属性验证:在ViewModel的setter里可以加业务逻辑验证,比如载荷大小不能为负,验证不通过就不更新Model也不触发通知。
- 统一命令处理:如果需要对载荷执行删除、复制等操作,可以在ViewModel基类里添加
ICommand属性,统一管理交互命令。 - 简化映射:如果Model和ViewModel属性很多,可以用AutoMapper这类工具自动转换,减少重复的属性包装代码。
内容的提问来源于stack exchange,提问作者Leon Bohmann
相关产品推荐
相关产品推荐

