我正在研究WPF MVVM应用程序,其中我有两个视图View1和View2及其各自的ViewModel。现在,我希望单击View1中的按钮将关闭View1并使用ViewModel1打开View2。 此外,我想从ViewModel1打开时向ViewModel2传递一些人类实例,这些数据将用于在View2中显示信息。
在ViewModels中实现此功能的最佳方式和最简单方法是什么,我希望避免在后面的代码中编写导航代码。
答案 0 :(得分:24)
我创建了这个Messenger
类来处理ViewModels之间的通信。
在MainViewModel
注册添加的人物对象:
Messenger.Default.Register<Person>(this, AddPersonToCollection, Context.Added);
通过CreatePersonViewModel
:
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.com或John 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 框架的一部分,因此可以单独使用它。非常容易安装和使用。
答案 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);
}
}
}
}
}