【问题标题】:Maintaining WPF MVVM View Model & Model Relationships维护 WPF MVVM 视图模型和模型关系
【发布时间】:2011-01-04 20:11:47
【问题描述】:

这似乎是一个基本问题,但我无法找出最佳实施方案。您如何管理两个视图模型及其对应模型之间的关系。

例如,如果您更改了 PersonViewModel 上的 Occupation 属性,那么该更改如何影响到 PersonModel 中的 Occupation 属性。

我现在唯一能看到它的方法是在视图模型中公开展示模型,但我认为这违背了 MVVM 的目的 - 将模型与视图解耦。

internal class PersonViewModel : INotifyPropertyChanged
{
    private readonly PersonModel person;

    private OccupationViewModel occupation;

    public PersonViewModel(PersonModel person)
    {
        this.person = person;
    }

    public OccupationViewModel Occupation
    {
        get { return this.occupation; }
        set 
        { 
            if (!this.occupation.Equals(value))
            {
                this.occupation = value;
                this.person.Occupation = this.occupation.Occupation; // Doesn't seem right

                this.OnPropertyChanged(new PropertyChangedEventArgs("Occupation"));
            }
        }
    }
}

internal class OccupationViewModel : INotifyPropertyChanged
{
    public OccupationViewModel(OccupationModel occupation)
    {
        this.Occupation = occupation;
    }

    public OccupationModel Occupation { get; set; } // Is this right?
} 

internal class PersonModel
{
    public OccupationModel Occupation { get; set; }
}

【问题讨论】:

  • 为什么 PersonViewModel.Occupation 是可写属性?通常它是只读的——毕竟,谁来设置它?
  • 我正在考虑公开它,以便其他视图模型可以更改它。或者这是一些方法或命令的来源?顺便说一句,我是 WPF、MVVM 新手。
  • Patrick 我认为 Joe 的意思是可以在外部修改视图模型而无需设置它(通过访问其属性):personViewModel.Occupation.Occupation = new OccupationModel("software engineer");
  • ...此外,除非 (a) 您正在评论他们的答案,(b) 他们已为该问题加注星标/收藏,或 (c) 您引导至少有一部分名字以“@”开头的问题——比如“@Joe 我在想……”
  • @Jay,在某些情况下您不希望直接设置视图模型吗?我在考虑视图模型有很多属性的情况。在我看来,设置属性(“PersonViewModel.Occupation”)比遍历所有属性并更改它们更容易。你怎么看?

标签: c# wpf mvvm


【解决方案1】:

您的视图模型 将模型与视图分离。看起来您可能混淆了视图和视图模型的概念。

视图模型既是网关又是两者之间的看门人——它决定了从模型到视图以及从视图返回模型的原因,以及以什么形式。

您可以在 VM 设置器中设置模型属性,也可以不设置。您可以保存视图中的状态,并且仅在用户单击“保存”时将这些更改传播到模型。您可以在其他地方持久化该状态,以便有人可以在将其持久化到视图之前返回并进行更多工作。

view-model 可以亲密地知道模型,所以视图根本不需要知道它。

我不确定我是否遵循您对在视图模型中公开公开模型的担忧。您还没有在示例代码中这样做。您已经公开了一个与模型中使用的 type 相同的对象,但这就像在模型和视图模型中使用 int 来表示年龄 - 您还没有公开实际模型对象;您仍然可以控制是否以及何时在模型上设置视图模型中的值。

【讨论】:

  • 感谢您的明确解释。我担心在 OccupationViewModel 中暴露 OccupationModel。在视图模型中公开暴露模型是不好的做法吗?我假设模型应该被视图模型封装并且对其他人隐藏。
  • @Patrick 在简单、直接的应用程序中,有时直接公开模型是方便的。如果您正在处理复杂性的度量,那么是的,最好通过视图模型上的属性传递您想要公开的任何内容。无论何时处理组合,作为面向对象的原则,这更普遍地适用。
【解决方案2】:

为了显示 Model 和 ViewModel 之间可能存在的关系,我首先通过将 Occupation 的类型更改为 string 来简化您的示例。那么PersonModelPersonViewModel 可能看起来像这样:

public class PersonModel : INotifyPropertyChanged
{
  private string occupation;
  public string Occupation
  {
    get
    {
      return this.occupation;
    }
    set
    {
      if (this.occupation != value)
      {
        this.occupation = value;
        this.OnPropertyChanged("Occupation");
      }
    }
  }
}

