绑定到Model或ViewModel

时间:2013-06-05 18:20:21

标签: c# wpf mvvm binding

我知道已经有关于该主题的问题,但其中的问题在某种程度上与其他问题有关,并且没有提供确凿的答案。

特别是那里:Question1Question2当然还有Question3 所以请不要太快关闭这个问题。他们回答那里只是说“做这个,做那个”而不是为什么!

有些人否认需要ViewModel并说 “标准”方式是直接绑定到模型。这是我否认并试图用技术论证来证明的。

MVCMVPPresentation Model的背景中,这很自然 给我使用ViewModel也许我错过了一个重点?

所以对我来说,默认是绑定到ViewModel,无论Model是什么(无论它是否实现INotifyPropertyChanged)。

我看到绑定到ViewModel有几个原因,包括 (如此处所述CodeProject和此处on another article

1。从视图中删除逻辑

  • 使逻辑单元可测试
  • 减少代码冗余(需要时复制)

2。安全

  • 模型包含用户不得更改的属性
  • 自动,但如果绑定到模型
  • ,则可能会发生不需要的更新

第3。松耦合

  • 如果直接绑定到模型,则较低层与View
  • 之间会存在耦合
  • 更改模型会导致所有视图中的更改
  • 视图不依赖于任何给定的模型
  • 可以使用EF,某些DSL,批处理文件等轻松生成模型

4。发展速度

  • 您可以从Prototype ViewModel层次结构开始并绑定到
  • 如果模型仍处于开发阶段,您可以从Prototype Model
  • 开始
  • ModelViewModel可以开发testdriven,无论视图
  • View完全由设计师或具有强大设计背景的开发人员构建

5。 “棘手的同步”已解决

  • 对于任何给定的“棘手同步”问题,有很多解决方案,例如
  • AutoMapper
  • 模型中的事件系统(模型触发事件,ViewModel订阅)

6。整个项目的结构相同

  • 有一些观点需要ViewModel,比如SelectedItem
  • 将Binding混合到Model和ViewModel是errorprone
  • 新鲜的开发人员很难找出项目的结构
  • 稍后开始带来ViewModel时,无法解决它是凌乱的

7。可扩展性

  • 让我们定义:如果您不使用ViewModel,那么它不是MVVM
  • MVVM可以很容易地被用于许多数据源,很多视图
  • 如果您发现任何性能问题:延迟加载和缓存都在ViewModel

8。关注点分离

  • 掌握复杂性是软件中的主要问题
  • ViewModels全权负责推动变革
  • 向视图发送通知和将其推送到其他进程或计算机一样容易
  • ViewModel,而不是模型/数据源上的更改通知的View注册
  • 数据源可以忽略向导致更改的ViewModel发送事件

相反,来自the other thread的人会转储一些积分,包括

  1. 如果直接更新模型,则视图模型将不知道触发属性更改事件。这会导致UI不同步。 这严重限制了您在父视图模型和子视图模型之间发送消息的选项。

  2. 如果模型有自己的属性更改通知,则#1和2不是问题。相反,如果包装器VM超出范围但模型没有,则必须担心内存泄漏。

  3. 如果您的模型很复杂,有很多子对象,那么您必须遍历整个树并创建第二个对象图,它会遮蔽第一个对象图。这可能非常繁琐且容易出错。

  4. 包装的收藏品特别难以使用。任何时候(UI或后端)从集合中插入或删除项目,都需要更新影子集合以匹配。这种代码很难做对。

  5. 所以,问题是:绑定的默认方式是什么?为什么?

    我是否会错过必须拥有ViewModel的点?

    是否有任何真正的理由要绑定到模型?

    最重要的是为什么,而不是如何。

6 个答案:

答案 0 :(得分:6)

视图模型通常包含成员,这些成员旨在与视图一起使用(例如,IsSomethingSelectedIsSomethingExpandedIsSomethingVisible等属性,ICommand的任何实现)。

你认为有什么理由将所有这些东西带入模特吗?当然不。这就是视图模型存在的原因。

答案 1 :(得分:5)

  

所以,问题是:绑定的默认方式是什么?为什么?

一般来说,我认为拥有ViewModel并绑定它是默认设置。有一个原因是“ViewModel”存在并且是MVVM模式的一部分。

除了纯粹的数据之外,还有其他原因需要ViewModel。您还通常实现特定于应用程序的逻辑(即:不是模型的一部分,但在应用程序中需要)。例如,任何ICommand实现都应该在ViewModel上,因为它与模型完全无关。

  

是否有任何想要绑定到模型的真正原因?

在某些情况下可能更简单,特别是如果您的模型已经实现INotifyPropertyChanged。降低代码复杂性是一个有价值的目标,具有自己的优点。

答案 2 :(得分:3)

反参数:

  
      
  1. 从视图中删除逻辑
  2.   

从视图模型中删除逻辑同样有用。通过将验证,计算字段等逻辑推送到模型中,您可以获得更轻,更清晰的视图模型。

  

•使逻辑单元可测试

模型本身非常容易进行单元测试。您不必担心模拟库,以及使用处理外部服务的视图模型的情况。

  

•减少代码冗余(需要时重复)

多个视图模型可以共享相同的模型,从而减少验证冗余,计算字段等。

  
      
  1. 安全   •模型包含用户不得更改的属性
  2.   

然后不要在用户界面上公开它们。

  

•自动,但如果绑定到模型

,可能会发生不需要的更新

这一切都没有任何意义。如果您的虚拟机只是模型的包装器,那么无论如何都只会推动这些更新。

  
      
  1. 松耦合   •如果直接绑定到模型,则较低层与View
  2. 之间会存在耦合   

当你在它们之间推动一个包装VM时,这种耦合并没有神奇地消失。

  

•更改模型会导致所有视图中的更改

更改模型会导致所有包装器视图模型发生更改。 更改视图模型还会导致所有视图中的更改。 因此,模型仍然可以导致所有视图的更改。

  

•视图不依赖于任何给定的模型

无论是否包含模型的视图模型都是如此。它只看到属性,而不是实际的类。

  

•可以使用EF,某些DSL,批处理文件等轻松生成模型

是的。通过一些工作,这些易于生成的模型可以包含有用的接口,如INotifyDataErrorInfo,IChangeTracking和IEditableObject。

  
      
  1. 发展速度
  2.   

绑定到模型可以提供更快的开发速度,因为您不必映射所有属性。

  

•您可以从Prototype ViewModel层次结构开始并绑定到

或者我可以从原型模型开始。添加包装器没有任何好处。

  

•如果模型仍处于开发阶段,您可以从原型模型开始   •无论View

,都可以开发Model和ViewModel testdriven

同样,通过在模型周围添加包装器没有任何好处。

•View可以完全由设计师或具有强大设计背景的开发人员构建

然而,通过在模型周围添加包装器,没有任何结果。

  
      
  1. "棘手的同步"解决了   •对于任何给定的"棘手的同步,有很多解决方案。问题,例如   •AutoMapper
  2.   

如果使用automapper将数据复制到视图模型中,那么您不使用MVVM模式。您只是使用视图和模型。

  

•模型中的事件系统(模型触发事件,ViewModel订阅)

Hello内存泄漏。也就是说,除非你非常小心并且放弃了跨多个视图共享模型的能力。

  
      
  1. 整个项目的平等结构   •必须有ViewModel,如SelectedItem
  2.   

无关。没有人反对非包装视图模型。

  

•将Binding与Model和ViewModel混合是错误的

不支持。

  

•新鲜的开发人员很难找出项目的结构

不支持。

  

•稍后无法解决时开始使用ViewModel是凌乱的

无关。同样,没有人反对使用非包装视图模型。

  
      
  1. 可伸缩性   •让我们定义:如果您不使用ViewModel,那么它不是MVVM
  2.   

无关。第三次,没有人反对使用非包装视图模型。

  

•MVVM可以轻松地用于大量数据源,大量视图

无关。我们不是在争论是否使用MVVM,我们正在争论如何最好地使用MVVM。

  

•如果发现任何性能问题:延迟加载和缓存都在ViewModel

同意,但无关紧要。没有人建议你将服务电话推入模型。

  
      
  1. 关注点分离
  2.   

这是包装器视图模型最难落下的地方。

视图模型必须处理UI数据(例如模式,所选项目)并托管调用外部服务的ICommands。

将所有模型数据,验证逻辑,计算属性等推入视图模型使得更加臃肿。

答案 3 :(得分:1)

我同意里德的观点 - ViewModel是应该绑定的。我总是认为模型是一个或多或少静态的值集合,可能不像ViewModel那样频繁或动态地改变。通常,我尝试在模型中放置任何可以在编译时假定的值,以及在ViewModel中在运行时确定的任何值。

View本身不应该只有最简单的基本逻辑。其余的应该是对ViewModel的引用。安全性有时是个问题,但我喜欢这样做只是为了代码的可读性和简洁性。当在视图中完成所有美学事物时,处理代码要容易得多,所有更多数学,逻辑事物都隐藏在ViewModel中,并且所有硬数据都在一个单独的模型中。

MVVM与MVC密切相关,其指导原则是模型和视图不应直接相互看见。再一次,对我而言,这是一个清晰的事情。决定模型值应该如何变化的逻辑也应该在ViewModel / Controller中。观点不应该自己思考。

将View视为接待员:这是一个友好的面孔,可以与用户进行交互(“对话”)。 ViewModel是前台旁边门后办公室的会计师,Model是他/她的参考书和笔记集。如果接待员开始在会计师账簿的边缘写作,擦除会计师的笔记,并更改记录中的内容,事情开始变得混乱。

答案 4 :(得分:1)

这是一个简单的对象图。只是一些非常简单的模型,具有正常的属性更改和验证事件。

那些认为模型需要包含在视图模型中的人会显示您的代码。

public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

        OnErrorChanged(propertyName);
    }

    protected void OnErrorChanged(string propertyName)
    {
        if (ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public virtual IEnumerable GetErrors(string propertyName)
    {
        return Enumerable.Empty<string>();
    }

    public virtual bool HasErrors
    {
        get { return false; }
    }
}

public class Customer : ModelBase
{
    public Customer()
    {
        Orders.CollectionChanged += Orders_CollectionChanged;
    }

    void Orders_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.OldItems.Count > 0)
            foreach (INotifyPropertyChanged item in e.OldItems)
                item.PropertyChanged -= Customer_PropertyChanged;

        if (e.NewItems.Count > 0)
            foreach (INotifyPropertyChanged item in e.NewItems)
                item.PropertyChanged += Customer_PropertyChanged;

        OnPropertyChanged("TotalSales");
    }

    void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Total")
            OnPropertyChanged("TotalSales");
    }

    public decimal TotalSales
    {
        get { return Orders.Sum(o => o.Total); }
    }

    private string _FirstName;
    public string FirstName
    {
        get { return _FirstName; }
        set
        {

            if (_FirstName == value)
                return;
            _FirstName = value;
            OnPropertyChanged();
        }
    }

    private string _LastName;
    public string LastName
    {
        get { return _LastName; }
        set
        {

            if (_LastName == value)
                return;
            _LastName = value;
            OnPropertyChanged();
        }
    }



    private readonly ObservableCollection<Order> _Orders = new ObservableCollection<Order>();
    public ObservableCollection<Order> Orders
    {
        get { return _Orders; }
    }

}

