124

I am going through some MVVM articles, primarily this and this.

My specific question is: How do I communicate Model changes from the Model to the ViewModel?

In Josh's article, I don't see that he does this. The ViewModel always asks the Model for properties. In Rachel's example, she does have the model implement INotifyPropertyChanged, and raises events from the model, but they are for consumption by the view itself (see her article/code for more detail on why she does this).

Nowhere do I see examples where the model alerts the ViewModel of changes to model properties. This has me worried that perhaps it's not done for some reason. Is there a pattern for alerting the ViewModel of changes in the Model? It would seem to be necessary as (1) conceivably there are more than 1 ViewModel for each model, and (2) even if there is just one ViewModel, some action on the model might result in other properties being changed.

I suspect that there might be answers/comments of the form "Why would you want to do that?" comments, so here's a description of my program. I'm new to MVVM so perhaps my whole design is faulty. I'll briefly describe it.

I am programming up something that is more interesting (at least, to me!) than "Customer" or "Product" classes. I am programming BlackJack.

I have a View that doesn't have any code behind and just relies on binding to properties and commands in the ViewModel (see Josh Smith's article).

For better or worse, I took the attitude that the Model should contain not just classes such as PlayingCard, Deck, but also the BlackJackGame class that keeps state of the whole game, and knows when the player has gone bust, the dealer has to draw cards, and what the player and dealer current score is (less than 21, 21, bust, etc.).

From BlackJackGame I expose methods like "DrawCard" and it occurred to me that when a card is drawn, properties such as CardScore, and IsBust should be updated and these new values communicated to the ViewModel. Perhaps that's faulty thinking?

One could take the attitude that the ViewModel called the DrawCard() method so he should know to ask for an updated score and find out if he is bust or not. Opinions?

In my ViewModel, I have the logic to grab an actual image of a playing card (based on suit,rank) and make it available for the view. The model should not be concerned with this (perhaps other ViewModel would just use numbers instead of playing card images). Of course, perhaps some will tell me that the Model should not even have the concept of a BlackJack game and that should be handled in the ViewModel?

4

11 回答 11

68

If you want your Models to alert the ViewModels of changes, they should implement INotifyPropertyChanged, and the ViewModels should subscribe to receive PropertyChange notifications.

Your code might look something like this:

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

But typically this is only needed if more than one object will be making changes to the Model's data, which is not usually the case.

If you ever have a case where you don'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 or MVVM Light's Messenger.

I have a brief overview of messaging systems on my blog, however to summarize it, any object can broadcast a message, and any object can subscribe to listen for specific messages. So you might broadcast a PlayerScoreHasChangedMessage from one object, and another object can subscribe to listen for those types of messages and update it's PlayerScore property when it hears one.

But I don't think this is needed for the system you have described.

In an ideal MVVM world, your application is comprised of your ViewModels, and your Models are the just the blocks used to build your application. They typically only contain data, so would not have methods such as DrawCard() (that would be in a ViewModel)

So you would probably have plain Model data objects like these:

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

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

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

and you'd have a ViewModel object like

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

(Above objects should all implement INotifyPropertyChanged, but I left it out for simplicity)

于 2013-03-15T19:48:10.317 回答
25

Short answer: it depends on the specifics.

In your example the models are being updated "on their own" and these changes of course need to somehow propagate to the views. Since the views can only directly access the viewmodels, it means the model must communicate these changes to the corresponding viewmodel. The established mechanism for doing so is of course INotifyPropertyChanged, which means that you 'll get a workflow like this:

  1. Viewmodel is created and wraps model
  2. Viewmodel subscribes to model's PropertyChanged event
  3. Viewmodel is set as view's DataContext, properties are bound etc
  4. View triggers action on viewmodel
  5. Viewmodel calls method on model
  6. Model updates itself
  7. Viewmodel handles model's PropertyChanged and raises its own PropertyChanged in response
  8. View reflects the changes in its bindings, closing the feedback loop

On the other hand if your models contained little (or no) business logic, or if for some other reason (such as gaining transactional capability) you decided to let each viewmodel "own" its wrapped model then all modifications to the model would pass through the viewmodel so such an arrangement would not be necessary.

