背景
我正在使用MVVM模式编写WPF应用程序。我在各种教程中学习到使用Messenger在ViewModel之间通信。我正在使用本帖子代码部分(感谢@Dalstroem WPF MVVM communication between View Model和Pluralsight的Gill Cleeren)提供的Messenger类实现。
由于我的应用程序需要大量的Views/VMs,每个ViewModel都是在需要View时实例化并随后释放的(view-first,VM作为View的DataContext指定)。
问题
每个ViewModel的构造函数根据需要加载资源(Commands、Services等),并注册感兴趣的消息。从先前存在的ViewModel发送的消息不会被新的ViewModel接收。
因此,我无法使用Messenger类在ViewModel之间通信。
想法
我看到一些示例使用一个ViewModelLocator来预先实例化所有的ViewModel。当创建View时,视图只需从VML中获取现有的ViewModel。这种方法意味着消息将始终被接收并在每个ViewModel中可用。我的担忧是,对于30多个需要使用大量数据的ViewModel,如果每个View都被使用(没有资源被释放),我的应用程序会变得缓慢。
我考虑过找到一种方法来存储消息,并随后将所有消息重新发送给任何注册的接收者。如果实现了这个方法,我可以在注册了每个ViewModel的消息后调用一个重新发送的方法。我有几个担忧,包括随时间累积的消息。
我不确定我做错了什么或者是否有我不知道的方法。
代码
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);
}
}
}
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
更新
我的应用程序的架构方式是使用了一个ViewModel和我的MainWindow,它作为一种基本的外壳。它提供一个主要布局和几个用于导航、登录/注销等控件。
所有后续的视图都显示在MainWindow内部的ContentControl中(占用大部分窗口空间)。ContentControl绑定到我的"MainWindowViewModel"的"CurrentView"属性。MainWindowViewModel实例化了一个我为选择和返回适当的视图而创建的自定义导航服务,以更新我的"CurrentView"属性。
这种架构可能是不正常的,但我不确定如何在不使用TabControl之类的现成工具的情况下完成导航。
想法
借鉴@axlj的想法,我可以将一个ApplicationState对象作为"MainWindowViewModel"的属性。使用我的Messenger类,在向MainWindow中注入新视图时,我可以pub一个ApplicationState消息。每个视图的ViewModel当然会sub这个消息,并在创建时立即获得状态。如果任何ViewModel对它们的ApplicationState副本进行更改,则会发布一条消息。然后MainWindowViewModel通过其订阅进行更新。