Getter 和 setter 在 ViewModel 中用于 INotifyPropertyChanged 模型中的原始属性

Getter and setter in ViewModel for primitive properties in an INotifyPropertyChanged Model

我目前正在从事一个利用 MVVM 模式的项目,并且遇到过这种情况。

假定一个 class 模型:INotifyPropertyChanged 包含一些属性,例如

public class Model : INotifyPropertyChanged
{
    private string rabbit;
    public string Rabbit
    {
        get { return rabbit; }
        setter { rabbit = value; OnPropertyChanged("Rabbit"); }
    }
}

现在,我想编写一个环绕模型的 ViewModel,公开模型的属性以及其他属性(命令等)。

当为模型中已经存在的属性编写 getter 和 setter 时,问题就来了。我已经注释掉了代码的特定部分。

public class ViewModel : INotifyPropertyChanged
{
    private Model modelObject;
    public string Rabbit
    {
        get { return modelObject.rabbit; }
        setter 
        { 
            modelObject.rabbit = value; 
            OnPropertyChanged("Rabbit"); // Should OnPropertyChanged be called here?
        }
    }

    public ViewModel(Model modelObject)
    {
         this.modelObject = modelObject;
    }
}

我不确定是否应该在 ViewModel 中再次调用 OnPropertyChanged。由于 View 绑定到 ViewModel 但不应该能够访问模型,因此这样做似乎是合乎逻辑的。

(P.S。我让我的模型可观察的原因是相同的模型将在同一内存中除 GUI 之外的其他地方使用 space,其中还需要通知对模型的更改。)

这是 MVVM 的基本问题之一,有几种不同的解决方法。

您当前的方法很好,而且非常纯粹的 MVVM。缺点是需要的工作量。

I am not sure if I should call OnPropertyChanged again in the ViewModel.

你将不得不,模型的 INPC 不帮助数据绑定。您还必须将 ViewModel 订阅到 Model.PropertyChanged 并在 VM 中回显这些更改。

另一种可接受的方法是将模型公开为 public 属性(使用 INPC)并与 <TextBlock Text="{Binding Model.Rabbit}" />

绑定

你可以看看图片here。注意右上角的大块箭头,它们代表了这个问题的各种解决方案。

是的,如果您只是简单地包装模型,那么您还需要再次调用 OnPropertyChanged。这是因为您的视图绑定到视图模型,而不是模型,在这种情况下,您的视图模型只是提供了一层重定向。

如果您正在查看它并认为一定有更简单的方法,并且认为您可以指定一个绑定路径,该路径通过视图模型 属性 并直接到达 属性模型 - 听起来简单易行(确实如此),但不要这样做。一旦您的项目开始变大,这很快就会导致反模式,并且违反了 Law of Demeter(并且基本上使 viewmodel 的目的无效)。

是的,否则将不会通知绑定到视图模型的 View

请注意,如果 Model 在别处共享和更新(可能是另一个 View),则此 View 将不会更新,因为模型会发出通知。 ViewModel 需要监听 Model 上的变化,然后在 ViewModel.

上引发相关的 属性 变化

一个更实用的解决方案通常是公开 Model 并直接绑定到它,尽管有些人会认为实用主义是异端邪说。选择权在您手中 - 快速 google 会显示各种意见和一些 pros/cons。甚至其他答案也给出了一些!

这个问题有两种情况:

1.环绕模型对象的视图模型,看起来像您的场景。

为此有两个子选项:

1a) 模型对象已经根据您要在视图中使用的属性实现了 INotifyPropertyChanged。 在这种情况下,您可以导出模型。

public class ViewModel : Model 
{
   public ViewModel(){} //parameterless c-tor used for mock for the view.
   public ViewModel(Model model)
   {
      //Copy all model properties to view-model properties.
      //Assuming you get from "outside" a model or Data transfer object.
   } 
}

1b) 模型对象未实现 INotifyPropertyChanged 或未实现您要在视图中使用的所有属性。在这种情况下,您需要包含模型。

public class ViewModel : INotifyPropertyChanged
{
   protected Model model {get;} //C# 5 assumed..

   public ViewModel() //parameterless c-tor used for mock for the view.
   {
       model = new Model();
   } 

   public ViewModel(Model model)
   {
      this.model = model;
   }

   public string SomeProperty
   {
     get { return Model.SomeProperty; } 
     set
     { 
       Model.SomeProperty = value; 
       PropertyChanged?.Invoke(this, new PropertyChangedEventsArgs(this, "SomeProperty"));
     }
   }
}

如果我们仔细分析这些例子,我们可以看到第一个中使用的模式有一个非常严重的约束,它不是为视图服务而编写的..而是为了平行于模型属性而编写的.. .这意味着如果我想在系统中向该视图模型添加另一个模型,我将不得不与模式不同,并且最终会得到实际上看起来像第二个示例中所写内容的代码。

2。环绕视图的视图模型。

好吧,为了写那个 - 我再次查看了前一个选项中的第二个示例,并且所有内容看起来都适合这种情况。

结论:View-Model 环绕着一个视图——并且 99% 的时间环绕着一个特定的视图。因此,无论您的模型是如何编码的,都对您编写的每个视图模型使用选项 1b。

要完成对这个问题的理解过程,请考虑这个视图模型示例,您将很快承认没有 "nice" 使用示例 1a 中的模式来编写它的方法:

public class ViewModel : INotifyPropertyChanged
{
   protected ModelType1 model1 {get;}
   protected ModelType2 model2 {get;}

   public ViewModel() //parameterless c-tor used for mock for the view.
   {
      model1= new ModelType1();
      model2= new ModelType2();
   }

   public ViewModel(ModelType1 model1, ModelType2 model2)
   {
      this.model1 = model1;
      this.model2 = model2;
   }

   public string SpecialProperty
   {
     get { return $"{model1.SomeProperty}.{model2.SomeOtherProperty}"; } 
     set
     { 
       var arr = value.Split('.');
       model1.SomeProperty = arr[0];
       model2.SomtOtherProperty = arr[1];
       PropertyChanged?.Invoke(this, new PropertyChangedEventsArgs(this, "SpecialProperty"));
     }
   }
}

希望对您有所帮助 ;)