MVVM在WPF - 如何以提醒模式变化的视图模型...还是要这样呢?(MVVM in WPF -

2019-07-21 18:08发布

我会通过一些MVVM文章,主要是这个和这个 。

我的具体问题是: 我如何沟通模式的转变从模型到视图模型?

在约什的文章中,我没有看到他这样做。 该视图模型始终要求提供属性的模型。 在Rachel的例子,她确实有模型实现INotifyPropertyChanged ,并从模型引发事件,但它们对于通过视图本身消耗(见她的文章/代码,以便在她为什么这个更详细)。

没有在任何地方我看到的例子,其中模式改变警报的视图模型到属性模特。 这有我担心,也许它不是出于某种原因做了。 是否有警报的型号变化的视图模型的模式? 这似乎是必要的,因为(1)令人信服地就有超过1个视图模型每个模型,和(2)即使只有一个视图模型,对模型的一些动作可能会导致其他属性被更改。

我怀疑有可能是答案/形式的意见“为什么要这么做?” 评论,所以这里是我的方案的说明。 我是新来的所以也许MVVM我的整个设计是错误的。 我将简要介绍它。

我不是“客户”或“产品”类的东西是更有趣的编程了(至少对我来说!)。 我编程的BlackJack。

我有一个观点,即不具有任何代码的背后,只是依赖于绑定到视图模型属性和命令(见约什 - 史密斯的文章)。

是好还是坏,我把该模型应该不仅包含类,如态度PlayingCardDeck ,也BlackJackGame ,保持整场比赛的状态,当玩家已经破产知道类,经销商有画卡,什么玩家和庄家目前的比分是(小于21,21,胸围等)。

BlackJackGame我揭露喜欢“的drawcard”的方法和它发生,我认为,当卡被抽出,如属性CardScoreIsBust应更新和这些新的价值观传达给视图模型。 也许这就是不完善的思维?

人们可以采取的态度是视图模型称为DrawCard()方法,所以他应该知道要问一个更新的分数,看看他是破产与否。 意见?

在我的ViewModel,我必须抓住一个扑克牌的实际图像的逻辑(基于西装,排名),并使其可用于视图。 该模型不应该担心这个问题(也许还有其他视图模型只是将使用代替扑克牌图像的数字)。 当然,也许有些人会告诉我,型号甚至不应该有二十一点游戏的概念,并认为应在视图模型处理?

Answer 1:

如果你希望你的模型,以提醒变化的的ViewModels,他们应该实现INotifyPropertyChanged的 ,并且应该的ViewModels订阅接收的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");
}

但通常情况下这是只需要如果不止一个对象将在更改模型的数据,这是不常见的情况。

如果你有,你实际上并不需要你的模型属性PropertyChanged事件附加到它的引用的话,那么你可以使用邮件系统,如棱镜的EventAggregator或MVVM光的Messenger

我有一个消息传递系统的简要概述在我的博客,但总结它,任何对象都可以广播消息,任何对象可以订阅侦听特定的消息。 所以,你可能播出PlayerScoreHasChangedMessage从一个对象,而另一个对象可以订阅收听这些类型的邮件和更新它的PlayerScore财产,当它听到一个。

但我不认为这是需要你所描述的系统。

在一个理想的世界MVVM,你的应用程序是由您的ViewModels的,和你的模型是只是用来构建应用程序的块。 他们通常只包含数据,所以不会有方法,如DrawCard()这将是在视图模型)

所以,你可能有一个像这些普通型号的数据对象:

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

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

    bool IsBust
    {
        get
        {
            return Score > 21;
        }
    }
}

和你有一个视图模型对象像

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);
    }
}

(以上对象都应该实现INotifyPropertyChanged ,但我离开它为简单起见)



Answer 2:

简短的回答:这取决于具体情况。

