更好的MVVMLight属性变更方式

3

使用 MVVM Light 创建了一个项目。在 ViewModel 中经常会有很多类似以下代码的属性:

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

有时属性并不在模型中,而是由本地私有字段支持。这种方法可以正常工作,但存在大量样板代码的问题。如何减少代码重复?
除了我提出的解决方案之外,是否有更好的解决方案或者MVVM Light中有我错过的功能?
3个回答

16

我版本的MVVM Light属性模板如下:

private string _p = "";

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

这是一种可以达到最薄的方法,通过底层ObservableObject类所提供的Set函数实现(您也可以使用ViewModelBase)。

编辑

在C# 7及以上版本中,甚至可以将其进一步压缩:

private string _p = "";
public string P
{
  get => _p;
  set => Set(ref _p, value);
}

3
您可以省略 nameof(Prop),其中一个重载使用了 CallerMemberName,所以 set { ref _Prop, value); } 将产生相同的结果。 - Adam
1
Set(ref _Prop, value); 包含 if (_Prop != value) 吗? - gts13
2
@gts13:我认为它确实可以。 - dotNET
1
@dotNET,是的。如果您导航到ViewModelBase.cs中的Set访问器,您可以找到以下代码:if (EqualityComparer<T>.Default.Equals(field, newValue)) { return false; } - user12805184
你是我的英雄! - Madriesen
显示剩余6条评论

0

MVVM light 提供了以下示例的一组方法,您可以直接使用它们。

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

   public string DisplayValue
  {
     get{return model.DisplayValue;}
     set{ Set(()=>DisplayValue, ref model.DisplayValue, value); }
  }   
}

有没有办法传递一个函数,只有在属性更改时才会执行? - Adam
此 set 方法也会为您的属性分配值并引发属性更改事件。 - A. S. Mahadik
我明白,但是我想在值改变时运行一个方法,就像这样 if (_highDwellDiameter != value) { _highDwellDiameter = value; highDwellRadius = value / 2; RaisePropertyChanged(); } - Adam
你可以省略 ()=>DisplayValue,,其中一个重载使用了 CallerMemberName,所以 set { ref _Prop, value); } 会得到相同的结果。 - Adam
2
实际上,这不起作用,因为model.DisplayValue是一个属性,而不是一个字段。您不能在属性上使用ref - redcurry
显示剩余3条评论

-1
在我的应用程序中,模型是一个纯数据存储器,主要用于读写 SQL Server。该模型包含 ViewModel 中保存到服务器的所有属性的后备字段。因此,只在模型中拥有字段并通过 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。由于它是一个字段,可以通过引用将其传递给公共函数,并在所有属性中使用它。

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

这个函数接受所有类型,根据需要覆盖EqualTo以使相等性正常工作。它还可以根据需要运行其他计算。

如果能像MVVM Light Set方法一样从ViewModel中删除这个函数就好了,但我还没有想出来怎么做。


2
如果您的字段可能为 null,我建议使用 object.Equals(field, value)。在我的项目中,我有类似的东西,但它会在引发属性更改之前执行 Action(这有时非常有用),并且该函数返回一个布尔值,指示值是否实际更改,因此我可以使用简单的 if 语句触发其他代码。 - grek40
2
@Adam,如果field为空,则!field.Equals(value)会抛出NullReferenceException异常!也许你最好写成if (EqualityComparer<T>.Default.Equals(field, value)) - Massimiliano Kraus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接