I describe such a design in another MVVM question here.

于 2013-03-15T19:17:41.837 回答
4

Your choices:

  • Implement INotifyPropertyChanged
  • Events
  • POCO with Proxy manipulator

As I see it, INotifyPropertyChanged is a fundamental part of .Net. i.e. its in System.dll. Implementing it in your "Model" is akin to implementing an event structure.

If you want pure POCO, then you effectively have to manipulate your objects via proxies/services and then your ViewModel is notified of changes by listening to the proxy.

Personally I just loosely implement INotifyPropertyChanged and then use FODY to do the dirty work for me. It looks and feels POCO.

An example (using FODY to IL Weave the PropertyChanged raisers):

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

     public event PropertyChangedEventHandler PropertyChanged;
}

then you can have your ViewModel listen to PropertyChanged for any changes; or property specific changes.

The beauty of the INotifyPropertyChanged route, is you chain it up with an Extended ObservableCollection. So you dump your near poco objects into a collection, and listen to the collection... if anything changes, anywhere, you learn about it.

I'll be honest, this could join the "Why wasn't INotifyPropertyChanged autmatically handled by the compiler" discussion, which devolves to: Every object in c# should have the facility to notify if any part of it was changed; i.e. implement INotifyPropertyChanged by default. But it doesn't and the best route, that requires the least amount of effort, is to use IL Weaving (specifically FODY).

于 2013-03-15T19:30:34.493 回答
4

Fairly old thread but after a lot of searching I came up with my own solution: A PropertyChangedProxy

With this class, you can easily register to someone else's NotifyPropertyChanged and take appropriate actions if it is fired for the registered property.

Here's a sample of how this could look like when you have a model property "Status" which can change on it's own and then should automatically notify the ViewModel to fire it's own PropertyChanged on it's "Status" property so that the view is also notified :)

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

and here's the class itself:

/// <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));
        }
    }
}
于 2015-01-20T14:38:49.507 回答
3

I found this article helpful: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum=wpf

My summary:

The idea behind MVVM organization is to allow easier reuse of views and models and also to allow decoupled testing. Your view-model is a model that represent the view entities, your model represents the business entities.

What if you wanted to make a poker game later? Much of the UI should be reusable. If your game logic is bound up in your view-model, it would be very hard to reuse those elements without having to reprogram the view-model. What if you want to change your user interface? If your game logic is coupled to your view-model logic, you would need to recheck that your game still works. What if you want to create a desktop and a web app? If your view-model contains the game logic, it would become complicated trying to maintain these two applications side-by-side as the application logic would inevitably be bound up with the business logic in the view-model.

Data change notifications and data validation happen in every layer (the view, the view-model, and the model).

The model contains your data representations (entities) and business logic specific to those entities. A deck of cards is a logical 'thing' with inherent properties. A good deck can't have duplicate cards put into it. It needs to expose a way to get the top card(s). It needs to know not to give out more cards than it has left. Such deck behaviors are part of the model because they inherent to a deck of cards. There will also be dealer models, player models, hand models, etc. These models can and will interact.

The view-model would consist of the presentation and application logic. All the work associated with displaying the game is separate from the logic of the game. This could include displaying hands as images, requests for cards to the dealer model, user display settings, etc.

The guts of the article:

Basically, the way that I like to explain this is that your business logic and entities comprise the model. This is what your specific application is using, but could be shared across many applications.

The View is the presentation layer - anything relating to actually directly interfacing with the user.

The ViewModel is basically the "glue" that's specific to your application that links the two together.

I have a nice diagram here that shows how they interface:

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

In your case - lets tackle some of the specifics...

Validation: This typically comes in 2 forms. The validation related to user input would happen in the ViewModel (primarily) and the View (ie: "Numeric" TextBox preventing text from being entered is handled for you in the view, etc). As such, the validation of the input from the user is typically a VM concern. That being said, there's often a second "layer" of validation - this is the validation that the data being used matches the business rules. This often is part of the model itself - when you push data to your Model, it may cause validation errors. The VM will then have to remap this information back to the View.

