wpfcommunity-toolkit-mvvm

Handle NotifyCanExecuteFor when using Model as ObservableProperty in MVVM Toolkit ViewModel


I am using MVVM Toolkit and I am wondering if I could use separate UI models (duplicates of domain models defined in the UI Layer only) and then use those in my ViewModels keeping all the functionality. My main issue is NotifyCanExecuteChangedFor.

The UI model would look something like this:

public partial class ProjectUI: ObservableObject

public int Id { get; set; }

[ObservableProperty]
private ClientModel _client;

[ObservableProperty]
private string _name;

[ObservableProperty]
private DateTime? _startDate;

'... etc

And then in the ViewModel

public partial class ViewModel

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(CanExecute))]
    [NotifyCanExecuteChangedFor(nameof(ExecuteCommand))]
    private ProjectUI _project;

    private CanExecute => Project.Client != null &&
                          !string.IsNullOrEmpty(Project.Name) && 
                          Project.StartDate != null;

    [RelayCommand(CanExecute = nameof(CanExecute))]
    private async void Execute()
    '...Code that does stuff [not important]

All data bindings work and the properties update as intended. But NotifyCanExecuteChangedFor does not get triggered and the xaml buttons remain greyed out even though all ViewModel properties have the values they should. i.e. All Project properties match the UI and CanExecute = true.

I suppose this is because the property that needs to trigger NotifyCanExecuteChangedFor in ProjectUI but I obviously can't change that.

The weird thing about this is that if I start with CanExecute = true (edit an existing project) all the buttons work. If I delete a value to change CanExecute = false the buttons do not get greyed out, but they stop working (i.e. NotifyCanExecuteChangedFor kind of worked as the issue is "just the color of the buttons"). But if I add a new project and the buttons are greyed out, then they simply wont work even when CanExecute = true (i.e. NotifyCanExecuteChangedFor completely fails)

Any way to resolve this or am I just doing things wrong from a design standpoint?


Solution

  • Got this to work with help from this post. I also added a HasValidData property to ProjectUI as it should self-validate itself. Posting full code that works.

    ProjectUI:

    public partial class ProjectUI: ObservableObject
    
    public int Id { get; set; }
    
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasValidData))]
    private ClientModel _client;
    
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasValidData))]
    private string _name;
    
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(HasValidData))]
    private DateTime? _startDate;
    
    public bool HasValidData => Client != null &&
                                !string.IsNullOrEmpty(Name) && 
                                StartDate != null;
    

    For the ViewModel, CanExectue needs to be an ObservableProperty that gets set via the ProperyChanged Event. Add an Event Handler to the constructor or an OnLoaded method. Make sure that Project object is initialized else you will get an exception

    public partial class ViewModel
    
        [ObservableProperty]
        private ProjectUI _project;
        
        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(ExecuteCommand))]
        private _canExecute;
    
    ' It would be better if this was behind a RelayCommand bound to the Views Loaded Event. 
    But this is the easiest way to demonstrate how to solve the problem I am facing.'
    
        public ViewModel()
        {
            Project = new ProjectUI();
            Project.PropertyChanged += (s, e) =>
            {
                CanExecute = Project.HasValidData; 
            };
    
        }
    
    
        [RelayCommand(CanExecute = nameof(CanExecute))]
        private async void Execute()
        '...Code that does stuff [not important]