WinUI 3中PropertyChanged事件未触发:Checkbox绑定异常排查
勾选或取消勾选名为DefaultProgramCheckBox的复选框时,ProgramWrapper中DefaultProgram属性的SetValue方法已被调用,但ViewModel中的ProcessPropertyChanged方法未能捕获到对应的PropertyChanged事件。以下是相关代码及问题分析:
UI页面CheckBox代码
<CheckBox x:Name="DefaultProgramCheckBox" Content="设为默认程序" IsChecked="{x:Bind ViewModel.DesktopWrapper.DefaultProgram, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
ViewModel.cs代码
//主模型 [ObservableProperty] private ProgramWrapper _desktopWrapper = new(new Program()); public ViewModel() { LoadAsync(); PropertyChanged += ProcessPropertyChanged; DesktopWrapper.PropertyChanged += ProcessPropertyChanged; } private void ProcessPropertyChanged(object sender, PropertyChangedEventArgs e) { //勾选/取消勾选复选框时,未捕获到PropertyChanged事件 if (e.PropertyName == nameof(DesktopWrapper.DefaultProgram)) ShowDefaultProgramMessage(); }
ProgramWrapper.cs代码
public class ProgramWrapper : ModelWrapper<Program> { public ProgramWrapper(Program model) : base(model) { } public bool DefaultProgram { get { return GetValue<bool>(); } set { SetValue(value); } //勾选/取消勾选复选框时,此方法被调用 } }
ModelWrapper.cs代码
public class ModelWrapper<T> : NotifyDataErrorsInfoBase { public T Model { get; set; } public ModelWrapper(T model) { Model = model; } protected virtual TValue GetValue<TValue>( [CallerMemberName] string propertyName = null) { return (TValue)typeof(T).GetProperty(propertyName).GetValue(Model); } protected virtual void SetValue<TValue>( TValue value, [CallerMemberName] string propertyName = null) { typeof(T).GetProperty(propertyName).SetValue(Model, value); OnPropertyChanged(propertyName); ValidatePropertyInternal(propertyName, value); } private void ValidatePropertyInternal(string propertyName, object currentValue) { ClearErrors(propertyName); ValidateDataAnnotations(propertyName, currentValue); ValidateCustomErrors(propertyName); } private void ValidateCustomErrors(string propertyName) { var errors = ValidateProperty(propertyName); if (errors != null) { foreach (var error in errors) { AddError(propertyName, error); } } } private void ValidateDataAnnotations(string propertyName, object currentValue) { var context = new ValidationContext(Model) { MemberName = propertyName }; var results = new List<ValidationResult>(); Validator.TryValidateProperty(currentValue, context, results); foreach (var result in results) { AddError(propertyName, result.ErrorMessage); } } protected virtual IEnumerable<string> ValidateProperty(string propertyName) { return null; } }
NotifyDataErrorsInfoBase.cs代码
public class NotifyDataErrorsInfoBase : ModelBase, INotifyDataErrorInfo { private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>(); public bool HasErrors => _errors.Any(); public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; public IEnumerable GetErrors(string propertyName) { return _errors.ContainsKey(propertyName) ? _errors[propertyName] : null; } public Dictionary<string, List<string>> GetAllErrors() { return _errors; } protected virtual void OnErrorsChanged(string propertyName) { ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); base.OnPropertyChanged(nameof(HasErrors)); } protected void AddError(string propertyName, string error) { if (!_errors.ContainsKey(propertyName)) { _errors[propertyName] = new List<string>(); } if (!_errors[propertyName].Contains(error)) { _errors[propertyName].Add(error); OnErrorsChanged(propertyName); } } protected void ClearErrors(string propertyName) { if (_errors.ContainsKey(propertyName)) { _errors.Remove(propertyName); OnErrorsChanged(propertyName); } } }
问题根源分析
核心接口缺失:
ModelWrapper继承链中的ModelBase未实现INotifyPropertyChanged接口。虽然ModelWrapper的SetValue方法调用了OnPropertyChanged,但如果ModelBase没有定义PropertyChanged事件及正确实现OnPropertyChanged逻辑,事件根本不会被触发。事件订阅失效风险:ViewModel构造函数中直接订阅初始
DesktopWrapper实例的PropertyChanged事件,但DesktopWrapper是ObservableProperty生成的属性。如果后续LoadAsync或其他逻辑重新赋值DesktopWrapper,旧实例的订阅会失效,新实例的事件未被订阅,导致无法捕获后续变更。PropertyName匹配验证:虽然
nameof(DesktopWrapper.DefaultProgram)结果是"DefaultProgram",与实际触发的属性名一致,但前提是事件能正常触发。
修复方案
1. 补全INotifyPropertyChanged实现
确保ModelBase实现INotifyPropertyChanged接口:
using System.ComponentModel; using System.Runtime.CompilerServices; public class ModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
2. 动态订阅属性变更事件
利用ObservableProperty的自动生成的变更回调,在DesktopWrapper实例更换时重新订阅事件:
[ObservableProperty] private ProgramWrapper _desktopWrapper = new(new Program()); // ObservableProperty会自动生成此部分代码,只需实现方法体 partial void OnDesktopWrapperChanged(ProgramWrapper oldValue, ProgramWrapper newValue) { // 取消旧实例订阅 if (oldValue != null) oldValue.PropertyChanged -= ProcessPropertyChanged; // 订阅新实例 if (newValue != null) newValue.PropertyChanged += ProcessPropertyChanged; }
3. 验证事件触发逻辑
在ModelWrapper的SetValue方法中添加日志或断点,确认OnPropertyChanged被调用时传递的propertyName是否正确为"DefaultProgram"。
内容的提问来源于stack exchange,提问作者Ming

