Home > Software design >  Simplifying ViewModel properties in an MVVM design
Simplifying ViewModel properties in an MVVM design

Time:02-05

I am new to MVVM, and I believe I have successfully built an MVVM app to appreciate it. I soon realised that I could not have properties like below to fire notifications:

public string Status { get; set; }
public bool IsIdle { get; set; }
public ObservableCollection<SpecifiedRecord> FilesCollection { get; set; }

I had to rewrite them as below:

private string _status;
public string Status
{
    get { return _status; }
    set
    {
        _status = value;
        OnPropertyChanged(nameof(Status));
    }
}

private bool _isIdle;
public bool IsIdle
{
    get { return _isIdle; }
    set
    {
        _isIdle = value;
        OnPropertyChanged(nameof(IsIdle));
    }
}

private ObservableCollection<SpecifiedRecord> filesColl = new ObservableCollection<SpecifiedRecord>();
public ObservableCollection<SpecifiedRecord> FilesCollection
{
    get { return filesColl; }
    set
    {
        if (value != this.filesColl)
            filesColl = value;
        OnPropertyChanged(nameof(FilesCollection));
    }
}

MVVM has been there for nearly 9 years, and I thought in this day and age, Microsoft would allow OnPropertyChanged events to fire automatically i.e. built-in to Net without us having to write it everytime because if I find it very inefficient to do so.

Alternatively, is there a more simplified way to achieve the same by way of an inherited class?

Sources: https://github.com/mainroads/SpecifiedRecordsExporter/blob/mvvm/SpecifiedRecordsExporter/MVVM/ViewModels/MainPageViewModel.cs https://github.com/mainroads/SpecifiedRecordsExporter/blob/mvvm/SpecifiedRecordsExporter/MVVM/ViewModels/ViewModelBase.cs

Thanks,

Michael

CodePudding user response:

You can simplify the PropertyChangedEventHandler alot using the CallerMemberName attribute and using an Inherited Class

public class Core : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void PropChanged([CallerMemberName] string callerName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(callerName));
    }
 }

Then you use it like so after adding : Core to your class

public class Program : Core
{
    private bool _isIdle;
    public bool IsIdle
    {
        get
        {
            return _isIdle;
        }

        set
        {
            //if (_isIdle != value)
            //{
                _isIdle = value;
                PropChanged();
            //} reduce number of events (comments)
        }
    }
}

You can also specify the Caller Name manually to trigger an update for a different property.

This is the most modern way of doing this that I know of, The extra bonus here is you can also use the Core class for anything Static

CodePudding user response:

There's no need anymore for all that boilerplate code.

Using the Source Generators from the MVVM Community Toolkit you can inherit from ObservableObject and then write your properties like this:

public partial class MyViewModel : ObservableObject
{
    [ObservableProperty]
    string name;
}

This will be used to generate a class that looks similar to this under the hood:

partial class MyViewModel
{
    public string Name
    {
        get => name;
        set
        {
            if(name.Equals(value)) return;
            OnPropertyChanging();
            name = value;
            OnPropertyChanged();
        }
    }
}

You can also raise notifications for other properties and have commands auto-generated, too:

public partial class MyViewModel : ObservableObject
{
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(FullName)]
    string firstName;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(FullName)]
    string lastName;

    public string FullName => $"{FirstName} {LastName}";

    [RelayCommand]
    private void SayHello()
    {
        Console.WriteLine($"Hello, {FullName}!");
    }
}

Note that the backing fields for auto-generated properties must be lowercase, because the generator will create the properties beginning with an uppercase letter.

Commands will have the same name as the method with the "Command" suffix, so SayHello() becomes SayHelloCommand. In case of an async method, e.g. async Task SayHelloAsync(), the Command will still be called SayHelloCommmand, without the "Async" suffix.

I've also written a blog series going into more detail about this.

  • Related