Reputation: 537
I'm working this bug in a Winui 3 application. As you can see in the following code example, when check/uncheck the "DefaultProgramCheckBox", the SetValue of Variable "DefaultProgram" is called, but PropertyChanged event is not triggered and captured by the ProcessPropertyChanged() in the ViewModel, any idea what is the root cause of this problem. Thanks.
Here is CheckBox in the UI. (A Page).
<CheckBox x:Name="DefaultProgramCheckBox"
Content="Make this the default program"
IsChecked="{x:Bind ViewModel.DesktopWrapper.DefaultProgram, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Here is the code in ViewModel.cs
//Main model
[ObservableProperty]
private ProgramWrapper _desktopWrapper = new(new Program());
public ViewModel()
{
LoadAsync();
PropertyChanged += ProcessPropertyChanged;
DesktopWrapper.PropertyChanged += ProcessPropertyChanged;
}
private void ProcessPropertyChanged(object sender, PropertyChangedEventArgs e)
{
//PropertyChangedEvent not captured here when check/uncheck the checkbox
if (e.PropertyName == nameof(DesktopWrapper.DefaultProgram))
ShowDefaultProgramMessage();
}
Here is the ProgramWrapper.cs:
public class ProgramWrapper : ModelWrapper<Program>
{
public ProgramWrapper(Program model) : base(model)
{
}
public bool DefaultProgram
{
get { return GetValue<bool>(); }
set { SetValue(value); } //When check/check the checkbox, SetValue is called.
}
}
Here is 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;
}
}
Here is 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);
}
}
}
Upvotes: 0
Views: 43