【问题标题】:Invoke user control function from view model从视图模型调用用户控制功能
【发布时间】:2014-03-29 19:32:47
【问题描述】:

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

在我的场景中,我有一个带有 ScoreDisplay UserControl 的“主”视图:

<local:ScoreDisplay/>

此控件将显示我的游戏中 n 名玩家的得分。主视图模型连接到一个游戏控制器,该控制器为其提供分数更新。我需要将这些更新后的分数传递给 UserControl(并运行一些动画、重要的逻辑等)

我看到了几个选项:

  1. 创建一个“分数”依赖属性并将其绑定到视图模型的分数集合。我看到这种方法的唯一问题是,无论何时修改它,我都需要去看看有什么变化,这样我才能运行适当的动画。这样做当然是可能的,但似乎并不“正确”。

  2. 让 ViewModel 在 UserControl 上调用“UpdateScore”函数。当然,唯一的问题是 ViewModel 不应该知道任何关于 View 的信息,因此不应该有执行此操作所需的参考。

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

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

编辑:

需要明确的是,分数集合中的 正在发生变化(集合本身保持不变)。我可以在分数 int 周围放置一个包装器并收听 PropertyChanged,但同样,这似乎是一个简单问题的过于复杂的方法。如果这是最好的解决方案,请告诉我!

编辑 2:

UpdateScore 是一个函数,(理论上)接受更新分数的索引和添加到该玩家分数的值(它可以接受整个分数)。然后它会导致玩家的钉子沿着纸条轨道移动到新位置。

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

【问题讨论】:

  • 如果分数是一个集合,您可以使用ObservableCollection,因为它实现了INotifyCollectionChanges,它可以准确地告诉您发生了什么更改。
  • 那行不通,因为它只是改变的值(而不是插入/删除)。不过感谢您的评论,我将编辑我的问题以包含此信息。
  • 如果里面的元素实现了INotifyPropertyChanged,集合也会通知这些变化。
  • 里面的元素只是一个int(见我的编辑)。显式侦听包装器的 PropertyChanged 事件可能有效,但我仍然想知道是否有更好的方法。
  • @AnatoliyNikolaev 我试图在编辑 2 中包含更多信息。如果我能提供更多信息,请告诉我!

标签: c# wpf mvvm


【解决方案1】:

在这种情况下,我们可以应用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的例子,请看这个答案:

One ViewModel for UserControl and Window or separate ViewModels

【讨论】:

  • 这看起来是个不错的方法! +1 是一个非常容易理解的例子,当我开始工作时我会接受。再次感谢!
【解决方案2】:

Anatoliy Nikolaev 上面的回答很好。

作为另一种选择,我还建议查看事件聚合器。这是一个很棒的模式,有很多用途。他们都将获得对事件聚合器的引用,然后一个人可以发布一个事件,另一个人会收到它并可以采取行动。如果您在应用程序中启用类似的功能,多个 ViewModel 以完全解耦的方式进行通信变得微不足道。此外,测试变得简单,因为您可以轻松模拟您的事件聚合器以提供所需的任何数据。

【讨论】:

  • 您有此模式的链接或示例吗?
【解决方案3】:

我找到了一个方法来做到这一点:

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

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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多