Operations "behind the scenes with no view, like writing to DB, sending email, etc": This is really part of the "Domain Specific Operations" in my diagram, and is really purely part of the Model. This is what you're trying to expose via the application. The ViewModel acts as a bridge to expose this information, but the operations are pure-Model.

Operations for the ViewModel: The ViewModel needs more than just INPC - it also needs any operation that are specific to your application (not your business logic), such as saving preferences and user state, etc. This is going to vary app. by app., even when interfacing the same "model".

A good way to think about it - Say you want to make 2 versions of your ordering system. The first is in WPF, and the second is a web interface.

The shared logic that deals with the orders themselves (sending emails, entering into DB, etc) is the Model. Your application is exposing these operations and data to the user, but doing it in 2 ways.

In the WPF application, the user interface (what the viewer interacts with) is the "view" - in the web application, this is basically the code that (at least eventually) is turned into javascript + html + css on the client.

The ViewModel is the rest of the "glue" that is required to adapt your model (these operations related to ordering) in order to make it work with the specific view technology/layer you're using.

于 2014-05-20T19:29:23.027 回答
2

Notification based on INotifyPropertyChanged and INotifyCollectionChanged is exactly what you need. To simplify your life with subscription to property changes, compile-time validation of property name, avoiding memory leaks, I would advice you to use PropertyObserver from Josh Smith's MVVM Foundation. As this project is open source, you can add just that class to your project from sources.

To understand, how to use PropertyObserver read this article.

Also, have a look deeper at Reactive Extensions (Rx). You can expose IObserver<T> from your model and subscribe to it in view model.

于 2013-03-16T12:05:43.043 回答
1

I have been advocating the directional Model -> View Model -> View flow of changes for a long time now, as you can see in the Flow of Changes section of my MVVM article from 2008. This requires implementing INotifyPropertyChanged on the model. As far as I can tell, it's since become common practice.

Because you mentioned Josh Smith, take a look at his PropertyChanged class. It's a helper class for subscribing to the model's INotifyPropertyChanged.PropertyChanged event.

You can actually take this approach much further, as I have recenty by creating my PropertiesUpdater class. Properties on the view-model are computed as complex expressions that include one or more properties on the model.

于 2013-03-15T19:26:27.697 回答
1

The guys did an amazing job answering this but in situations like this i really feel that the MVVM pattern is a pain so i would go and use a Supervising Controller or a Passive View approach and let go of the binding system at least for model objects that are generate changes on their own.

于 2013-03-15T22:31:14.300 回答
1

There's nothing wrong to implement INotifyPropertyChanged inside Model and listen to it inside ViewModel. In fact you can even dot into model's property right in XAML : {Binding Model.ModelProperty}

As for dependent / calculated read-only properties, by far I haven't seen anything better and simpler than this: https://github.com/StephenCleary/CalculatedProperties. It's very simple but incredibly useful, it's really "Excel formulas for MVVM" - just works same way as Excel propagating changes to formula cells without extra effort from your side.

于 2017-01-04T11:23:22.227 回答
0

You can raise events from the model, which the viewmodel would need to subscribe to.

For example, I recently worked on a project for which I had to generate a treeview (naturally, the model had a hierarchical nature to it). In the model I had an observablecollection called ChildElements.

In the viewmodel, I had stored a reference to the object in the model, and subscribed to the CollectionChanged event of the observablecollection, like so: ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

Then your viewmodel gets automatically notified once a change happens in the model. You can follow the same concept using PropertyChanged, but you will need to explicitly raise property change events from your model for that to work.

于 2013-03-15T18:47:12.780 回答
0

This seems to me like a really important question - even when there is no pressure to do it. I am working on a test project, which involves a TreeView. There are menu items and such that are mapped to commands, for example Delete. Presently, I am updating both the model and the view model from within the view model.

For example,

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
}

This is simple, but seems to have a very basic flaw. A typical unit test would execute the command, then check the result in the view model. But this does not test that the model update was correct, since the two are updated simultaneously.

So perhaps it is better to use techniques like PropertyObserver to let the model update trigger a view model update. The same unit test would now only work if both actions were successful.

This is not a potential answer, I realize, but it seems worth putting out there.

于 2015-11-04T22:57:14.080 回答