从视图模型调用用户控制函数

本文关键字:控制 函数 用户 调用 视图 模型 | 更新日期: 2023-09-27 17:55:05

如何从单独的视图模型调用用户控件上的函数?

在我的场景中,我有一个"主"视图与ScoreDisplay UserControl:

<local:ScoreDisplay/>

这个控件将显示n玩家在我的游戏中的分数。主视图模型连接到游戏控制器,为其提供分数更新。我需要得到这些更新的分数到UserControl(并运行一些动画,非琐碎的逻辑,等)

我看到一些选项:

  1. 创建一个"Scores"依赖属性,并将其绑定到视图模型的分数集合。我认为这种方法的唯一问题是,无论何时修改它,我都需要查看更改的内容,以便运行适当的动画。这样做当然是可能的,但似乎不"正确"。

  2. 让ViewModel调用UserControl上的"UpdateScore"函数。唯一的问题,当然,是ViewModel不应该知道任何关于视图,所以不应该有必要的引用来做这个

  3. 让UserControl注册视图模型上的"ScoreUpdated"事件。这似乎是最好的选择,但我不知道如何获得对ViewModel的引用,以便注册事件。

如果有的话,哪个选项是正确的方法?如果(2)或(3),我如何实现这一点?

编辑:

要明确的是,分数集合中的正在改变(集合本身保持不变)。我可以在score int周围放一个包装器,然后收听PropertyChanged,但是,对于一个简单的问题,这似乎是一种过于复杂的方法。如果这是最好的解决方案,请让我知道!编辑2:

UpdateScore是一个函数,(理论上)接受更新分数的索引和要添加到玩家分数上的值(它可以接受整个分数)。然后,它会使玩家的木桩沿着木桩轨道移动到新的位置。

每当玩家获得点数时,它就会被调用(这是一款克里比奇游戏,所以这种情况经常发生)。视图模型附加到一个游戏控制器,该控制器引发一个事件,通知VM玩家已获得积分。视图模型基本上只需要将此信息传递给ScoreDisplay以显示/动画等

从视图模型调用用户控制函数

在这种情况下,我们可以应用Mediator模式,如果这是成功的,它将不需要事件。

Mediator模式有几个实现,但我最喜欢的是 XAML Guy 的实现,它简单明了- 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);
        }
    }
}

通过中介进行的通信如下:

Mediator.NotifyColleagues("NameOfYourAction", ObjectValue);

在这种情况下,我们通知NameOfYourAction,您需要为他通过ObjectValue。为了 NameOfYourAction 成功接收数据,有必要在类或ViewModel中注册它,如下所示:

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

在您的例子中,值在ViewModel中传递ScoreData,它将被更改。

有关使用模式Mediator的更多示例,请参阅以下答案:

一个ViewModel用于UserControl和Window或单独的ViewModel

Anatoliy Nikolaev的回答很好。

作为另一个选项,我还建议查看事件聚合器。这是一个很好的模式,有很多用途。它们都将获得对Event Aggregator的引用,然后一个可以发布事件,另一个可以接收事件并采取行动。如果你在你的应用程序中启用这样的东西,那么多个viewmodel以完全解耦的方式进行通信就变得微不足道了。作为一个额外的好处,测试变得简单,因为您可以轻松模拟您的事件聚合器来提供所需的任何数据。

我找到了一个方法:

我创建了一个视图模型类型的依赖属性:

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));
并将我的XAML更改为:
<local:ScoreDisplay BaseViewModel="{Binding}"/>

然后在依赖属性的PropertyChanged事件处理程序中,我就可以连接我的事件了。

(e.NewValue as GameViewModel).ScoreUpdated += (d as ScoreDisplay).UpdateScore;