0

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);
         }
     }

 }
7
  • Can you share the code around ModelWrapper<T>? Is it an ObservableObject? Commented Jan 25 at 8:15
  • How does ProgramWrapper implement INotifyPropertyChanged? Is DesktopWrapper supposed to be an instance of ProgramWrapper? Commented Jan 25 at 17:27
  • @GerrySchmitz ProgramWrapper does not implement INotifyPropertyChanged. Yes, DesktopWrapper is an instance of ProgramWrapper. Commented Jan 26 at 9:58
  • @AndrewKeepCoding I shared the code of ModelWrapper.cs. Commented Jan 26 at 9:59
  • 1
    PropertyChanged needs to be invoked but you didn't share the code around it. I guess, NotifyDataErrorsInfoBase implements INotifyPropertyChanged but we can't see it. Commented Jan 27 at 0:33

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.