public class PersonViewModel: INotifyPropertyChanged
{
  private PersonModel model;

  public string Occupation
  {
    get
    {
      return this.model.Occupation;
    }
    set
    {
      this.model.Occupation = value;
    }
  }

  public PersonViewModel(PersonModel model)
  {
    this.model = model;
    this.model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
  }

  private void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
  {
    this.OnPropertyChanged(e.PropertyName);
  }
}

与您的版本的重要区别在于 PersonModelPersonViewModel 两者都实现了 INotifyPropertyChanged。这很重要,因为否则直接更改PersonModel 的属性(即不通过PersonViewModel)将不会对视图产生影响。还要注意模型中的PropertyChangedEvent 是如何通过管道传输到视图的。

现在假设Occupation 不是string 而是一个具有自己属性的类,例如:

public class OccupationModel : INotifyPropertyChanged
{
  private double salary;
  public double Salary
  {
    get
    {
      return this.salary;
    }
    set
    {
      if (this.salary != value)
      {
        this.salary = value;
        this.OnPropertyChanged("Salary");
      }
    }
  }
}

在 View 和 Model 之间使用 ViewModel 可以让您在如何将数据呈现给 View 方面具有一定的灵活性。这里有两个选择:

选项1 直接在PersonViewModel 中公开Occupation 的属性。这是一个简单的解决方案,因为您不需要实现另一个 ViewModel。

public class PersonViewModel: INotifyPropertyChanged
{
  private PersonModel model;

  public double OccupationSalary
  {
    get
    {
      return this.model.Occupation.Salary;
    }
    set
    {
      this.model.Occupation.Salary = value;
    }
  }

  public PersonViewModel(PersonModel model)
  {
    this.model = model;
    this.model.Occupation.PropertyChanged += new PropertyChangedEventHandler(occupation_PropertyChanged);
  }

  private void occupation_PropertyChanged(object sender, PropertyChangedEventArgs e)
  {
    this.OnPropertyChanged("Occupation" + e.PropertyName);
  }
}

OccupationSalary 属性提供对OccupationSalary 属性的直接访问。注意现在需要如何处理OccupationPropertyChanged 事件,并且我们必须重命名occupation_PropertyChanged 中的属性。

选项 2(推荐)通过 OccupationViewModel 公开 Occupation 的属性。如果您需要实现特定于Occupation 的任何业务逻辑,您应该这样做。鉴于您的示例,这可能是您打算做的:

public class PersonViewModel: INotifyPropertyChanged
{
  private PersonModel model;
  private OccupationViewModel occupationViewModel;
  public OccupationViewModel OccupationViewModel
  {
    get
    {
      return this.occupationViewModel;
    }
  }

  public PersonViewModel(PersonModel model)
  {
    this.model = model;
    this.occupationViewModel = new OccupationViewModel(this.model.occupation);
  }
}

public class OccupationViewModel : INotifyPropertyChanged
{
  private OccupationModel model;

  public double Salary
  {
    get
    {
      return this.model.Salary;
    }
    set
    {
      this.model.Salary = value;
    }
  }

  public OccupationViewModel(OccupationModel model)
  {
    this.model = model;
    this.model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
  }

  private void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
  {
    this.OnPropertyChanged(e.PropertyName);
  }
}

如您所见,OccupationViewModel 的结构与我一开始展示的简化版PersonViewModel 完全相同。您的OccupationViewModel 版本的重要区别在于它公开了OccupationModel属性,而不是OccupationModel 本身。

【讨论】:

    【解决方案3】:

    看起来您应该只在您的 PersonViewModel 上公开一个“Person”属性,而不是公开一个 Occupation 属性。 Occupation 属性似乎是一个不必要的层。

    person 属性如下所示,而 Occupation 属性可以被类似“viewModel.Person.Occupation”的东西引用。

            public Person Person
        {
            get
            {
                return this.person;
            }
            set
            {
                if (!this.person.Equals(value))
                {
                    this.person = value;
                    this.OnPropertyChanged(new PropertyChangedEventArgs("Person"));
                }
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-13
      • 1970-01-01
      • 1970-01-01
      • 2019-01-10
      • 2011-07-14
      • 1970-01-01
      • 2010-12-07
      • 2020-01-17
      相关资源
      最近更新 更多