public class Order : ModelBase
{
    public Order()
    {
        OrderLines.CollectionChanged += OrderLines_CollectionChanged;
    }

    void OrderLines_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.OldItems.Count > 0)
            foreach (INotifyPropertyChanged item in e.OldItems)
                item.PropertyChanged -= OrderLine_PropertyChanged;

        if (e.NewItems.Count > 0)
            foreach (INotifyPropertyChanged item in e.NewItems)
                item.PropertyChanged += OrderLine_PropertyChanged;

        OnPropertyChanged("Total");
        OnErrorChanged("");
    }

    public override bool HasErrors
    {
        get { return GetErrors("").OfType<string>().Any() || OrderLines.Any(ol => ol.HasErrors); }
    }

    void OrderLine_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Extension")
            OnPropertyChanged("Total");
    }

    public decimal Total
    {
        get { return OrderLines.Sum(o => o.Extension); }
    }

    private int _OrderNumber;
    private DateTime _OrderDate;

    public DateTime OrderDate
    {
        get { return _OrderDate; }
        set
        {
            if (_OrderDate == value)
                return;
            _OrderDate = value;
            OnPropertyChanged();
        }
    }
    public int OrderNumber
    {
        get { return _OrderNumber; }
        set
        {
            if (_OrderNumber == value)
                return;
            _OrderNumber = value;
            OnPropertyChanged();
        }
    }

    private readonly ObservableCollection<OrderLine> _OrderLines = new ObservableCollection<OrderLine>();
    public ObservableCollection<OrderLine> OrderLines
    {
        get { return _OrderLines; }
    }

}

