WPF中的MVVM - 如何提醒ViewModel模型中的更改...还是应该?

Dav*_*ave 105 .net c# wpf mvvm

我正在阅读一些MVVM文章,主要是这个这个.

我的具体问题是:如何将模型更改从模型传递到ViewModel?

在Josh的文章中,我没有看到他这样做.ViewModel始终向Model询问属性.在Rachel的例子中,她确实拥有模型实现INotifyPropertyChanged,并从模型中引发事件,但它们是供视图本身使用的(有关她为什么这样做的详细信息,请参阅她的文章/代码).

我在任何地方都看不到模型警告ViewModel模型属性更改的示例.这让我担心,也许是因为某些原因没有做到的. 是否有一种模式用于警告ViewModel模型中的更改? 这似乎是必要的,因为(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中处理?

Rac*_*hel 59

如果您希望模型向ViewModel发出更改警报,则应实现INotifyPropertyChanged,并且ViewModel应订阅以接收PropertyChange通知.

您的代码可能如下所示:

// Attach EventHandler
PlayerModel.PropertyChanged += PlayerModel_PropertyChanged;

...

// When property gets changed in the Model, raise the PropertyChanged 
// event of the ViewModel copy of the property
PlayerModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "SomeProperty")
        RaisePropertyChanged("ViewModelCopyOfSomeProperty");
}
Run Code Online (Sandbox Code Playgroud)

但通常只有在多个对象对模型数据进行更改时才需要这种情况,通常情况并非如此.

如果你有一个案例,你实际上没有引用你的Model属性来附加PropertyChanged事件,那么你可以使用消息系统,如Prism EventAggregator或MVVM Light Messenger.

我在我的博客上简要概述了消息传递系统,但总而言之,任何对象都可以广播消息,任何对象都可以订阅以侦听特定消息.因此,您可以PlayerScoreHasChangedMessage从一个对象广播,另一个对象可以订阅侦听这些类型的消息,并PlayerScore在听到一个消息时更新它的属性.

但我不认为这是您所描述的系统所必需的.

在理想的MVVM世界中,您的应用程序由ViewModel组成,而您的模型只是用于构建应用程序的块.它们通常只包含数据,因此不会有DrawCard()(例如在ViewModel中)的方法

所以你可能会有这样的普通模型数据对象:

class CardModel
{
    int Score;
    SuitEnum Suit;
    CardEnum CardValue;
}

class PlayerModel 
{
    ObservableCollection<Card> FaceUpCards;
    ObservableCollection<Card> FaceDownCards;
    int CurrentScore;

