首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >WPF MVVM与Messenger通信(VM的后消息负载)

WPF MVVM与Messenger通信(VM的后消息负载)
EN

Stack Overflow用户
提问于 2016-11-16 18:38:54
回答 2查看 4K关注 0票数 4

背景

我正在使用MVVM模式编写一个WPF应用程序。正如我在各种教程中学到的那样,我正在使用一个信使在ViewModels之间进行通信。我正在使用本文代码部分中找到的Messenger类的实现(感谢@Dalstroem 视图模型间的WPF MVVM通信和Pluralsight的Gill Cleeren )。

由于我的应用程序需要大量的视图/ VM,所以在需要并随后释放视图时,每个ViewModel都会被实例化(视图-首先,VM被指定为DataContext of View)。

问题

每个ViewModel的构造函数加载资源(命令、服务等)根据需要注册感兴趣的消息。从以前存在的ViewModels发送的消息不会被新的ViewModels接收。

因此,我无法使用我的Messenger类在ViewModels之间进行通信。

Thoughts

我见过的一些示例使用了一个ViewModelLocator,它可以预先实例化所有的ViewModels。创建视图时,只需从VML中提取现有的ViewModel。这种方法意味着消息将始终在每个ViewModel中接收和可用。我担心的是,在使用30+ ViewModels时,所有用户都会加载大量的数据,我的应用程序在使用每个视图时都会变得缓慢,因为每个视图都会被使用(从来没有释放过资源)。

我已经考虑过找到一种方法来存储邮件,然后将所有邮件重新发送给任何注册的收件人。如果实现了,这将允许我在每个ViewModel中注册消息之后调用排序的重新结束方法。我对这种方法有几点担忧,包括随着时间的推移,信息的积累。

我不知道我做错了什么,或者是否有我不知道的路。

代码语言:javascript
运行
复制
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,它是一种基本的shell。它为导航和登录/注销等提供了一些控件的主布局。

所有后续视图都显示在ContentControl中的MainWindow中(占用了大部分窗口房地产)。ContentControl绑定到我的“MainWindowViewModel”的"CurrentView“属性。MainWindowViewModel实例化了我创建的自定义导航服务,目的是选择并返回适当的视图来更新我的"CurrentView“属性。

这种架构可能是非正统的,但我不确定导航通常是如何在不使用TabControl之类的开箱即用的东西的情况下实现的。

想法

基于@axlj的思想,我可以将ApplicationState对象保留为“MainWindowViewModel”的属性。使用我的Messenger类,每当在我的ApplicationState中注入一个新视图时,我就可以发布一条MainWindow消息。当然,每个视图的ViewModels将在创建时将此消息转述并立即获得状态。如果任何ViewModels对他们的ApplicationState副本进行更改,他们就会发布一条消息。然后通过订阅更新MainWindowViewModel。

EN

回答 2

Stack Overflow用户

发布于 2016-11-16 18:49:18

我建议您不要“存储消息”--即使您想出了一个恢复消息的好模式,您最终还是会遇到难以测试的逻辑。这确实表明您的视图模型需要对应用程序状态了解太多。

对于视图模型定位器--一个设计良好的视图模型定位器可能会懒惰--加载视图模型,这将使您处于与现在相同的位置。

选项1

相反,可以考虑在可能的情况下使用UserControls和DependencyProperties。

选项2

如果您的视图实际上是视图,那么考虑一个维护必要状态并将其注入视图模型的单例上下文类。此方法的好处是您的上下文类可以实现INotifyPropertyChanged,任何更改都将自动传播到您的消费视图。

选项3

如果您在视图之间导航,您可能希望实现类似于描述的这里的导航服务。

代码语言:javascript
运行
复制
interface INavigationService(string location, object parameter) {}

在这种情况下,您的参数被认为是状态对象。新的视图模型从您要导航的视图接收模型数据。

这个博客帖子有助于解释何时使用视图模型和用户控件的最佳实践。

票数 1
EN

Stack Overflow用户

发布于 2016-11-16 22:51:58

...and注册感兴趣的消息。从以前存在的ViewModels发送的消息不会被新的ViewModels接收。因此,我无法使用我的Messenger类在ViewModels之间进行通信。

为什么您的VM需要知道历史信息?

通常,消息传递应该是pub/sub;消息被发布("pub"),任何可能对特定消息感兴趣的人都会订阅("sub")来接收这些消息。发布者不应该关心如何处理消息--这取决于订阅者。

如果您有一些模糊的业务案例需要了解以前的消息,那么您应该创建自己的消息队列机制(即将它们存储在数据库中并根据日期时间检索它们)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40639789

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档