【CommunityToolkit.Mvvm】Messenger 信使

参考:https://learn.microsoft.com/zh-cn/archive/msdn-magazine/2014/june/mvvm-the-mvvm-light-messenger-in-depth

               https://www.cnblogs.com/happyyftk/p/6903775.html

环境

WPF+.net6.0+mvvm 8.0+vs2022

 完整案例下载

作用

负责 ViewModel与ViewModel、View与ViewModel之间的通信

Messenger简介

 

 

 


 

当我们使用MVVM开发模式进行开发时,ViewModel之间的通信常常是很头疼的事情,好在MVVM Light提供了Messenger类可以轻松的在ViewModel之间传递消息。

   诸如 Messenger 之类的系统有时称为事件总线或事件聚合器,事件总线这个概念对你来说可能很陌生,但提到观察者(发布-订阅)模式,你也许就很熟悉。事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。

此类组件连接接收端和发送端(有时分别称为“发布服务器”和“订阅服务器”)。

MVVM Light Messenger 旨在通过简单的前提来精简此场景:任何对象都可以是接收端;任何对象都可以是发送端;任何对象都可以是消息。

词汇也得到了简化。使用消息这类容易理解的词语,而不是使用难以定义的词语(如“事件聚合”)。订阅服务器变为接收端,发布服务器则变为发送端。消息取代了事件。通过语言与实施上的简化,您可以更轻松地开始使用 Messenger 并了解它的工作方式。..

 CommunityToolkit.Mvvm介绍

 

 

 

Messenger它是如何工作的


ObservableRecipient设计出来是用来作为viewmodel的基础(或者说基对象)的,它也使用了IMessenger的特性,因为它为IMessenger提供了内置支持。特别地:
    1、它有一个无参的构造函数和一个接受IMessenger实例的构造函数,被用作依赖注入。它还暴露了一个Messenger属性,可用来在viewmodel中收发消息。如果使用了无参构造函数,WeakReferenceMessenger.Default实例将会被分配给Messenger属性。
   2、 它暴露了IsActive属性以激活(Activate)/禁用(Deactivate)viewmodel。在这种情况下,激活意味着viewmodel被标记为正在使用,例如,它将开始监听已注册的消息,执行其他的设置操作等。有两个相关的方法OnActivated 和 OnDeactivated,在属性改变值时,它们会被调用。默认情况下,OnDeactivated会自动从所有已注册的消息中注销当前实例。为了获得最佳效果和避免内存泄漏,建议使用OnActivate来注册消息,使用OnDeactivate来做清理操作。这种模式允许viewmodel启停多次,同时可以安全地收集,而不会在每次停用的时候产生内存泄漏的风险。默认情况下,OnActivate会自动注册所有通过IRecipient<TMessage>接口定义的消息处理器。
    3、它暴露了Broadcast<T>(T, T, string)方法,该方法通过IMessenger实例(可以从Messenger属性获得)发送PropertyChangedMessage<T>消息。这可以用来轻松传播viewmodel中的变化,而不需要手动地检索Messenger实例来使用。该方法通过各种SetProperty方法的重载来使用,这些方法还有一个附加的bool broadcast属性,用于指示是否也发送消息。

1、开启 接收信息 的功能,_mainViewModel.IsActive = true;

using CTMvvmDemo.MVVM.ViewsModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace CTMvvmDemo.MVVM.Views
{
    /// <summary>
    /// DisplayText.xaml 的交互逻辑
    /// </summary>
    public partial class DisplayTextView : Window
    {
        public DisplayTextView()
        {
            InitializeComponent();
            var _mainViewModel = new DisplayTextViewModel();
            //必须设置为true,否则消息无法无法传递到DisplayTextViewModel
            _mainViewModel.IsActive = true;
            this.DataContext = _mainViewModel;

        }
    }
}

2、 消息

 

2.1使用【值变更消息】

定义值变更消息