在您的示例模式正在更新“自己的”,当然,这些变化需要以某种方式传播的意见。 由于视图的只能直接访问的ViewModels,这意味着模型必须这些变化传送给相应的视图模型。 这样做的既定机制当然是INotifyPropertyChanged ,这意味着你会得到这样的工作流程:

  1. 视图模型创建和包装模型
  2. 视图模型订阅模型PropertyChanged事件
  3. 视图模型设置为视图的DataContext ,属性绑定等
  4. 查看触发的视图模型行动
  5. 视图模型上的模型调用方法
  6. 模型更新自身
  7. 视图模型处理模型PropertyChanged ,并提出了自己的PropertyChanged响应
  8. 查看反映在其绑定的变化,关闭反馈回路

在另一方面,如果你的模型包含很少(或没有)的业务逻辑,或者某些其他原因(如获得交易能力)你决定让每个视图模型“自己”的包装模式,那么所有的修改模型将通过的视图模型所以这样的布置将是不必要的。

我描述了这样的设计在另一个MVVM的问题在这里 。



Answer 3:

您的选择:

  • 执行INotifyPropertyChanged
  • 活动
  • POCO与代理操盘

在我看来, INotifyPropertyChanged是净的基本组成部分。 即其在System.dll 。 在你的“模型”实现它近乎于实现事件的结构。

如果你想纯POCO,那么你有效必须通过代理服务器/服务来操作你的对象,然后您的视图模型是通过监听代理变更通知。

个人而言,我只是松散地执行INotifyPropertyChanged然后用FODY做肮脏的工作对我来说。 它的外观和感觉POCO。

一个例子(使用FODY到IL编织的PropertyChanged饲养员):

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

     public event PropertyChangedEventHandler PropertyChanged;
}

然后你可以有你的ViewModel听的PropertyChanged任何变化; 或财产的具体变化。

在INotifyPropertyChanged的路线的美女,是要链接它与一个扩展的ObservableCollection 。 所以,你转储附近POCO对象到一个集合,并听取他们的集合......如果有什么变化,在任何地方,你了解它。

我会说实话,这可能加入讨论,其中,转予“为什么没有INotifyPropertyChanged的autmatically由编译器处理”:在C#中的每个对象应具备的设施,但其中有部分变更通知; 即在默认情况下执行INotifyPropertyChanged。 但它不和的最佳途径,这需要努力的量最少,是使用IL织(特别FODY )。



Answer 4:

相当老的线程,但很多搜索的,我想出了自己的解决方案后:一个PropertyChangedProxy

有了这个类,你可以很容易注册到别人的NotifyPropertyChanged,如果它被解雇的登记财产采取适当的行动。

下面是如何可以看,当你有一个模型属性“状态”,它可以改变它自己,然后会自动通知视图模型火它自己的PropertyChanged在它的“状态”属性,这样的观点也被通知像一个示例: )

public class MyModel : INotifyPropertyChanged
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set { _status = value; OnPropertyChanged(); }
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class MyViewModel : INotifyPropertyChanged
{
    public string Status
    {
        get { return _model.Status; }
    }

    private PropertyChangedProxy<MyModel, string> _statusPropertyChangedProxy;
    private MyModel _model;
    public MyViewModel(MyModel model)
    {
        _model = model;
        _statusPropertyChangedProxy = new PropertyChangedProxy<MyModel, string>(
            _model, myModel => myModel.Status, s => OnPropertyChanged("Status")
        );
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

和这里的类本身:

/// <summary>
/// Proxy class to easily take actions when a specific property in the "source" changed
/// </summary>
/// Last updated: 20.01.2015
/// <typeparam name="TSource">Type of the source</typeparam>
/// <typeparam name="TPropType">Type of the property</typeparam>
public class PropertyChangedProxy<TSource, TPropType> where TSource : INotifyPropertyChanged
{
    private readonly Func<TSource, TPropType> _getValueFunc;
    private readonly TSource _source;
    private readonly Action<TPropType> _onPropertyChanged;
    private readonly string _modelPropertyname;

    /// <summary>
    /// Constructor for a property changed proxy
    /// </summary>
    /// <param name="source">The source object to listen for property changes</param>
    /// <param name="selectorExpression">Expression to the property of the source</param>
    /// <param name="onPropertyChanged">Action to take when a property changed was fired</param>
    public PropertyChangedProxy(TSource source, Expression<Func<TSource, TPropType>> selectorExpression, Action<TPropType> onPropertyChanged)
    {
        _source = source;
        _onPropertyChanged = onPropertyChanged;
        // Property "getter" to get the value
        _getValueFunc = selectorExpression.Compile();
        // Name of the property
        var body = (MemberExpression)selectorExpression.Body;
        _modelPropertyname = body.Member.Name;
        // Changed event
        _source.PropertyChanged += SourcePropertyChanged;
    }

    private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _modelPropertyname)
        {
            _onPropertyChanged(_getValueFunc(_source));
        }
    }
}


