3

Created a project using MVVM Light. It is common for the ViewModels to have a lot of properties that look like this

class TestModel
{
    public string DisplayValue { get; set; }
}

class TestViewModel : ViewModelBase
{
    public string DisplayValue
    {
         private TestModel model = new TestModel();

         get
         {
              return model.DisplayValue;
         }
         set
         {
              if (model.DisplayValue != value)
              {
                   model.DisplayValue = value;
                   RaisePropertyChanged();
              }
         }
    }
}

Sometimes the property is not in the model and is backed by a local private field instead. This approach works fine, but there is a ton of boilerplate code. How can the code duplication be reduced?

Are there any better solutions than the one I proposed or is there something built into MVVM Light that I missed?

Adam
  • 1,825
  • 1
  • 17
  • 24

3 Answers3

16

My version of the boilerplate MVVM Light property looks like this:

private string _p = "";

public string P
{
  get { return _p; }
  set { Set(ref _p, value); }
}

which is pretty much the thinnest one can get, made possible through the use of Set function provided by the underlying ObservableObject class (you could use ViewModelBase too).

Edit

In C# 7 and above you could squeeze it even further:

private string _p = "";
public string P
{
  get => _p;
  set => Set(ref _p, value);
}
Welcor
  • 2,431
  • 21
  • 32
dotNET
  • 33,414
  • 24
  • 162
  • 251
  • 3
    You can omit `nameof(Prop)`, one of the overloads uses CallerMemberName so `set { ref _Prop, value); }` gives the same result – Adam Apr 19 '17 at 17:04
  • 1
    does the Set(ref _Prop, value); include the if (_Prop != value) ? – gts13 Feb 28 '18 at 12:15
  • 2
    @gts13: I think it does. – dotNET Feb 28 '18 at 19:46
  • @gts13, Yes it does. –  Apr 20 '20 at 16:48
  • @sodjsn26fr: Do u have reference on this? I recall I tried locating this in MVVM Light's source code,but couldn't. – dotNET Apr 21 '20 at 04:06
  • 1
    @dotNET , Yes. If you navigate to the Set accessor in ViewModelBase.cs, you can find this code : if (EqualityComparer.Default.Equals(field, newValue)) { return false; } –  Apr 21 '20 at 08:20
  • becareful, Set(..) only works with fields, not with properties. – Welcor Jun 06 '20 at 11:17
  • @Blechdose: There is no point in using `Set` for properties. It is just a little helper for doing two things; 1. Setting the underlying field, 2. Raising `PropertyChanged`. Moreover, it is actually the `ref` keyword that doesn't work with properties, not `Set`. See [this great post](https://stackoverflow.com/questions/564557/is-it-possible-to-pass-properties-as-out-or-ref-parameters) for an explanation of why it is so. – dotNET Jun 06 '20 at 12:31
  • @dotNET when you dont have a backing field but you have a model and use its properties, It makes sense. example `public string Name { get => _model.Name }` with Name being a string property in the _model object. But I already found a solution for this creating a Set() method with an Action as additonal parameter. Looks a bit ugly, but it works – Welcor Jun 06 '20 at 13:41
  • @Blechdose: In those cases, it is cleanest to simply call `RaisePropertyChanged` yourself. Something like this: `public string Name { get => _model.Name; set { _model.Name = value; RaisePropertyChanged(nameof(Name)); } }`.MVVM Light's `Set` too doesn't do much more than that. – dotNET Jun 06 '20 at 16:58
  • You are my hero! – Madriesen Aug 15 '20 at 12:19
0

MVVM light provide set methods as below example you directly used it.

class TestViewModel : ViewModelBase
{
   private TestModel model = new TestModel();

   public string DisplayValue
  {
     get{return model.DisplayValue;}
     set{ Set(()=>DisplayValue, ref model.DisplayValue, value); }
  }   
}
A. S. Mahadik
  • 585
  • 1
  • 5
  • 17
  • Is there any way to pass in a function that will only be executed if the property changed? – Adam Apr 19 '17 at 12:56
  • This set method also assign value to your property and raise property change event – A. S. Mahadik Apr 19 '17 at 13:08
  • I get that, but I want to run a method if the value changes like this `if (_highDwellDiameter != value) { _highDwellDiameter = value; highDwellRadius = value / 2; RaisePropertyChanged(); } ` – Adam Apr 19 '17 at 13:19
  • This is also good but your are using MVVM light that's why I am told you to use set method and then highDwellRadius=value/2 – A. S. Mahadik Apr 19 '17 at 13:25
  • There is very little time spent running code like this `model.myValue = value; RaisePropertyChanged(); `, it is only when a value change causes a lot of other calculations to run that it is really worthwhile to check if the value actually changed. So I'm wondering if there is a way to pass a method to the set that will be executed only if the property changed. For example `Set(()=>DisplayValue, ref model.DisplayValue, value, SomeCalcs);` – Adam Apr 19 '17 at 13:31
  • Where `SomeCalcs` is defined as `void SomeCalcs() { // Arbitrary code here }` – Adam Apr 19 '17 at 13:32
  • You can omit `()=>DisplayValue, `, one of the overloads uses CallerMemberName so `set { ref _Prop, value); }` gives the same result – Adam Apr 19 '17 at 17:05
  • 2
    Actually, this doesn't work because `model.DisplayValue` is a property, not a field. You can't use `ref` on properties. – redcurry Feb 11 '19 at 16:14
-1

In my application the model is a pure data store and is used primarily for reading and writing to a SQL Server. The model contains the backing fields for all the properties in the ViewModel that get saved to the server. So it makes sense to only have fields in the model and only access them through the ViewModel

class TestModel
{
    public string displayValue;
    public override bool Equals(object obj)
    {
        if (obj.GetType() != typeof(TestModel))
                return false;

        var testObj = obj as TestModel;
        return testObj?.GetHashCode() == testValue?.GetHashCode();
    }

    public override int GetHashCode()
    {
        return displayValue.GetHashCode();
    }
}

GetHashCode will be the only thing that needs to get updated when additional fields are added to the class. Since it is a field it is possible to pass it by reference to a common function and use it in all of the properties.

class TestViewModel:ViewModelBase
{
    private TestModel model = new TestModel();
    public string DisplayValue
    {
        get { return model.displayValue; }
        set { SetIfChanged(ref model.displayValue, value, RunCalculations); }
    }


    public bool SetIfChanged<T>(ref T field, T value, Action MoreWork, [CallerMemberName] string propertyName = null)
    {
        if (!Equals(field, value))
        {
            field = value;
            MoreWork.Invoke();
            RaisePropertyChanged(propertyName);
            return true;
        }

        return false;
    }

    private void RunCalculations()
    {
       // Do some work before RaisePropertyChanged()
    }
}

This accepts all types, override EqualTo as required for equality to work properly. It can also run additional calculations as required.

It would be nice if this could be removed from the ViewModel like the MVVM Light Set method, but I haven't been able to figure that out.

Adam
  • 1,825
  • 1
  • 17
  • 24
  • 2
    I'd recommend `object.Equals(field, value)` in case you ever have a `null` field. Also in my project I have something similar but it executes `Action` before raising property changed (which can be quite useful sometimes) and the function returns a boolean that indicates whether the value was actually changed, so i can trigger additional code with a simple `if` statement. – grek40 Apr 19 '17 at 14:24
  • 2
    @Adam, `!field.Equals(value)` will throw a NullReferenceException if `field` is null! Maybe you better write `if (EqualityComparer.Default.Equals(field, value))` – Massimiliano Kraus Apr 19 '17 at 15:30