本案例使用ValueChangedMessage<Student>
using CommunityToolkit.Mvvm.Messaging.Messages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CTMvvmDemo.MVVM.Models
{
 

    public class StudentMessage : ValueChangedMessage<Student>
    {
        /*  public class ValueChangedMessage<T>
         {
          //
          // 摘要:
          //     Gets the value that has changed.
          public T Value
          {
              get;
          }

          //
          // 摘要:
          //     Initializes a new instance of the CommunityToolkit.Mvvm.Messaging.Messages.ValueChangedMessage`1
          //     class.
          //
          // 参数:
          //   value:
          //     The value that has changed.
          public ValueChangedMessage(T value)
          {
              Value = value;
          }
      }*/
public StudentMessage(Student user) : base(user) { } } }

 在A_ViewModel中注册 属性变更消息

  protected override void OnActivated()
           {
            //注册,新建一个通道DisplayTextViewModel,发送值变更消息  
            WeakReferenceMessenger.Default.Register<StudentMessage,string>(this, "DisplayTextViewModel",(display, mess) => ((DisplayTextViewModel)display).SearchText = new StudentViewsModel(mess.Value)  );

  }

 在B_ViewModel中发送 属性变更消息

//新建一个通道“DisplayTextViewModel”,将消息通过该通道发送到displayTextViewModel 实例,

 WeakReferenceMessenger.Default.Send(new StudentMessage(stu), "DisplayTextViewModel");

 

 

2.2使用【请求消息】

messenger实例另一个有用的特性是,它可以被用于从一个模块向另一个模块请求值。为了做到这点,该包包含了一个RequestMessage<T>基类,它能像这样使用:

// Create a message
public class LoggedInUserRequestMessage : RequestMessage<User>
{
}

// Register the receiver in A_module
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
{
    // Assume that "CurrentUser" is a private member in our viewmodel.
    // As before, we're accessing it through the recipient passed as
    // input to the handler, to avoid capturing "this" in the delegate.
    m.Reply(r.CurrentUser);
});

// Request the value from B_module
User user = WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();

RequestMessage<T>类包含了一个隐式转换器,能使会话从LoggedInUserRequestMessage到其包含的User对象成为可能。这还将检查是否收到了消息的响应,若没有收到,则抛出异常。也可以在没有强制响应保证的情况下发送请求消息:只需将返回的消息存储在本地变量中,然后手动检查响应值是否可用。如果Send方法返回时没有收到响应,那么这样做不会触发自动异常。

相同的命名空间还包括用于其他场景的基本请求消息:
AsyncRequestMessage<T>, CollectionRequestMessage<T> 和 AsyncCollectionRequestMessage<T>。下面是一个怎样使用异步请求消息的例子:

// Create a message
public class LoggedInUserRequestMessage : AsyncRequestMessage<User>
{
}

// Register the receiver in a module
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
{
    m.Reply(r.GetCurrentUserAsync()); // We're replying with a Task<User>
});

// Request the value from another module (we can directly await on the request)
User user = await WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();

2.3使用【属性变更消息】