    bool IsBust
    {
        get
        {
            return Score > 21;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你有一个像ViewModel这样的对象

public class GameViewModel
{
    ObservableCollection<CardModel> Deck;
    PlayerModel Dealer;
    PlayerModel Player;

    ICommand DrawCardCommand;

    void DrawCard(Player currentPlayer)
    {
        var nextCard = Deck.First();
        currentPlayer.FaceUpCards.Add(nextCard);

        if (currentPlayer.IsBust)
            // Process next player turn

        Deck.Remove(nextCard);
    }
}
Run Code Online (Sandbox Code Playgroud)

(上面的对象都应该实现INotifyPropertyChanged,但为了简单起见,我把它留下了)

  • 这是得票最高和接受最高的答案!?不不不!业务逻辑保留在模型中,视图模型处理应用程序逻辑。视图模型不应该知道游戏的实际规则! (7认同)
  • 感谢Rachel的帮助.我将不得不再研究一下或写另一个问题; 我仍然对游戏逻辑的位置感到困惑.你(和其他人)主张把它放在ViewModel中,其他人说"业务逻辑"在我的例子中我假设是游戏规则和游戏状态属于模型(参见例如:http://msdn.microsoft.com /en-us/library/gg405484%28v=pandp.40%29.aspx)和http://stackoverflow.com/questions/10964003/where-does-business-logic-sit-in-mvvm).我认识到在这个简单的游戏中,它可能并不重要.但很高兴知道.Thxs! (5认同)
  • @Dave是的,`DrawCard()`方法将与你的其他游戏逻辑一起在ViewModel中.在理想的MVVM应用程序中,您应该能够在没有UI的情况下运行应用程序,只需创建ViewModel并运行其方法,例如通过测试脚本或命令提示符窗口.模型通常只包含原始数据和基本数据验证的数据模型. (3认同)
  • 更笼统地说,所有业务逻辑/规则都包含在模型中吗?逻辑上说可以最多拿21张卡(但发牌人保持在17位),可以拆分卡等在哪里去了?我以为所有这些都属于模型类,因此我觉得我需要模型中的BlacJackGame控制器类。我仍在尝试了解这一点,并希望您能喜欢示例/参考。例如,二十一点的想法是从iOS编程的iTunes类中提出的,其中业务逻辑/规则无疑是MVC模式的模型类。 (2认同)
  • 避免内存泄漏.使用WeakEvent模式.https://joshsmithonwpf.wordpress.com/2009/07/11/one-way-to-avoid-messy-propertychanged-event-handling/ (2认同)

Jon*_*Jon 21

简短回答:这取决于细节.

在您的示例中,模型正在"自行"更新,当然这些更改需要以某种方式传播到视图.由于视图只能直接访问视图模型,因此表示模型必须将这些更改传递给相应的视图模型.这样做的既定机制当然是INotifyPropertyChanged,这意味着您将获得这样的工作流程:

  1. Viewmodel已创建并包装模型
  2. Viewmodel订阅模型的PropertyChanged事件
  3. Viewmodel设置为视图DataContext,属性绑定等
  4. View触发viewmodel上的操作
  5. Viewmodel在模型上调用方法
  6. 模型更新自己
  7. Viewmodel处理模型PropertyChanged并提升自己PropertyChanged的响应
  8. 视图反映了其绑定的变化,关闭了反馈循环

另一方面,如果您的模型包含很少(或没有)业务逻辑,或者由于某些其他原因(例如获得事务性能),您决定让每个视图模型"拥有"其包装模型,那么对模型的所有修改都将通过视图模型因此不需要这样的安排.

我在这里描述了另一个MVVM问题中的这种设计.


Mei*_*hes 6

您的选择:

  • 实施 INotifyPropertyChanged
  • 活动
  • POCO 与代理操纵器

在我看来,它INotifyPropertyChanged是 .Net 的基本组成部分。即它在System.dll. 在“模型”中实现它类似于实现事件结构。

如果你想要纯粹的 POCO,那么你实际上必须通过代理/服务来操作你的对象,然后你的 ViewModel 通过侦听代理来收到更改通知。

就我个人而言,我只是松散地实现 INotifyPropertyChanged,然后使用FODY为我做脏活。它的外观和感觉都是 POCO。

一个示例(使用 FODY 来 IL Weave PropertyChanged 加注者):

public class NearlyPOCO: INotifyPropertyChanged
{
     public string ValueA {get;set;}
     public string ValueB {get;set;}

     public event PropertyChangedEventHandler PropertyChanged;
}
Run Code Online (Sandbox Code Playgroud)

然后你可以让你的 ViewModel 监听 PropertyChanged 的​​任何变化;或属性特定的更改。

INotifyPropertyChanged 路线的美妙之处在于您可以将其与Extended ObservableCollection链接起来。因此,您将附近的 poco 对象转储到一个集合中,然后聆听该集合......如果任何地方发生任何变化,您都会了解它。

老实说,这可以加入“为什么编译器不自动处理 INotifyPropertyChanged”的讨论,该讨论的主题是:c# 中的每个对象都应该具有通知其任何部分是否发生更改的功能;即默认实现 INotifyPropertyChanged。但事实并非如此,需要最少努力的最佳途径是使用 IL Weaving(特别是FODY)。


Vot*_*fee 5

我发现这篇文章很有帮助: 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 应用程序怎么办?如果您的视图模型包含游戏逻辑,则尝试并行维护这两个应用程序将变得复杂,因为应用程序逻辑将不可避免地与视图模型中的业务逻辑绑定在一起。

数据更改通知和数据验证发生在每一层(视图、视图模型和模型)。

该模型包含您的数据表示(实体)和特定于这些实体的业务逻辑。一副纸牌是具有固有属性的逻辑“事物”。好的牌组不能放入重复的牌。它需要公开一种获得顶牌的方法。它需要知道不要发出比剩余的更多的牌。这种牌组行为是模型的一部分,因为它们是一副牌所固有的。还会有庄家模型、玩家模型、手牌模型等。这些模型可以并且将会交互。

视图模型将由表示和应用程序逻辑组成。所有与显示游戏相关的工作都与游戏的逻辑分开。这可能包括将手牌显示为图像、向荷官模型请求牌、用户显示设置等。

文章的核心内容:

基本上,我喜欢解释这一点的方式是您的业务逻辑和实体组成了模型。这是您的特定应用程序正在使用的内容,但可以在许多应用程序之间共享。

视图是表示层 - 与实际直接与用户交互相关的任何内容。

ViewModel 基本上是特定于您的应用程序的“粘合剂”,它将两者链接在一起。

我这里有一个很好的图表,显示了它们的交互方式:

http://reedcopsey.com/2010/01/06/better-user-and-developer-experiences-from-windows-forms-to-wpf-with-mvvm-part-7-mvvm/

就您而言 - 让我们解决一些具体问题......

验证:这通常有两种形式。与用户输入相关的验证将在 ViewModel(主要)和视图中进行(即:在视图中为您处理防止输入文本的“数字”文本框等)。因此,用户输入的验证通常是虚拟机关注的问题。话虽这么说,通常还有第二个“层”验证 - 这是验证所使用的数据是否符合业务规则。这通常是模型本身的一部分 - 当您将数据推送到模型时,可能会导致验证错误。然后,虚拟机必须将此信息重新映射回视图。

“没有视图的幕后操作,例如写入数据库、发送电子邮件等”:这实际上是我的图中“特定于域的操作”的一部分,并且实际上纯粹是模型的一部分。这就是您试图通过应用程序公开的内容。ViewModel 充当公开此信息的桥梁,但操作是纯模型的。

ViewModel 的操作:ViewModel 需要的不仅仅是 INPC - 它还需要特定于您的应用程序(而不是您的业务逻辑)的任何操作,例如保存首选项和用户状态等。这将因应用程序而异。通过应用程序,即使在连接相同的“模型”时也是如此。

一个很好的思考方式 - 假设您想要制作 2 个版本的订购系统。第一个是 WPF,第二个是 Web 界面。

处理订单本身(发送电子邮件、输入数据库等)的共享逻辑是模型。您的应用程序向用户公开这些操作和数据,但有两种方式。

在 WPF 应用程序中,用户界面(查看器与之交互的)是“视图” - 在 Web 应用程序中,这基本上是(至少最终)在客户端上转换为 javascript + html + css 的代码。

ViewModel 是调整模型(这些与排序相关的操作)所需的其余“粘合剂”,以使其与您正在使用的特定视图技术/层一起工作。