2

How do I call a function on a user control from a separate view model?

In my scenario, I have a "main" view with a ScoreDisplay UserControl:

<local:ScoreDisplay/>

This control will display the score of n number of players in my game. The main view model is hooked up to a game controller that feeds it the score updates. I need to get these updated scores to the UserControl (and run some animations, non-trivial logic, etc.)

I see a few options:

  1. Create a "Scores" dependency property and bind it to the view models collection of scores. The only problem I see with this approach is that whenever it is modified, I need to go look and see what changed so I can run the appropriate animations. Doing this is certainly possible, but doesn't seem "right".

  2. Have the ViewModel invoke a "UpdateScore" function on the UserControl. The only problem, of course, is that the ViewModel shouldn't know anything about the View and so shouldn't have the reference necessary to do this.

  3. Have the UserControl register for a "ScoreUpdated" event on the view model. This seems like the best option, but I have no idea on how to get the reference to the ViewModel in order to register for the event.

Which option, if any, is the correct approach? If (2) or (3), how can I implement this?

EDIT:

To be clear, the values in the scores collection are changing (the collection itself remains the same). I could put a wrapper around the score int and listen to PropertyChanged, but again, this seems like an overcomplicated approach to a simple problem. If that is the best solution though, please let me know!

EDIT 2:

UpdateScore is a function that (in theory) accepts the index of the updated score and the value to add to that player's score (it could accept the whole score). It then causes the player's peg to move along a cribbage track to its new position.

It gets called whenever a player gets points (this is a Cribbage game, so this happens a lot). The view model is attached to a game controller that raises an event to notify the VM that a player has been awarded points. The view model basically just needs to pass this information to ScoreDisplay for display/animation etc.

BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
  • If the scores is a collection, you can use an [`ObservableCollection`](http://msdn.microsoft.com/en-us/library/ms668604.aspx) for it as it implements [`INotifyCollectionChanges`](http://msdn.microsoft.com/en-us/library/System.Collections.Specialized.INotifyCollectionChanged.aspx) which tells you exactly *what* changed. – poke Mar 29 '14 at 19:36
  • That won't work because it is just the values that are changing (not insert/remove). Thanks for the comment though, I will edit my question to include this information. – BradleyDotNET Mar 29 '14 at 19:38
  • If the elements inside implement `INotifyPropertyChanged`, the collection will notify about those changes too though. – poke Mar 29 '14 at 19:40
  • The element inside is just an int (see my edit). Explicitly listening for a wrapper's PropertyChanged event could work, but I'm still wondering if there is a better way. – BradleyDotNET Mar 29 '14 at 19:41
  • @AnatoliyNikolaev I tried to include more information in Edit 2. Please let me know if I can provide any more! – BradleyDotNET Mar 29 '14 at 20:20
  • If I understand correctly, that if you use the second option, it should not violate the principle of MVVM. Only if: a function has no direct connection with the `View`. That is, there are no references to controls, there will only work with properties that are shown in the `View`. I think there will be an abstract relationship that may exist between the `View` and `ViewModel`. Tie to the ScoreUpdated event and call function UpdateScore in the `ViewModel`. – Anatoliy Nikolaev Mar 29 '14 at 20:41
  • I agree, that the event is the way to go, I'm just not sure how to get the necessary reference without using my answer below. I very much appreciate your time and input! – BradleyDotNET Mar 29 '14 at 20:46
  • If I'm not mistaken, we can apply the [`Mediator`](http://en.wikipedia.org/wiki/Mediator_pattern) pattern. With it you can pass a collection `ScoreUpdate` in ViewModel, which will change the data directly in the collection. If this is succeed, it will not need the event. If you're interested, I can show an example implementation, but does not guarantee what it will work :). – Anatoliy Nikolaev Mar 29 '14 at 21:07
  • I would love to see an example implementation, as I have not used that pattern before. Thanks! – BradleyDotNET Mar 29 '14 at 21:09

3 Answers3

3

In this case we can apply the Mediator pattern, if this is succeed, it will not need the event.

There are several embodiments of the Mediator pattern, but I like the most the implementation by XAML Guy, it is simple and clear - The Mediator Pattern.

Implementation code

public static class Mediator
{
    static IDictionary<string, List<Action<object>>> pl_dict = new Dictionary<string, List<Action<object>>>();

    static public void Register(string token, Action<object> callback)
    {
        if (!pl_dict.ContainsKey(token))
        {
            var list = new List<Action<object>>();
            list.Add(callback);
            pl_dict.Add(token, list);
        }
        else
        {
            bool found = false;
            foreach (var item in pl_dict[token])
                if (item.Method.ToString() == callback.Method.ToString())
                    found = true;
            if (!found)
                pl_dict[token].Add(callback);
        }
    }

    static public void Unregister(string token, Action<object> callback)
    {
        if (pl_dict.ContainsKey(token))
        {
            pl_dict[token].Remove(callback);
        }
    }

    static public void NotifyColleagues(string token, object args)
    {
        if (pl_dict.ContainsKey(token))
        {
            foreach (var callback in pl_dict[token])
                callback(args);
        }
    }
}

Communication via a Mediator is carried out as follows:

Mediator.NotifyColleagues("NameOfYourAction", ObjectValue);

In this case, we notify NameOfYourAction that you need to pass the ObjectValue for him. In order to NameOfYourAction successfully received the data, it is necessary to register it in the class or in the ViewModel like this:

private void NameOfYourAction_Mediator(object args)
{
    MyViewModel viewModel = args as MyViewModel;

    if (viewModel != null)
        viewModel.PropertyA = someValue;
}

// Somewhere, may be in constructor of class
Mediator.Register("NameOfYourAction", NameOfYourAction_Mediator);

In your case, the value is passed ScoreData in ViewModel, where which will be changes.

For more example of using pattern Mediator, please see this answer:

One ViewModel for UserControl and Window or separate ViewModels

Community
  • 1
  • 1
Anatoliy Nikolaev
  • 22,370
  • 15
  • 69
  • 68
  • 1
    That looks like a great approach! +1 for the very easy to understand example, I'll accept when I get it working. Thanks again! – BradleyDotNET Mar 29 '14 at 21:39
0

I found a way to do this:

I made a dependency property of my view model type:

public GameViewModel BaseViewModel
{
    get { return (GameViewModel)GetValue(baseViewModelProperty); }
    set { SetValue(baseViewModelProperty, value); }
}
public static readonly DependencyProperty baseViewModelProperty =
    DependencyProperty.Register("BaseViewModel", typeof(GameViewModel), typeof(ScoreDisplay), new PropertyMetadata(null, RegisterForScoreChange));

and changed my XAML to:

<local:ScoreDisplay BaseViewModel="{Binding}"/>

Then in the PropertyChanged event handler of the dependency property, I was able to wire up my event.

(e.NewValue as GameViewModel).ScoreUpdated += (d as ScoreDisplay).UpdateScore;
BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
0

Anatoliy Nikolaev's answer above is a good one.

As another option I would also suggest looking into the Event Aggregator. This is a great pattern and has many uses. They would both get a reference to the Event Aggregator and then one could publish an event and the other would receive it and could take action. If you enable something like this in your application it becomes trivial for multiple ViewModels to communicate in a completely decoupled way. And as an added bonus testing becomes simple as you can easily mock out your Event Aggregator to supply any data needed.

Kelly
  • 6,992
  • 12
  • 59
  • 76