Answer 5:

我发现这篇文章有帮助: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = WPF

我的总结:

MVVM组织背后的想法是允许的视图和模型,也更容易重用,让解耦测试。 您的视图模型为代表的观点实体的模型,模型代表的业务实体。

如果你想以后做一个扑克游戏? 大部分的用户界面应该是可重复使用的。 如果你的游戏逻辑是在您的视图模型结合起来,这将是非常难以重用这些元素,而无需重新编程视图模型。 如果你想改变你的用户界面是什么? 如果你的游戏逻辑耦合到您的视图模型的逻辑,你就需要重新检查你的游戏仍然有效。 如果你想什么来创建一个桌面和网络应用? 如果您的视图模型包含游戏逻辑,它会试图维持这两个应用程序并排侧的应用逻辑将不可避免地与视图模型的业务逻辑包扎变得复杂。

数据变更通知和数据验证发生在每一个层(视图,视图模型,并且模型)。

该模型包含特定于这些实体的数据表示(实体)和业务逻辑。 一副牌是具有固有特性的逻辑“东西”。 一个良好的平台不能有重复的卡放了进去。 它需要公开的方式来获取顶级卡(S)。 它需要知道不要泄露牌比已经离开。 这样的甲板行为是模型的一部分,因为它们固有的一副牌。 也将有经销商模式,球员模型,手模型等,这些模型可以和将要与之交互。

视图模型将包括呈现和应用逻辑的。 与显示游戏相关联的所有的工作是从游戏的逻辑分离。 这可能包括显示的手,图像,卡的经销商模式,用户的显示设置等要求

文章的勇气:

基本上,我想解释一下这个问题的方法是,你的业务逻辑和实体包括模型。 这是您的特定应用程序正在使用,但可以在许多应用程序共享。

视图是在表示层 - 任何与实际直接与用户交互。

该视图模型基本上是“胶水”这是特定于应用程序,这两个连接在一起。

我这里有一个很好的图,显示他们是如何接口:

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

在你的情况 - 让解决一些具体的...

验证:这通常涉及2种形式。 与用户输入验证会发生在视图模型(主要)和景观(即:“数字”文本框防止文本被输入的为您在视图处理等)。 这样,从用户输入的验证通常是VM关注。 话虽这么说,往往是验证的第二个“层” - 这是正在使用的数据业务规则相匹配的验证。 这往往是模型本身的一部分 - 当你将数据推送到你的模型,它可能会导致验证错误。 然后,VM将不得不重新映射这个信息反馈给视图。

操作“没有考虑到幕后,就像写DB,发送电子邮件等”:这确实是我的图表中的“域具体操作”的一部分,是真正的模型纯粹的一部分。 这是你想通过应用程序来揭露什么。 该视图模型充当网桥公开此信息,但操作是纯粹的模型。

对于视图模型操作:视图模型需要的不仅仅是更多的INPC - 它也需要特定于应用程序的任何操作(不是你的业务逻辑),如保存喜好和用户状态等,这将改变应用程序。 通过应用程序,接口相同的“模式”时也是如此。

想一想一个好办法 - 说出你想2个版本的订购系统。 首先是在WPF,和第二个是一个网络界面。

与该订单本身(发送电子邮件,进入DB等)涉及共享逻辑是模型。 您的应用程序公开这些操作和数据给用户,但在2种方式这样做。

