【问题标题】:WPF MVVM communication between View Model [closed]视图模型之间的 WPF MVVM 通信 [关闭]
【发布时间】:2014-07-10 23:52:55
【问题描述】:

我正在开发 WPF MVVM 应用程序,其中我有 2 个视图 View1 和 View2 以及它们各自的 ViewModel。现在,我想单击 View1 中的按钮将关闭 View1 并使用 ViewModel1 打开 View2。 另外,我想在从 ViewModel1 打开时将一些数据(例如人员类的实例)传递给 ViewModel2,这将用于在 View2 中显示信息。

仅在 ViewModels 中实现这一目标的最佳且可能最简单的方法是什么,我希望避免在代码后面编写导航代码。

【问题讨论】:

    标签: c# wpf mvvm viewmodel


    【解决方案1】:

    我创建了这个Messenger 类来处理 ViewModel 之间的通信。

    MainViewModel注册一个添加的人对象:

    Messenger.Default.Register<Person>(this, AddPersonToCollection, Context.Added);
    

    CreatePersonViewModel 通知所有已注册的 ViewModel 关于添加的人:

    Messenger.Default.Send(person, Context.Added);
    

    源代码:

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace Application.Messaging
    {
        public class Messenger
        {
            private static readonly object CreationLock = new object();
            private static readonly ConcurrentDictionary<MessengerKey, object> Dictionary = new ConcurrentDictionary<MessengerKey, object>();
    
            #region Default property
    
            private static Messenger _instance;
    
            /// <summary>
            /// Gets the single instance of the Messenger.
            /// </summary>
            public static Messenger Default
            {
                get
                {
                    if (_instance == null)
                    {
                        lock (CreationLock)
                        {
                            if (_instance == null)
                            {
                                _instance = new Messenger();
                            }
                        }
                    }
    
                    return _instance;
                }
            }
    
            #endregion
    
            /// <summary>
            /// Initializes a new instance of the Messenger class.
            /// </summary>
            private Messenger()
            {
            }
    
            /// <summary>
            /// Registers a recipient for a type of message T. The action parameter will be executed
            /// when a corresponding message is sent.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="recipient"></param>
            /// <param name="action"></param>
            public void Register<T>(object recipient, Action<T> action)
            {
                Register(recipient, action, null);
            }
    
            /// <summary>
            /// Registers a recipient for a type of message T and a matching context. The action parameter will be executed
            /// when a corresponding message is sent.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="recipient"></param>
            /// <param name="action"></param>
            /// <param name="context"></param>
            public void Register<T>(object recipient, Action<T> action, object context)
            {
                var key = new MessengerKey(recipient, context);
                Dictionary.TryAdd(key, action);
            }
    
            /// <summary>
            /// Unregisters a messenger recipient completely. After this method is executed, the recipient will
            /// no longer receive any messages.
            /// </summary>
            /// <param name="recipient"></param>
            public void Unregister(object recipient)
            {
                Unregister(recipient, null);
            }
    
            /// <summary>
            /// Unregisters a messenger recipient with a matching context completely. After this method is executed, the recipient will
            /// no longer receive any messages.
            /// </summary>
            /// <param name="recipient"></param>
            /// <param name="context"></param>
            public void Unregister(object recipient, object context)
            {
                object action;
                var key = new MessengerKey(recipient, context);
                Dictionary.TryRemove(key, out action);
            }
    
            /// <summary>
            /// Sends a message to registered recipients. The message will reach all recipients that are
            /// registered for this message type.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="message"></param>
            public void Send<T>(T message)
            {
                Send(message, null);
            }
    
            /// <summary>
            /// Sends a message to registered recipients. The message will reach all recipients that are
            /// registered for this message type and matching context.
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="message"></param>
            /// <param name="context"></param>
            public void Send<T>(T message, object context)
            {
                IEnumerable<KeyValuePair<MessengerKey, object>> result;
    
                if (context == null)
                {
                    // Get all recipients where the context is null.
                    result = from r in Dictionary where r.Key.Context == null select r;
                }
                else
                {
                    // Get all recipients where the context is matching.
                    result = from r in Dictionary where r.Key.Context != null && r.Key.Context.Equals(context) select r;
                }
    
                foreach (var action in result.Select(x => x.Value).OfType<Action<T>>())
                {
                    // Send the message to all recipients.
                    action(message);
                }
            }
    
            protected class MessengerKey
            {
                public object Recipient { get; private set; }
                public object Context { get; private set; }
    
                /// <summary>
                /// Initializes a new instance of the MessengerKey class.
                /// </summary>
                /// <param name="recipient"></param>
                /// <param name="context"></param>
                public MessengerKey(object recipient, object context)
                {
                    Recipient = recipient;
                    Context = context;
                }
    
                /// <summary>
                /// Determines whether the specified MessengerKey is equal to the current MessengerKey.
                /// </summary>
                /// <param name="other"></param>
                /// <returns></returns>
                protected bool Equals(MessengerKey other)
                {
                    return Equals(Recipient, other.Recipient) && Equals(Context, other.Context);
                }
    
                /// <summary>
                /// Determines whether the specified MessengerKey is equal to the current MessengerKey.
                /// </summary>
                /// <param name="obj"></param>
                /// <returns></returns>
                public override bool Equals(object obj)
                {
                    if (ReferenceEquals(null, obj)) return false;
                    if (ReferenceEquals(this, obj)) return true;
                    if (obj.GetType() != GetType()) return false;
    
                    return Equals((MessengerKey)obj);
                }
    
                /// <summary>
                /// Serves as a hash function for a particular type. 
                /// </summary>
                /// <returns></returns>
                public override int GetHashCode()
                {
                    unchecked
                    {
                        return ((Recipient != null ? Recipient.GetHashCode() : 0) * 397) ^ (Context != null ? Context.GetHashCode() : 0);
                    }
                }
            }
        }
    }
    

    【讨论】:

    • 我真的很喜欢你的解决方案。将通用消息类型与可选的上下文对象结合起来非常棒。由于我还不能决定一个 MVVM 框架,也许永远不会,我找到了你的解决方案,并在我的爱好项目中使用了它。但是 MessengerKey 有一个问题:它没有考虑消息的通用类型。因此,如果您向具有相同上下文的同一收件人注册两种不同的消息类型,则第二次注册将失败(但不会引发异常)。我通过使用消息类型扩展 MessengerKey 在本地为我解决了这个问题。
    • @Yeah69 你能分享你创建的这个 MessengerKey 扩展吗?
    • @Conrad:是的,当然。我做了一个github repo:github.com/Yeah69/MessengerPattern
    • 这很好用!只是一个简单的问题,当方法不带参数时,我将如何使用 Register ? private void MyMethod()
    • 如何查看课程是否已经注册?
    【解决方案2】:

    如何使用中介者模式(例如参见technical-recipes.comJohn Smith)或弱事件? Afaik 几个 MVVM 框架/库(如 PRISM、Caliburn.Micro、MVVMCross)已经附带了这些的基础设施代码。 还有一些独立于任何特定 MVVM 框架的独立库,例如 Appccelerate EventBroker 可以帮助您实现您想要的目标。

    但是,对于事件,我想知道您是否需要一些关于事件是否“正确”处理的反馈。有一些方法可以实现这一点(改变事件参数的值,处理事件同步,引发事件后,检查事件参数的值),但它们不如方法的返回值或方法抛出一个例外。

    编辑:抱歉,我刚刚意识到第二个视图/ViewModel 尚未打开。所以我的“解决方案”并不(那么简单)适用。您需要在视图模型树中“向上”传递指令,甚至可能传递到根,您可以在其中实例化并显示新的视图模型(在新窗口中显示或作为现有视图中的 ContentControl?)

    【讨论】:

    • 这个建议对我有用。中介者模式是实现视图模型之间通信的直接且实用的方式。这里给出了一个示例实现:technical-recipes.com/2016/…
    【解决方案3】:

    使用小型专用Light Message Bus。它不是任何 MVVM 框架的一部分,因此可以独立使用。非常非常容易安装和使用。

    Usage guidelines

    【讨论】:

      【解决方案4】:

      我最终稍微适应了Dalstroem's solution。这帮助我解决了两个问题:-

      问题 1:每个收件人只能在每个上下文中注册一条消息

      解决方案 - 将类型作为字典键的一部分(如上面 Dima 所建议的那样)。

      问题 2:我的 xUnit 测试始终失败

      解决方案 - 将 Messenger 从单例中更改。相反,将信使注入 ViewModel。

      此外,至关重要的是,将 Dictionary 更改为 非静态成员。否则你会在并行测试中遇到各种各样的问题。

      适应的解决方案:

      using System;
      using System.Collections.Concurrent;
      using System.Collections.Generic;
      using System.Linq;
      
      namespace Application.Messaging
      {
          public class Messenger
          {
              private readonly ConcurrentDictionary<MessengerKey, object> RecipientDictionary = new ConcurrentDictionary<MessengerKey, object>();
      
              public Messenger()
              {
              }
      
              public void Register<T>(object recipient, Action<T> action)
              {
                  Register(recipient, action, null);
              }
      
              public void Register<T>(object recipient, Action<T> action, object context)
              {
                  var key = new MessengerKey(recipient, typeof(T), context);
                  RecipientDictionary.TryAdd(key, action);
              }
      
              public void Unregister<T>(object recipient, Action<T> action)
              {
                  Unregister(recipient, action, null);
              }
      
              public void Unregister<T>(object recipient, Action<T> action, object context)
              {
                  object removeAction;
                  var key = new MessengerKey(recipient, typeof(T), context);
                  RecipientDictionary.TryRemove(key, out removeAction);
              }
      
              public void UnregisterAll()
              {
                  RecipientDictionary.Clear();
              }
      
              public void Send<T>(T message)
              {
                  Send(message, null);
              }
      
              public void Send<T>(T message, object context)
              {
                  IEnumerable<KeyValuePair<MessengerKey, object>> result;
      
                  if (context == null)
                  {
                      // Get all recipients where the context is null.
                      result = from r in RecipientDictionary where r.Key.Context == null select r;
                  }
                  else
                  {
                      // Get all recipients where the context is matching.
                      result = from r in RecipientDictionary where r.Key.Context != null && r.Key.Context.Equals(context) select r;
                  }
      
                  foreach (var action in result.Select(x => x.Value).OfType<Action<T>>())
                  {
                      // Send the message to all recipients.
                      action(message);
                  }
              }
      
              protected class MessengerKey
              {
                  public object Recipient { get; private set; }
                  public Type MessageType { get; private set; }
                  public object Context { get; private set; }
      
                  public MessengerKey(object recipient, Type messageType, object context)
                  {
                      Recipient = recipient;
                      MessageType = messageType;
                      Context = context;
                  }
      
                  protected bool Equals(MessengerKey other)
                  {
                      return Equals(Recipient, other.Recipient) 
                          && Equals(MessageType, other.MessageType)
                          && Equals(Context, other.Context) ;
                  }
      
                  public override bool Equals(object obj)
                  {
                      if (ReferenceEquals(null, obj)) return false;
                      if (ReferenceEquals(this, obj)) return true;
                      if (obj.GetType() != GetType()) return false;
      
                      return Equals((MessengerKey)obj);
                  }
      
                  public override int GetHashCode()
                  {
                      unchecked
                      {
                          return ((Recipient != null ? Recipient.GetHashCode() : 0) * 397) 
                              ^ ((MessageType != null ? MessageType.GetHashCode() : 0) * 397)
                              ^ (Context != null ? Context.GetHashCode() : 0);
                      }
                  }
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2014-10-11
        • 2013-08-31
        • 1970-01-01
        • 1970-01-01
        • 2020-01-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多