View Model之间的WPF MVVM通信

时间:2014-05-22 05:26:18

标签: c# wpf mvvm viewmodel

我正在研究WPF MVVM应用程序,其中我有两个视图View1和View2及其各自的ViewModel。现在,我希望单击View1中的按钮将关闭View1并使用ViewModel1打开View2。 此外,我想从ViewModel1打开时向ViewModel2传递一些人类实例,这些数据将用于在View2中显示信息。

在ViewModels中实现此功能的最佳方式和最简单方法是什么,我希望避免在后面的代码中编写导航代码。

4 个答案:

答案 0 :(得分:24)

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

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

答案 1 :(得分:4)

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

但是,对于事件,我想知道您是否需要一些反馈,以确定事件是否正确&#34;处理与否。有实现这一目标的方法(改变事件args的值,处理事件同步,提升事件,检查事件args的值),但它们不像方法那样简洁返回值或抛出异常的方法。

编辑:抱歉,我刚刚意识到第二个视图/ viewmodel尚未打开。所以我的解决方案&#34;不是(简单地说)适用的。你需要传递指令&#34; up&#34;在视图模型树中,甚至可以在根中,您可以在其中实例化并显示新的视图模型(在新窗口中显示或在现有视图中显示为ContentControl?)

答案 2 :(得分:1)

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

Usage guidelines

答案 3 :(得分:0)

我最终稍微调整了 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);
                }
            }
        }
    }
}