WPF MVVM之间的通信视图模型



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

什么是最好的,可能是最简单的方法来实现这只在ViewModels内,我想避免在代码后面为导航编写代码。

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

MainViewModel中注册一个已添加的person对象:

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

如何使用中介模式(例如,参见technicalrecipes.com或John Smith)或弱事件呢?Afaik几个MVVM框架/库(如PRISM, Caliburn。Micro, MVVMCross)已经为这些提供了基础架构代码。还有一些独立的库,它们独立于任何特定的MVVM框架,比如appaccelerate EventBroker,它可以帮助你实现你想要的东西。

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

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

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

用法指南

最后我稍微调整了Dalstroem的解决方案。这帮我解决了两个问题:-

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

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

问题#2:我的xUnit测试总是失败

解决方案-将信使从单例更改。相反,将消息注入到ViewModels中。

另外,至关重要的是,将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);
                }
            }
        }
    }
}

相关内容

  • 没有找到相关文章

最新更新