MVVM模式下批量实现对象属性OnPropertyChanged触发方案咨询
我正在用MVVM模式开发,有个包含约50个属性的ClientClass对象。视图里把Picker和Image图标都绑定到Client.ClientType,图标会根据这个属性值变化。但修改ClientType时,OnPropertyChanged没触发,只有给Client重新赋值新的ClientClass实例(比如Client = new ClientClass())才会触发。
我知道可以用包装属性的方式强制触发,代码如下:
public int ClientType { get => client.ClientType; set { if (client.ClientType == value) return; client.ClientType = value; OnPropertyChanged(nameof(Client)); } }
但不想给50个属性都写一遍这种代码。有没有不用逐个声明属性的简便方法实现类似效果?
简化代码示例:
public class BaseVM : INotifyPropertyChanged { bool isBusy; string? title; public bool IsBusy { get => isBusy; set { if (isBusy == value) return; isBusy = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler? PropertyChanged; public void OnPropertyChanged([CallerMemberName] string? name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } public class ClientEditorVM : BaseVM { private ClientClass client; public ClientClass Client{ get => client; set { if (client.Equals(value)) return; client = value; OnPropertyChanged(); } } // 这个方法可行,但要重复写50次? public int ClientType { get => client.ClientType; set { if (client.ClientType == value) return; client.ClientType = value; OnPropertyChanged(nameof(Client)); } } public ClientEditorVM(ClientClass c){ Client = c; } } public class ClientClass { public int ID { get; } public int ClientType { get; set; } public string Name { get; set; } public string Address { get; set; } ... // 约50个属性,没有实现OnPropertyChanged,且无法修改这个类 }
方法1:创建ClientClass的代理包装类(自动生成属性)
既然不能修改ClientClass,可以写一个继承INotifyPropertyChanged的代理类,内部持有ClientClass实例,再用代码生成工具(比如T4模板、Roslyn代码生成)自动遍历ClientClass的所有属性,生成对应的包装代码,避免手动写50次。
示例框架
public class ClientProxy : INotifyPropertyChanged { private readonly ClientClass _originalClient; public ClientProxy(ClientClass original) { _originalClient = original; } // 以ClientType为例,其他属性由工具自动生成 public int ClientType { get => _originalClient.ClientType; set { if (_originalClient.ClientType == value) return; _originalClient.ClientType = value; OnPropertyChanged(nameof(ClientType)); } } // 其他属性的包装代码自动生成... public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
ViewModel改造
把原Client属性替换为ClientProxy类型:
public class ClientEditorVM : BaseVM { private ClientProxy _client; public ClientProxy Client{ get => _client; set { if (_client?.OriginalClient.Equals(value.OriginalClient) ?? false) return; _client = value; OnPropertyChanged(); } } public ClientEditorVM(ClientClass c){ Client = new ClientProxy(c); } }
之后视图绑定Client.ClientType时,属性变更会直接触发通知,且无需手动编写所有属性的包装代码。
方法2:用动态代理拦截属性变更
借助动态代理库(比如Castle DynamicProxy),创建ClientClass的代理对象,拦截所有属性的set操作,自动触发ViewModel的Client属性变更通知。
拦截器实现
using Castle.DynamicProxy; public class ClientPropertyInterceptor : IInterceptor { private readonly BaseVM _viewModel; public ClientPropertyInterceptor(BaseVM viewModel) { _viewModel = viewModel; } public void Intercept(IInvocation invocation) { // 只拦截属性的set方法 if (invocation.Method.Name.StartsWith("set_")) { invocation.Proceed(); // 触发ViewModel中Client属性的变更通知 _viewModel.OnPropertyChanged(nameof(ClientEditorVM.Client)); } else { invocation.Proceed(); } } }
ViewModel改造
public class ClientEditorVM : BaseVM { private ClientClass _client; public ClientClass Client{ get => _client; set { // 为原始Client对象创建代理 var proxyGenerator = new ProxyGenerator(); _client = proxyGenerator.CreateClassProxyWithTarget(value, new ClientPropertyInterceptor(this)); OnPropertyChanged(); } } public ClientEditorVM(ClientClass c){ Client = c; } }
这样不管修改Client的哪个属性,代理都会自动拦截并触发通知,视图绑定的内容会同步更新。
方法3:零散修改场景下的手动触发
如果修改Client属性的代码位置不多,可以在每次修改后手动调用通知:
// 修改ClientType后触发通知 Client.ClientType = newType; OnPropertyChanged(nameof(Client));
但此方法仅适合修改点较少的场景,修改点多的话还是推荐前两种方案。
内容的提问来源于stack exchange,提问作者Abelucho