在A_ViewModel种发送 属性变更消息,new PropertyChangedMessage<T>

      private string _propertyChangedMessage= " ";
        public string PropertyChangedMessage
        {
            get=> _propertyChangedMessage;
            set
            {
                //当属性更新时候,发送信息通知更新。
                //当采用汉字输入法时候,会倒置属性更新两次,因此要添加_propertyChangedMessage != value判断
                if (_propertyChangedMessage != value)
                WeakReferenceMessenger.Default.Send<PropertyChangedMessage<string>, string>(new PropertyChangedMessage<string>(this, "SearchText", _propertyChangedMessage, value), "DisplayTextViewModel");
               SetProperty(ref _propertyChangedMessage, value);
            }
        }

 在B_ViewModel种注册 属性变更消息

 /// <summary>
        /// 必须重写OnActivated,OnActivated负责注册
        /// </summary>
           protected override void OnActivated()
           {
            //注册,新建一个通道DisplayTextViewModel,发送值变更消息  
            WeakReferenceMessenger.Default.Register<StudentMessage,string>(this, "DisplayTextViewModel",(display, mess) => ((DisplayTextViewModel)display).SearchText = new StudentViewsModel(mess.Value)  );

            //注册,新建一个通道DisplayTextViewModel,发送属性变更消息 
            WeakReferenceMessenger.Default.Register<PropertyChangedMessage<String>,string>(this, "DisplayTextViewModel", (display, message) =>
            {(
                (DisplayTextViewModel)display).Displayname = (message.OldValue + " --> " + message.NewValue);//输出旧值到新值的内容
                
            });

 

 

3、发生消息

 在项目的任何地方都可以发送消息,注册消息的类 都能接收到消息。例如:WeakReferenceMessenger.Default.Send(new StudentMessage(stu));

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CTMvvmDemo.Respositoies;
using Microsoft.Extensions.DependencyInjection;
using CommunityToolkit.Mvvm.DependencyInjection;
using CTMvvmDemo.MVVM.Views;
using CTMvvmDemo.MVVM.Models;
using CommunityToolkit.Mvvm.Messaging;
using System.Windows;

namespace CTMvvmDemo.MVVM.ViewsModels
{
    public sealed partial class MainWindowViewModel:ViewModelBase, IRecipient<Student>
    {

 ///其他代码private void Seacrh(object  item)
        {

        //其他代码else
            {
              var stu=  IstudentRespository.Find(searchText);
                if (stu is null) return;
               //应用默认的通道发送
               //  WeakReferenceMessenger.Default.Send(new StudentMessage(stu));

                //新建一个通道“DisplayTextViewModel”,将消息通过该通道发送到displayTextViewModel 实例
               //  WeakReferenceMessenger.Default.Send(new StudentMessage(stu), "DisplayTextViewModel");


                 WeakReferenceMessenger.Default.Send(new StudentMessage(stu));
                 WeakReferenceMessenger.Default.Send(new StudentMessage(stu),“xxxx_viemodel”);
                StudentViewsModels.Add(new StudentViewsModel(stu));

            }

        }

    
    }
}

 

 4、注册消息

 上篇给出了注册的方法,但是注册可以有很多种方式,最常见的就是命名方法调用和Lambda表达式调用的方式:

4.1、基本的命名方法注册

 1   // 使用命名方法进行注册
 2   Messenger.Default.Register<String>(this, HandleMessage);
 3 
 4   //卸载当前(this)对象注册的所有MVVMLight消息
 5   this.Unloaded += (sender, e) => Messenger.Default.Unregister(this);
 6 
 7 
 8   private void HandleMessage(String msg)
 9   {
10     //Todo
11   }

4.2、使用 Lambda 注册

1   Messenger.Default.Register<String>(this, message => {
2                 // Todo 
3                   
4   }); 
5   //卸载当前(this)对象注册的所有MVVMLight消息
6   this.Unloaded += (sender, e) => Messenger.Default.Unregister(this);

4.3使用令牌(Token)

区分和使用信道:这是最常用的方式。使用专属Token,可以区分不同的信道,并提高复用性。

Messenger中包含一个token参数,发送方和注册方使用同一个token,便可保证数据在该专有信道中流通,所以令牌是筛选和隔离消息的最好办法。

1  //以ViewAlert位Tokon标志,进行消息发送
2  Messenger.Default.Send<String>("ViewModel通知View弹出消息框", "ViewAlert");

 

复制代码
 1         public MessagerForView()
 2         {
 3             InitializeComponent();
 4 
 5             //消息标志token:ViewAlert,用于标识只阅读某个或者某些Sender发送的消息,并执行相应的处理,所以Sender那边的token要保持一致
 6             //执行方法Action:ShowReceiveInfo,用来执行接收到消息后的后续工作,注意这边是支持泛型能力的,所以传递参数很方便。
 7             Messenger.Default.Register<String>(this, "ViewAlert", ShowReceiveInfo);
 8             this.DataContext = new MessengerRegisterForVViewModel();
 9             //卸载当前(this)对象注册的所有MVVMLight消息
10             this.Unloaded += (sender, e) => Messenger.Default.Unregister(this);
11         }
12 
13         /// <summary>
14         /// 接收到消息后的后续工作:根据返回来的信息弹出消息框
15         /// </summary>
16         /// <param name="msg"></param>
17         private void ShowReceiveInfo(String msg)
18         {
19             MessageBox.Show(msg);
20         }
复制代码

 案例

在MVVM模式中,XXXXXXXViewModel类负责接收消息。CommunityToolkit.Mvvm框架中ObservableRecipient的子类具有接收消息的功能。

 

XXXXXXXViewModel类继承 ObservableRecipient,并且重写OnActivated方法,OnActivated负责注册。

ObservableRecipient作为ViewModel的基类负责接收信息。

 

第一写法(建议)

XXXXXXXViewModel类继承 ObservableRecipient,并且重写OnActivated方法,OnActivated负责注册。

为了获得最佳效果和避免内存泄漏,建议使用OnActivate来注册消息,使用OnDeactivate来做清理操作。这种模式允许viewmodel启停多次,同时可以安全地收集,而不会在每次停用的时候产生内存泄漏的风险。

///发送端

 

internal partial class  XXXXViewModel
{
//其他代码 采用管道发送,AddStocksViewModel 是注册好的管道名
  WeakReferenceMessenger.Default.Send<PacketpocoMessage,string>(new PacketpocoMessage(currentPacketpoco), "AddStocksViewModel");
               
//其他代码
}

接收端

注册接收的方法

///接收送端
internal partial class  VVVVViewModel: ObservableRecipient
{
  public VVVVViewModel( )
        {
            this.IsActive = true;///开关按钮,true时开始接受信息,false 关闭
        }


//其他代码
        /// <summary>
        ///   注册,新建一个通道AddStocksViewModel,发送值变更消息  
        /// </summary>
        protected override void OnActivated()
        {
            WeakReferenceMessenger.Default.Register<PacketpocoMessage, string>(this, "AddStocksViewModel", (display, mess) => ((AddStocksViewModel)display).curentPacketpoco = mess.Value);
       }
/// <summary> /// 注销信息   //重载OnDeactivated事件,在窗体被取消激活时触发。 /// </summary> protected override void OnDeactivated() { base.OnDeactivated(); WeakReferenceMessenger.Default.Unregister<PacketpocoMessage>(this); } //其他代码 }

 

 

第二种写法

继承IRecipient<StudentMessage>,并且实现    public void Receive(StudentMessage message)方法。

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CTMvvmDemo.MVVM.Models;
using CTMvvmDemo.MVVM.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CTMvvmDemo.MVVM.ViewsModels
{
 
    public partial class DisplayTextViewModel:ObservableRecipient,IRecipient<StudentMessage>
    {
        [ObservableProperty]
        [NotifyPropertyChangedFor(nameof(Displayname))]

        private StudentViewsModel searchText;

        private string displayname;
     
        public string Displayname
        {
            get=> SearchText ==null? "":SearchText .Name;
            set => SetProperty(ref displayname, value);
        }

        public void Receive(StudentMessage message)
        {
            SearchText = new StudentViewsModel(message.Value);
        }

        /// <summary>
        /// 必须重写OnActivated,OnActivated负责注册
        /// </summary>
        /*   protected override void OnActivated()
           {
               WeakReferenceMessenger.Default.Register<DisplayTextViewModel, StudentMessage>(this, (display, mess) => display.SearchText = new StudentViewsModel(mess.Value));

           }*/


    }
}

 效果

 

 释放注册信息

当不再需要某个接收者时,你应该注销它,使其停止接收消息。你可以通过消息类型、注册令牌或者接收者来取消注册

 4.1、基于View界面内的UnRegister的释放(为当前视图页面的Unload事件 附加 释放注册信息的功能):

  1 //卸载当前(this)对象注册的所有MVVMLight消息 2 this.Unloaded += (sender, e) => Messenger.Default.Unregister(this); 

 4.2、基于ViewModel类中的UnRegister释放(用户在关闭使用页面的时候同事调用该方法,释放注册,这个需要开发人员在关闭视图模型的时候发起): 

复制代码
1  /// <summary>
2  /// 手动调用释放注册信息(该视图模型内的所有注册信息全部释放)
3  /// </summary>
4  public void ReleaseRegister()
5  {
6    Messenger.Default.Unregister(this);             
7  }
复制代码

  释放注册信息和内存处理

为了避免不必要的内存泄漏, .Net框架提出了比较实用的 WeakReference(弱引用)对象。该功能允许将对象的引用进行弱存储。如果对该对象的所有引用都被释放了,则垃圾回收机便可回收该对象。

 

类似将所有的注册信息保存在一个弱引用的存储区域,一旦注册信息所寄宿的宿主(View或者ViewModel)被释放,引用被清空,该注册信息也会在一定时间内被释放。

 

下面一个表格来源于 Laurent Bugnion 对 MVVM 的说明文档:

 

未取消注册时的内存泄漏风险:

 

可见性 WPF    Silverlight Windows Phone 8 Windows 运行时
静态 无风险 无风险 无风险 无风险
公共 无风险 无风险 无风险 无风险
内部 无风险 风险 风险 无风险
专用 无风险 风险 风险 无风险
匿名Lambda 无风险 风险 风险 无风险

 

 

 

 

 

 

 

posted @ 2022-11-02 21:38  小林野夫  阅读(6748)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/