WPF MVVM之间的通信视图模型
本文关键字:视图 模型 通信 MVVM 之间 WPF | 更新日期: 2023-09-27 18:04:37
我正在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);
}
}
}
}
}