在WPF应用程序,用户界面(什么给观众互动)是“​​视图” - 在Web应用程序,这基本上是(至少最终)变成JavaScript的HTML + CSS +客户端的代码。

该视图模型是需要的,以使其与您正在使用的特定视图技术/层的工作,以适应你的模型(这些操作与顺序)“胶水”的其余部分。



Answer 6:

基于INotifyPropertyChanged的INotifyCollectionChanged通知正是你所需要的。 为了简化与订阅你的生活性质的变化,属性名的编译时间验证,避免了内存泄漏,我会建议你使用PropertyObserver从约什-史密斯的MVVM基金会 。 作为这个项目是开源的,你可以只是类添加到从源项目。

要理解,如何使用PropertyObserver阅读这篇文章 。

此外,有在看的更深一些无扩展器(Rx) 。 您可以从模型中暴露IObserver <T>和订阅它在视图模型。



Answer 7:

这些家伙做了了不起的工作,回答这个问题,但是在这样的情况下,我真的觉得MVVM模式是一种痛苦,所以我会去和使用监督控制器或被动视图方式,并让绑定系统的至少去了模型对象是产生对自己的变化。



Answer 8:

我一直提倡的方向型号- >视图模型- >查看很长一段时间的变化的流程现在,你可以在我的变更流程的部分看到MVVM文章自2008年这需要实现INotifyPropertyChanged型号。 据我所知,它从此成为普遍的做法。

因为你提到的约什-史密斯,看看他的PropertyChanged类 。 它是订阅模型的一个辅助类INotifyPropertyChanged.PropertyChanged事件。

实际上,你可以更进一步采取这种方法,因为我已经通过创建recenty 我PropertiesUpdater类 。 在视图模型的属性被计算为复杂的表达式,其中包括在模型中的一个或多个属性。



Answer 9:

这没有什么错,以执行INotifyPropertyChanged内部模型,并听取其内部视图模型。 事实上,你甚至可以点到XAML模型的产权:{结合Model.ModelProperty}

至于相关的/计算的只读属性,到目前为止我还没有看到任何比这更好更简单: https://github.com/StephenCleary/CalculatedProperties 。 这很简单,但非常有用的,它是真正“Excel公式为MVVM” - 只是工作方式相同的Excel传播更改公式的单元格不从你身边额外的努力。



Answer 10:

您可以提高从模型,视图模型将需要订阅事件。

比如,我最近工作的一个项目,我不得不产生一个TreeView(当然,该模型具有层次性的话)。 在这个模型中我不得不叫一个ObservableCollection ChildElements

在视图模型,我已经存储到模型中的对象的引用,并订阅了CollectionChanged中的ObservableCollection的情况下,像这样: ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here) ...

然后,一旦在模型中的变化发生的视图模型就会自动通知。 您可以按照使用同一概念PropertyChanged ,但你需要从你的模型明确提升的属性更改事件为工作。



Answer 11:

这似乎对我来说,一个真正重要的问题 - 即使在没有压力这样做。 我工作的一个测试项目,其中涉及一个TreeView。 有迹象表明,被映射到命令菜单项的内容,例如删除。 目前,我更新这两个模型和视图模型中的视图模型。

例如,

public void DeleteItemExecute ()
{
    DesignObjectViewModel node = this.SelectedNode;    // Action is on selected item
    DocStructureManagement.DeleteNode(node.DesignObject); // Remove from application
    node.Remove();                                // Remove from view model
    Controller.UpdateDocument();                  // Signal document has changed
}

这很简单,但似乎有一个非常基本的缺陷。 一个典型的单元测试将执行该命令,然后检查结果在视图模型。 但是,这并不能测试模型更新是正确的,因为两者是同步更新。

因此,也许这是更好地使用技术,如PropertyObserver让模型更新触发视图模型的更新。 如果两个行动是成功的同样的单元测试将只有现在的工作。

这不是一个潜在的答案,我知道,但似乎值得投入在那里。



文章来源: MVVM in WPF - How to alert ViewModel of changes in Model… or should I?
标签: c# .net wpf mvvm