简体   繁体   中英

Invoke user control function from view model

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.

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

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;

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM