维护WPF MVVM视图模型和模型之间的关系

5

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

例如,如果您在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是可写属性?通常它应该是只读的 -- 毕竟,谁会去设置它呢? - Joe White
我在考虑将其公开,以便其他视图模型可以更改它。或者这是某个方法或命令的地方吗?顺便说一下,我是一个WPF、MVVM的新手。 - Patrick
Patrick,我认为Joe的意思是视图模型可以在不设置它的情况下被外部修改(通过访问其属性):personViewModel.Occupation.Occupation = new OccupationModel("软件工程师"); - Jay
此外,除非(a)您在评论他们的答案,(b)他们已将问题标记为星号/收藏夹,或者(c)您在问题开头使用其名称的至少一部分,并在前面加上“@”符号,否则用户不会收到您的评论通知--例如:“@Joe 我在想…” - Jay
@Jay,难道没有一些情况下你想直接设置视图模型的吗?我在想如果视图模型有很多属性的情况下,直接设置属性("PersonViewModel.Occupation")会更容易,而不是遍历所有属性并逐个更改。你怎么看? - Patrick
3个回答

6

你的视图模型正在将模型与视图解耦。看起来你可能混淆了视图和视图模型的概念。

视图模型既是两者之间的通道,也是守门员 - 它确定从模型到视图以及从视图返回模型的内容以及形式。

你可以在VM setter中设置模型属性,也可以不设置。你可以保存视图中的状态,并仅在用户单击“保存”时将这些更改传播到模型。你可以将该状态持久化存储在其他地方,以便某人可以在将其持久化到视图之前继续处理它。

视图模型可以熟知模型,因此视图根本不必知道它。

我不确定你对公开在视图模型中暴露模型的担忧。你在示例代码中没有这样做。你公���了一个与模型中使用的类型相同的对象,但这就像在模型和视图模型中都使用int作为年龄一样 - 你仍然可以控制视图模型中的值何时设置到模型中。


感谢清晰的解释。我担心在OccupationViewModel中公开OccupationModel。公开模型在视图模型中是不好的实践吗?我认为模型应该由视图模型封装并对其他人隐藏。 - Patrick
@Patrick 在简单直接的应用程序中,有时将模型直接公开是很方便的。如果你处理的是一定程度的复杂性,那么最好通过视图模型上的属性来传递任何想要公开的内容。这通常作为面向对象原则更普遍地适用于处理组合时。 - Jay

5
为了展示模型和视图模型之间可能存在的关系,我首先简化了您的示例,将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");
      }
    }
  }
}

在视图和模型之间使用ViewModel可以使数据呈现到视图的方式更加灵活。以下是两种可行的方案:

方案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 属性直接访问 Occupation 中的 Salary 属性。需要注意的是,现在必须处理 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本身。


0

看起来你应该在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"));
            }
        }
    }

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