是否适合向域模型对象添加ICommand?

时间:2015-05-27 17:36:03

标签: mvvm

我正在为Windows应用商店应用程序应用MVVM模式(并在过程中学习它)。

现在我倾向于在View和ViewModel之间建立1:1的对应关系,其中多个ViewModel依赖于相同的底层模型。

例如,假设我有一个实体“学生”。我有两种方式来查看学生:在全屏详细信息页面中或作为教室中的学生列表。这导致以下View / ViewModel对:

  • StudentDetailsView / StudentDetailsViewModel
  • StudentListItemView / StudentListItemViewModel

目前我假设我的ViewModel将直接公开Model,而我的Xaml将绑定到ViewModel.Model.property-name(我意识到这是有争议的)。

假设我可以从任一视图对学生执行某些操作(例如,“毕业生”)。我希望在我的模型中具有研究生行为(以避免遗忘域模型),并且我希望避免在依赖于相同模型的ViewModel之间重复行为。

我的意图是拥有一个ICommand(例如,一个RelayCommand),我可以将一个Graduate按钮绑定到View中。这是我的问题:

有没有理由不让ICommand成为Model类的属性?

基本上这意味着类似以下内容(忽略了对存储库的需求):

public class Student {
    public ICommand GraduateCommand { get { ... } }
    void Graduate() { ... }
}

这样,StudentDetailsView和StudentListItemsView都可以拥有绑定到该命令的Xaml(其中DataContext是StudentViewModel,Model是公共属性):

<Button Command="{Binding Model.GraduateCommand}" />

显然,我可以将Student :: Graduate()公开,在两个ViewModel上创建重复的GraduateCommands,并让执行委托调用Model.Graduate()。但是,通过ICommand而不是方法暴露类的行为会有什么不利之处?

2 个答案:

答案 0 :(得分:2)

首先,在很多情况下,如果你可以在模型上实现INotifyPropertyChanged,那么直接从View绑定到模型就完全没问题了。它仍然是MVVM。这可以防止ViewModel混乱使用大量“relay-direct-to-Model”代码。您只在VM中包含View不能直接使用的内容(需要换行/非规范化/转换数据,或者Model属性不实现INPC,或者您需要另一个验证层......)。

那就是,命令是View和ViewModel之间通信的主要手段。

  • 命令可能有很多接收器(可能在不同的ViewModel上)。
  • Execute / CanExecute模式通常不适合VM的上下文。
  • 即使真实的东西是在模型的方法中完成的,命令也可能有一些逻辑,而不仅仅是委托给模型(验证,与其他VM属性/方法的交互......)。
  • 在测试您的虚拟机时,如果命令位于虚拟机之外,则无法存根命令。

由于这些原因,命令不属于模型

如果您担心跨VM的代码重复,可以创建一个StudentViewModelStudentDetailsViewModelStudentListItemViewModel将继承。 StudentViewModel将定义命令及其常见行为。

答案 1 :(得分:1)

如果在视图中使用模型的属性,则应该停止调用该MVVM。您可以将研究生命令实现移动到另一个类(比如一个Helper类),并在ViewModel之间共享它(在初始化期间)。

GraduateCommand=new RelayCommand (GraduateHelper.Graduate, CanGraduate);

错误:将Graduate()放入您的实体。

修改

INotifyPropertyChanged的扩展方法

public static class NotifyExtension
{
    public static void OnPropertyChanged(this INotifyPropertyChanged source, PropertyChangedEventHandler h, string propertyName)
    {
        PropertyChangedEventHandler handler = h;
        if (handler != null) handler(source, new PropertyChangedEventArgs(propertyName));
    }
    public static bool SetProperty<T>(this INotifyPropertyChanged source,PropertyChangedEventHandler handler, ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        source.OnPropertyChanged(handler, propertyName);
        return true;
    }

}

然后:

public class Student:INotifyPropertyChanged
{
    private string _name = "Name";

    public string Name
    {
        get { return _name; }
        set {
            this.SetProperty<string>(PropertyChanged, ref _name, value, "Name");            }
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

public partial class MyViewModel :INotifyPropertyChanged
{
    private Student _student=new Student();
    public Student Student
    {
        get { return _student; }
        set
        {
            this.SetProperty<Student>(PropertyChanged, ref _student, value, "Student");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;        

}

最后Xaml:

<TextBlock Text="{Binding Path=Student.Name}"></TextBlock>