【问题标题】:Getter and setter in ViewModel for primitive properties in an INotifyPropertyChanged ModelViewModel 中的获取器和设置器,用于 INotifyPropertyChanged 模型中的原始属性
【发布时间】:2015-05-17 10:54:08
【问题描述】:

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

假设一个类 Model : INotifyPropertyChanged 包含一些属性,例如

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

现在,我希望编写一个封装模型的 ViewModel,将模型的属性与其他属性(命令等)一起公开。

为 Model 中已经存在的属性编写 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 之外的其他地方使用,其中还需要通知模型的更改。)

【问题讨论】:

    标签: c# wpf mvvm getter-setter


    【解决方案1】:

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

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

    我不确定是否应该在 ViewModel 中再次调用 OnPropertyChanged。

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

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

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

    【讨论】:

      【解决方案2】:

      这个问题有两种情况:

      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% 的时间都是特定视图。因此,无论您的模型是如何编码的,对您编写的每个 View-Model 都使用选项 1b。

      要完成对这个问题的理解过程,请考虑这个视图模型示例,您会很快意识到使用示例 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"));
           }
         }
      }
      

      我希望这会有所帮助;)

      【讨论】:

        【解决方案3】:

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

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

        【讨论】:

          【解决方案4】:

          是的,否则绑定到视图模型的View 将不会收到任何更改的通知。

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

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

          【讨论】:

            猜你喜欢
            • 2016-01-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-07-24
            • 2021-05-19
            • 2014-04-02
            • 1970-01-01
            相关资源
            最近更新 更多