public class OrderLine : ModelBase
{
    private string _ProductName;
    private decimal _Quantity;
    private decimal _Price;
    public decimal Price
    {
        get { return _Price; }
        set
        {
            if (_Price == value)
                return;
            _Price = value;
            OnPropertyChanged();
        }
    }
    public string ProductName
    {
        get { return _ProductName; }
        set
        {
            if (_ProductName == value)
                return;
            _ProductName = value;
            OnPropertyChanged();
            OnPropertyChanged("Extension");
        }
    }
    public decimal Quantity
    {
        get { return _Quantity; }
        set
        {
            if (_Quantity == value)
                return;
            _Quantity = value;
            OnPropertyChanged();
            OnPropertyChanged("Extension");
        }
    }
    public decimal Extension
    {
        get { return Quantity * Price; }
    }

    public override IEnumerable GetErrors(string propertyName)
    {
        var result = new List<string>();

        if ((propertyName == "" || propertyName == "Price") && Price < 0)
            result.Add("Price is less than 0.");
        if ((propertyName == "" || propertyName == "Quantity") && Quantity < 0)
            result.Add("Quantity is less than 0.");

        return result;
    }

    public override bool HasErrors
    {
        get { return GetErrors("").OfType<string>().Any(); }
    }
}

以下是一个典型的ViewModel:

