Binding to Model or ViewModel
我知道已经有关于这个主题的问题,但是这些问题在某种程度上是特定于其他问题的,并没有提供确切的答案。
尤其是这里的问题:问题1,问题2,当然还有问题3,所以请不要太快地结束这个问题。他们回答说"做这个,做那个",而不是为什么!
有人否认需要一个
从我在
因此,对我来说,无论
我认为绑定到EDOCX1[0]有几个原因,包括(如本文所述,codeproject和另一篇文章所述)
1。从视图中删除逻辑
- 使逻辑单元可测试
- 减少代码冗余(需要时重复)
2。安全性
- 模型包含用户不应更改的属性
- 自动,但如果绑定到模型,可能会发生不需要的更新
三。松耦合
- 如果直接绑定到模型,则下层和视图之间将存在耦合。
- 更改模型会导致所有视图发生更改
- 视图不依赖于任何给定的模型
- 模型可以很容易地用ef、一些DSL、批处理文件等生成。
4。发展速度
- 您可以从
Prototype ViewModel 层次结构开始并绑定到该层次结构 - 如果模型仍在开发中,可以从
Prototype Model 开始。 - 无论从哪个角度看,都可以开发测试驱动的
Model 和ViewModel 。 View 完全可以由设计人员或具有强大设计背景的开发人员建造。
5。棘手的同步问题解决了
- 对于任何给定的"棘手的同步"问题,都有很多解决方案,例如
- 自动装订机
- 模型中的事件系统(模型激发事件,视图模型订阅)
6。整个项目的平等结构
- 有些地方需要视图模型,比如selecteditem
- 混合绑定到模型和视图模型很容易出错
- 新开发人员很难搞清楚项目的结构。
- 在没有办法解决的情况下,稍后开始引入ViewModel
7。可扩展性
- 让我们定义:如果不使用ViewModel,则它不是MVVM
- MVVM可以很容易地应用于许多数据源、许多视图
- 如果发现任何性能问题:延迟加载和缓存将进入ViewModel
8。关注点分离
- 掌握复杂性是软件的主要问题
- 视图模型的唯一责任是推动变化。
- 向视图发送通知与将其推送到其他进程或计算机一样容易。
- 视图模型,而不是模型/数据源上更改通知的视图寄存器
- 数据源可以忽略向导致更改的ViewModel发送事件
相反,另一个线程的家伙会转储一些点,包括
如果直接更新模型,视图模型将不知道是否触发属性更改事件。这会导致UI不同步。这严重限制了在父视图模型和子视图模型之间发送消息的选项。
如果模型有自己的属性更改通知,1和2不是问题。相反,如果包装虚拟机超出范围,但模型没有,则必须担心内存泄漏。
如果您的模型很复杂,有很多子对象,那么您必须遍历整棵树,并创建第二个对象图来隐藏第一个对象。这可能非常乏味且容易出错。
打包的集合尤其难以处理。每当有东西(UI或后端)从集合中插入或删除项目时,需要更新阴影集合以匹配。这种代码很难纠正。
所以,问题是:默认的绑定方式是什么,为什么?
我是否错过了一些需要建立视图模型的要点?
有没有什么真正的理由要绑定到模型上?
最重要的是为什么,而不是如何。
视图模型通常包含用于视图的成员(如
你有什么理由把这些东西都带到模特身上吗?当然没有。这就是视图模型存在的原因。
So, the question is: what is the default way to bind and why?
一般来说,我认为拥有一个视图模型并绑定到它是默认的。"viewModel"存在的原因之一就是它是MVVM模式的一部分。
除了纯粹的数据之外,还有其他的原因需要一个视图模型。您还通常实现特定于应用程序的逻辑(即:不是模型的一部分,而是应用程序所必需的)。例如,任何
Are there any real reasons one would like to bind to a model?
在某些情况下,它可能更简单,特别是当您的模型已经实现了
计数器参数:
Removing logic from the View
从视图模型中删除逻辑同样有用。通过将验证、计算字段等逻辑推到模型中,您可以得到一个更轻、更清晰的视图模型。
?Make logic unit testable
模型本身非常容易进行单元测试。您不必担心模仿库,也不必担心使用处理外部服务的视图模型。
?reduce code redundance (duplication where needed)
多个视图模型可以共享同一个模型,从而减少验证、计算字段等的冗余。
Security
?The model contains properties that the user shall not change
然后不要在用户界面上公开它们。
?Automatic, but unwanted updates can happen if Binding to model
这一切都没有道理。如果你的虚拟机只是一个围绕你的模型的包装器,那么它无论如何都会把这些更新推下去。
Loose coupling
?If binding directly to the model, there will be a coupling between lower layers and View
当您在它们之间推送一个包装器VM时,这种耦合不会神奇地消失。
?Changing the Model causes changes in all the Views
更改模型会导致所有包装视图模型发生更改。更改视图模型也会导致所有视图发生更改。因此,模型仍然可以导致所有视图发生更改。
?The view is not depending on any given model
无论视图模型是否包装模型,这都是正确的。它只看到属性,而不是实际的类。
?Model can be easily generated with EF, some DSL, batch files, on so on
是的。通过一点工作,这些容易生成的模型可以包括有用的接口,比如inotifyDataErrorInfo、icChangeTracking和ieditableObject。
Speed of development
绑定到模型提供了更快的开发,因为您不必映射所有属性。
?You can start with a Prototype ViewModel hierarchy and bind to that
或者我可以从一个原型模型开始。通过添加包装器什么也得不到。
?If the model is still under development, you can start with a Prototype Model
?Model and ViewModel can be developed testdriven, no matter of the View
同样,在模型周围添加一个包装器并不能获得任何东西。
?视图完全可以由设计师或具有强大设计背景的开发人员构建。
同样,在模型周围添加一个包装器并不能获得任何好处。
"Tricky synchronizion" is solved
?There are plenty of solutions to any given"tricky synchronization" problem, e.g.
?AutoMapper
如果使用automapper将数据复制到视图模型中,那么就不会使用mvvvm模式。您只是在使用视图和模型。
?Event system from the model (model fires event, ViewModel subscribes)
你好,内存泄露。也就是说,除非您非常小心,并且放弃跨多个视图共享模型的能力。
Equal structure throughout the project
?There are points where have to a ViewModel, like SelectedItem
无关紧要。没有人反对非包装视图模型。
?Mixing Binding to Model and ViewModel is errorprone
不支持。
?It is harder for fresh developers to figure out structure of the project
不支持。
?starting to bring ViewModel later when there is no way around it is messy
无关紧要。同样,没有人反对使用非包装视图模型。
Scalability
?Lets define: If you do not use a ViewModel, it is not MVVM
无关紧要。第三次,没有人反对使用非包装视图模型。
?MVVM can be easily adopted to lots of datasources, lots of views
无关紧要。我们不是在争论是否使用MVVM,而是在争论如何最好地使用它。
?If you find any performance issues: Lazy loading and caching goes in the ViewModel
同意,但不相干。没有人建议您将服务调用推送到模型中。
Separation of concerns
这是包装视图模型下降最困难的地方。
视图模型已经必须处理UI数据(例如,模式、选定项)并承载调用外部服务的ICommand。
将所有的模型数据、验证逻辑、计算属性等推送到视图模型中会使操作更加繁琐。
你的问题没有"正确"的答案。当然,WPF会很高兴地允许您绑定到您声明为"模型"对象的任何对象;框架根本不关心。您不必总是遵循MVVM模式,因为您在WPF中执行应用程序。上下文始终是您编写的任何软件的关键。如果你时间紧迫,需要一个快速的原型,无论如何绑定到模型和重构,如果/当你需要的时候。
所以我想你真正要问的是"我什么时候应该使用MVVM模式?"
答案当然是,"当你的问题符合模式时"。
那么MVVM模式给了你什么呢?您已经列出了使用MVVM的几个原因,但是模式最重要的一个原因是松耦合——所有其他的排序都是这样的。
MVVM模式的整个要点是确保您的模型是一个状态机,它不知道如何向用户显示数据或从用户那里获取数据。在某种程度上,您的模型是以对模型有意义的格式构造的纯数据,而不是以对人类有意义的格式构造的。您的视图模型负责在纯数据平台(模型)和用户输入平台(视图)之间进行转换。视图模型与视图和模型紧密耦合,但重要的是模型对视图一无所知。
这里是一个简单的对象图。只是一些非常简单的模型,具有正常的属性更改和验证事件。
那些认为模型需要包装在视图模型中的人会显示您的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | 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(); } } } |
下面是一个典型的视图模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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; } } |
我同意里德的观点——视图模型是人们应该绑定的。我总是把模型想象成一组或多或少静态的值,这些值可能不会像ViewModel那样频繁或动态地变化。一般来说,我尝试将任何具有可以在模型编译时假定的值的东西,以及在运行时在ViewModel中确定的东西放在一起。
视图本身不应该有任何超过最简单的逻辑。其余的应该是对ViewModel的引用。安全性有时是个问题,但我喜欢这样做只是为了代码的可读性和简洁性。当所有的美学事物都在视图中完成时,处理代码就容易得多,所有的数学、逻辑事物都隐藏在视图模型中,所有的硬数据都在一个单独的模型中。
MVVM与MVC也有着密切的关系,其指导原则是模型和视图不应该直接看到彼此。再一次,对我来说,这是一件很清楚的事情。确定模型值应该如何更改的逻辑也应该在ViewModel/控制器中。这种观点本身不应该考虑。
把这个视图想象成一个接待员:它是一个友好的界面(与用户对话)。ViewModel是前台后面办公室的会计,模型是他/她的一套参考书和笔记。如果接待员开始在会计账簿的空白处写东西,抹掉会计的笔记,改变记录,事情就会变得混乱起来。