wpfdata-bindingwindows-community-toolkitrelaycommandcanexecute

In MVVM Community Toolkit VM I load a Person(with 20 observable props), in V I display Person's data. How to enable SaveButton on any data changes?


I have a model

public partial class Patient : GenericEntity
{
    [ObservableProperty]
    private int genderId;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(Crucials))]
    private string lastName = string.Empty;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(Crucials))]
    private string firstName = string.Empty;

    [ObservableProperty]
    private string fatherName = string.Empty;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(Crucials))]
    [NotifyPropertyChangedFor(nameof(Age))]
    private DateTime _DOB = DateTime.Now;

    [NotMapped]
    public int Age
    {
        get
        {
            //DateOnly currentDate = DateOnly.FromDateTime(DateTime.Now);
            DateTime currentDate =DateTime.Now;
            int age = currentDate.Year - DOB.Year;

            // Check if the current date is before the birth date
            if (currentDate < DOB.AddYears(age))
            {
                age--;
            }

            return age;
        }
    }

    [ObservableProperty]
    private string _address = "";
    [ObservableProperty]
    private string _city = "";
    [ObservableProperty]
    private string _country = "";
    [ObservableProperty]
    private string _tel1 = "";
    [ObservableProperty]
    private string _tel2 = "";
    [ObservableProperty]
    private string _email = "";
  
   //(15 more fields are following all of them decorated with [ObservableProperty])
}

The viewmodel is as follows

public partial class PatientViewModel : ObservableObject { private bool _patientPropertyChanged = false;

private AppDbContext _db;

private readonly PatientRepository _patientRepository;
private readonly NATreeNodeRepository _natreenodeRepository;

private Patient? patient;

[ObservableProperty]
private int _patientId = 0;

[RelayCommand(CanExecute = nameof(CanSaveChanges), IncludeCancelCommand = true)]
private async Task SaveChanges(CancellationToken token)
{
    var hasChanges = _db.ChangeTracker.HasChanges();
    if (hasChanges)
    {
        try
        {
            // Update the Patient's registration date to DateTime.Now
            if (Patient != null)
            {
                Patient.UpdatedOn = DateTime.Now;
            }

            await _db.SaveChangesAsync();
            MessageBox.Show("Changes successfully saved to the database!");
        }
        catch (System.Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    MessageBox.Show("Saved changes");
}

}

private bool CanSaveChanges() => PatientPropertyChanged;



public PatientViewModel(int patientId)
{
    _db = new();

    _patientRepository = new PatientRepository(_db);
    _natreenodeRepository = new NATreeNodeRepository(_db);

    PatientId = patientId;

    InitializeAsync(PatientId);

    _patientPropertyChanged = false;
}

private async void InitializeAsync(int patientId)
{
    try
    {
                                                                                        // Retrieve the patient with the given ID from the repository
        Patient = await Task.Run(() => _patientRepository.Get(patientId)); // Replace with your repository method

        // Check if patient is null or doesn't exist
        if (Patient == null)
        {
            Patient = new()
            {
            };
            _patientRepository.Add(Patient);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Error: " + ex.Message);
    }
}

}

The PersonView window contains several controls (one for each of the person's properties) and a, initially disabled, SaveButton bound to a RelayCommand SaveCommand

My question is how can someone enable or disable the save button when any of the Person's properties is changed by the user on this window.

As I am stuck I can not find any solution to this question.

There is a solution if the Person contains 2-3 properties. But when it contains many then I can not come up with a clever solution that could avoid repeating blocks of code

Thanks in advance


Solution

  • Solution 1 - Use Database

    As I understand the capabilities of you Database you may delegate this to your database, as _db.ChangeTracker.HasChanges() seams to check if there are changes. So you may change CanSaveChanges()to

    private bool CanSaveChanges() => _db.ChangeTracker.HasChanges();
    

    Solution 2 - Use PropertyChanged Event

    Otherwise if you database can not check for changes this you may use the PropertyChanged Event like:

    // Properties for Patient and Changed State
    [ObservableProperty] private bool _patientPropertyChanged;
    [ObservableProperty] private Patient _patient;
    
    public bool CanSaveChanges() => PatientPropertyChanged;
    
    private async void InitializeAsync(int patientId)
    {
        try
        {
             if (Patient == null)
            {
                Patient = new()
                {
                };
                _patientRepository.Add(Patient);
            }
    
            //Add Event Handle to set changed
            Patient.PropertyChanged += (s, e) => 
            {
                //You may filter here for relevant changes
                PatientPropertyChanged = true;
            };
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Error: " + ex.Message);
        }
    }
    
    [RelayCommand(CanExecute = nameof(CanSaveChanges), IncludeCancelCommand = true)]
    private async Task SaveChanges(CancellationToken token)
    {
        var hasChanges = _db.ChangeTracker.HasChanges();
        if (hasChanges)
        {
            //..
        }
        //Reset changed Satte
        PatientPropertyChanged = false;
    }