public class CustomerViewModel : ModelBase
{
    public CustomerViewMode()
    {
        LoadCustomer = null; //load customer from service, database, repositry, etc.
        SaveCustomer = null; //save customer to service, database, repositry, etc.
    }

    private Customer _CurrentCustomer;

    public Customer CurrentCustomer
    {
        get { return _CurrentCustomer; }
        set
        {
            if (_CurrentCustomer == value)
                return;
            _CurrentCustomer = value;
            OnPropertyChanged();
        }
    }
    public ICommand LoadCustomer { get; private set; }
    public ICommand SaveCustomer { get; private set; }

}

答案 5 :(得分:1)

您的问题没有“正确”的答案。当然,WPF会愉快地允许您绑定到您自己声明的任何“模型”对象;框架根本不关心。您并不总是必须遵循MVVM模式,因为您正在WPF中执行应用程序。上下文始终是您编写的任何软件的关键。如果您时间紧迫并需要快速完成原型,请务必绑定到模型并在需要时重构。

所以我认为你真正要问的是“我什么时候应该使用MVVM模式?”

答案当然是“当你的问题符合模式时”。

那么MVVM模式给你的是什么?您已经列出了使用MVVM的几个原因,但对于该模式最重要的一个是松散耦合 - 所有其余的sorta都带有它。

MVVM模式的重点是确保您的模型是一个状态机,它知道 nothing 关于如何向用户呈现数据或从用户获取数据。在某种程度上,您的模型是以对模型有意义的格式构建的纯数据,而不是以对人类有意义的格式构建。您的ViewModel负责在纯数据平台(Model)和用户输入平台(View)之间进行转换。 ViewModel与View和Model紧密耦合,但重要的是Model不知道View。