【发布时间】:2016-11-16 18:38:54
【问题描述】:
背景
我正在使用 MVVM 模式编写 WPF 应用程序。正如我在各种教程中所学到的,我正在使用 Messenger 在 ViewModel 之间进行通信。我正在使用本文代码部分中的 Messenger 类的实现(感谢 @Dalstroem WPF MVVM communication between View Model 和 Pluralsight 的 Gill Cleeren)。
由于我的应用需要大量的 Views/VM,每个 ViewModel 在需要 View 时实例化并随后处理(view-first,VM 指定为 View 的 DataContext)。
问题
每个 ViewModel 的构造函数都会根据需要加载资源(命令、服务等),并注册感兴趣的消息。从先前存在的 ViewModel 发送的消息不会被新的 ViewModel 接收。
因此,我无法使用我的 Messenger 类在 ViewModel 之间进行通信。
想法
我见过的一些示例使用了一个预先实例化所有 ViewModel 的 ViewModelLocator。创建视图时,只需从 VML 中提取现有的 ViewModel。这种方法意味着消息将始终在每个 ViewModel 中被接收和可用。我担心的是,使用 30 多个 ViewModel 时都会加载大量数据,我的应用会随着每个 View 的使用而延长使用时间(没有资源被释放)。
我考虑找到一种方法来存储消息,然后将所有消息重新发送给任何注册的收件人。如果实现,这将允许我在每个 ViewModel 中注册消息后调用各种 Resend 方法。我对这种方法有一些顾虑,包括随着时间的推移消息的积累。
我不确定我做错了什么,或者是否有我不知道的方法。
代码
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);
}
}
}
}
++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++
更新
我的应用程序的架构方式是与我的 MainWindow 一起使用的 ViewModel,它用作一种基本的 shell。它提供了一个主要布局,其中包含一些导航和登录/注销等控件。
所有后续视图都显示在 MainWindow 内的 ContentControl 内(占据了大部分窗口空间)。 ContentControl 绑定到我的“MainWindowViewModel”的“CurrentView”属性。 MainWindowViewModel 实例化了我创建的自定义导航服务,目的是选择并返回适当的视图以更新我的“CurrentView”属性。
这种架构可能是非正统的,但我不确定在不使用 TabControl 等开箱即用的东西的情况下通常如何完成导航。
想法
基于 @axlj 的想法,我可以将 ApplicationState 对象作为“MainWindowViewModel”的属性。使用我的 Messenger 类,每当在我的 MainWindow 中注入一个新视图时,我都可以发布一条 ApplicationState 消息。当然,每个 View 的 ViewModel 都会子此消息并在创建后立即获得状态。如果任何 ViewModel 对其 ApplicationState 副本进行更改,它们将发布一条消息。 MainWindowViewModel 然后将通过其订阅进行更新。
【问题讨论】:
标签: c# wpf mvvm communication messenger