我的具体问题是: How do I communicate Model changes from the Model to the ViewModel?
在Josh _239973中看到他这样做了 . ViewModel始终向Model询问属性 . 在Rachel的例子中,她确实拥有模型实现 INotifyPropertyChanged
,并从模型中引发事件,但它们是供视图本身使用的(有关她为什么这样做的详细信息,请参阅她的文章/代码) .
我在任何地方都看不到模型警告ViewModel模型属性更改的示例 . 这让我担心,也许是因为某些原因没有做到的 . Is there a pattern for alerting the ViewModel of changes in the Model? 似乎有必要,因为(1)可以想象每个模型有超过1个ViewModel,(2)即使只有一个ViewModel,模型上的某些操作可能会导致其他属性被更改 .
我怀疑可能会有“你为什么要这样做?”的答案/评论 . 评论,所以这里是我的程序的描述 . 我是MVVM的新手,所以也许我的整个设计都有问题 . 我将简要介绍一下 .
我编写的东西比“客户”或“产品”类更有趣(至少对我而言!) . 我在编程BlackJack .
我有一个View,后面没有任何代码,只依赖于绑定到ViewModel中的属性和命令(参见Josh Smith的文章) .
无论好坏,我采取的态度是模型应该不仅包含诸如 PlayingCard
, Deck
之类的类,而且还包含保持整个游戏状态的 BlackJackGame
类,并且知道玩家何时破产,经销商必须绘制卡片,以及玩家和经销商当前得分(小于21,21,胸围等) .
从 BlackJackGame
开始我暴露像"DrawCard"这样的方法,我想到当绘制一张卡时,应该更新 CardScore
和 IsBust
等属性,并将这些新值传递给ViewModel . 也许这是错误的思考?
人们可以采取ViewModel称之为 DrawCard()
方法的态度,因此他应该知道要求更新的分数并找出他是否破产 . 意见?
在我的ViewModel中,我有逻辑来获取扑克牌的实际图像(基于套装,等级)并使其可用于视图 . 该模型不应该与此有关(也许其他ViewModel只会使用数字而不是扑克牌图像) . 当然,也许有人会告诉我模型甚至不应该有BlackJack游戏的概念,而应该在ViewModel中处理?
11 回答
我一直在提倡定向模型 - >查看模型 - >查看更改流程很长一段时间,正如您在2008年MVVM article的更改流程部分中所看到的那样 . 这需要在模型上实现
INotifyPropertyChanged
. 据我所知,它已成为惯例 .因为你提到约什史密斯,看看his PropertyChanged class . 它's a helper class for subscribing to the model' s
INotifyPropertyChanged.PropertyChanged
事件 .实际上你可以通过创建my PropertiesUpdater class来进一步采用这种方法 . 视图模型上的属性计算为包含模型上一个或多个属性的复杂表达式 .
基于 INotifyPropertyChanged 和 INotifyCollectionChanged 的通知正是您所需要的 . 为了通过订阅属性更改来简化您的生活,对属性名称进行编译时验证,避免内存泄漏,我建议您使用Josh Smith's MVVM Foundation来自Josh Smith's MVVM Foundation . 由于此项目是开源的,因此您可以从源中仅将该类添加到项目中 .
要了解,如何使用PropertyObserver读取this article .
另外,请深入了解Reactive Extensions (Rx) . 您可以从模型中公开 IObserver<T> 并在视图模型中订阅它 .
在我看来,这似乎是一个非常重要的问题 - 即使没有压力要做 . 我正在开发一个涉及TreeView的测试项目 . 有菜单项和映射到命令的项目,例如Delete . 目前,我正在视图模型中更新模型和视图模型 .
例如,
这很简单,但似乎有一个非常基本的缺陷 . 典型的单元测试将执行命令,然后在视图模型中检查结果 . 但这并不能测试模型更新是否正确,因为两者同时更新 .
因此,最好使用PropertyObserver等技术让模型更新触发视图模型更新 . 现在,只有两个操作都成功,相同的单元测试才会起作用 .
我意识到,这不是一个潜在的答案,但似乎值得推出 .
在Model内部实现 INotifyPropertyChanged 并在ViewModel中监听它没有错 . 实际上你甚至可以在XAML中点入模型的属性:
至于依赖/计算的只读属性,到目前为止我还没有看到比这更好,更简单的东西:https://github.com/StephenCleary/CalculatedProperties . 它's very simple but incredibly useful, it'真的"Excel formulas for MVVM" - 就像Excel传播对公式单元格的更改一样没有你额外的努力 .
相当老的线程,但经过大量的搜索,我想出了我自己的解决方案:一个PropertyChangedProxy
使用此类,您可以轻松注册到其他人的NotifyPropertyChanged,并在注册属性被触发时采取适当的操作 .
下面是一个示例,当你有一个模型属性“Status”可以改变它自己然后应该自动通知ViewModel在它的“Status”属性上触发它自己的PropertyChanged以便视图也被通知时: )
这是 class 本身:
您可以从模型中引发事件,viewmodel需要订阅该事件 .
例如,我最近在一个项目上工作,我必须生成一个树视图(当然,模型具有层次结构性质) . 在模型中,我有一个名为
ChildElements
的observablecollection .在viewmodel中,我已经存储了对模型中对象的引用,并订阅了observablecollection的
CollectionChanged
事件,如下所示:ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)
...然后,一旦模型中发生更改,您的viewmodel就会自动得到通知 . 您可以使用
PropertyChanged
遵循相同的概念,但是您需要从模型中明确地引发属性更改事件才能生效 .你的选择:
实施INotifyPropertyChanged
活动
POCO与代理操纵器
在我看来,
INotifyPropertyChanged
是.Net的基本组成部分 . 即它在System.dll
. 在"Model"中实现它类似于实现事件结构 .如果你想要纯POCO,那么你实际上必须通过代理/服务来操纵你的对象,然后通过监听代理通知你的ViewModel变化 .
就个人而言,我只是松散地实现了INotifyPropertyChanged,然后使用FODY为我做了肮脏的工作 . 它看起来和感觉POCO .
一个例子(使用FODY到IL编织PropertyChanged提升者):
那么你可以让你的ViewModel听取PropertyChanged的任何变化;或 property 特定的变化 .
INotifyPropertyChanged路线的美妙之处在于你用Extended ObservableCollection链接它 . 所以你将你附近的poco对象转移到一个集合中,并听取集合......如果有任何变化,无论在哪里,你都会了解它 .
我需要最少努力的最佳途径是使用IL Weaving(特别是FODY) .
这些家伙做了一个惊人的工作回答这个,但在这样的情况下,我真的觉得MVVM模式是一个痛苦所以我会去使用监督控制器或被动视图方法,并放弃绑定系统至少为模型对象,是自己产生变化 .
如果您希望模型向ViewModel发出更改警报,则应实现INotifyPropertyChanged,并且ViewModel应订阅以接收PropertyChange通知 .
您的代码可能如下所示:
但通常只有在多个对象对模型数据进行更改时才需要这种情况,通常情况并非如此 .
如果您遇到过't actually have a reference to your Model property to attach the PropertyChanged event to it, then you can use a Messaging system such as Prism' s
EventAggregator
或MVVM Light的Messenger
.我的博客上有brief overview of messaging systems,但总结一下,任何对象都可以广播消息,任何对象都可以订阅以侦听特定消息 . 因此,您可以从一个对象广播
PlayerScoreHasChangedMessage
,另一个对象可以订阅侦听这些类型的消息,并在听到一个消息时更新它的PlayerScore
属性 .但我不认为这是您所描述的系统所必需的 .
在理想的MVVM世界中,您的应用程序由ViewModel组成,而您的模型只是用于构建应用程序的块 . 它们通常只包含数据,因此不会有
DrawCard()
等方法(将在ViewModel中)所以你可能会有这样的普通模型数据对象:
你有一个像ViewModel这样的对象
(上面的对象都应该实现
INotifyPropertyChanged
,但为了简单起见,我把它留下了)简短回答:这取决于细节 .
在您的示例中,模型正在更新"on their own",这些更改当然需要以某种方式传播到视图 . 由于视图只能直接访问视图模型,因此表示模型必须将这些更改传递给相应的视图模型 . 这样做的既定机制当然是
INotifyPropertyChanged
,这意味着你将获得这样的工作流程:创建了Viewmodel并包装了模型
Viewmodel订阅了模型的
PropertyChanged
事件Viewmodel设置为视图
DataContext
,属性绑定等View触发viewmodel上的操作
Viewmodel在模型上调用方法
模型更新自身
Viewmodel处理模型的
PropertyChanged
并在响应中引发自己的PropertyChanged
视图反映了其绑定的变化,关闭了反馈循环
另一方面,如果你的模型包含很少(或没有)业务逻辑,或者如果由于某些其他原因(例如获得事务性能),您决定让每个视图模型“拥有”其包装模型,那么对模型的所有修改都将通过视图模型,因此不需要这样的安排 .
我在另一个MVVM问题here中描述了这样的设计 .
我发现这篇文章有用:http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum=wpf
我的总结:
MVVM组织背后的想法是允许更容易地重用视图和模型,并允许解耦测试 . 您的视图模型是表示视图实体的模型,您的模型表示业务实体 .
如果你想在以后制作扑克游戏怎么办? UI的大部分应该是可重用的 . 如果您的游戏逻辑在视图模型中被绑定,那么重用这些元素将非常困难,而无需重新编程视图模型 . 如果您想更改用户界面怎么办?如果您的游戏逻辑与视图模型逻辑耦合,则需要重新检查您的游戏是否仍然有效 . 如果您想创建桌面和Web应用程序,该怎么办?如果您的视图模型包含游戏逻辑,那么尝试并排维护这两个应用程序会变得很复杂,因为应用程序逻辑将不可避免地与视图模型中的业务逻辑绑定 .
数据更改通知和数据验证发生在每个层(视图,视图模型和模型)中 .
该模型包含您的数据表示(实体)和特定于这些实体的业务逻辑 . 一副牌是具有固有属性的逻辑“事物” . 好的套牌不能放入重复的卡片 . 它需要暴露一种获得顶级卡的方式 . 它需要知道不要提供比它剩下的更多的卡 . 这种甲板行为是模型的一部分,因为它们是一副牌固有的 . 还有经销商模型,玩家模型,手模型等 . 这些模型可以并且将会互动 .
视图模型将包含表示和应用程序逻辑 . 与显示游戏相关的所有工作都与游戏的逻辑分开 . 这可以包括将手显示为图像,向经销商模型请求卡,用户显示设置等 .
文章的内容: