zoukankan      html  css  js  c++  java
  • 利刃 MVVMLight 10:Messenger 深入

    1、Messager交互结构和消息类型

    衔接上篇,Messeger是信使的意思,顾名思义,他的目是用于View和ViewModel 以及 ViewModel和ViewModel 之间的消息通知和接收。

    Messenger类用于应用程序的通信,接受者只能接受注册的消息类型,另外目标类型可以被指定,用Send<TMessage, TTarget>(TMessage message)实现,在这种情况下信息只能被传递如果接受者类型和目标参数类型匹配,

    message可以是任何简单或者复杂的对象,你可以用特定的消息类型或者创建你自己的类型继承自他们。

    交互结构如下所示:

     

    消息类型如下表所示:

    message消息对象类型 说明
    MessageBase 简单的消息类,携带可选的信息关于消息发布者的
    GenericMessage<T> 泛型消息
    NotificationMessage 用于发送一个string类型通知给接受者
    NotificationMessage<T>

    和上面一样是一个,且具有泛型功能

    NotificationMessage 向接受者发送一个通知,允许接受者向发送者回传消息
    NotificationMessageAction<T> NotificationMessage的泛型方式
    DialogMessage 发送者(通常是View)显示对话,并且传递调用者得回传结果(用于回调),接受者可以选择怎样显示对话框,可以使是标准的MessageBox也可也是自定义弹出窗口
    PropertyChangedMessage<T> 用于广播一个属性的改变在发送者里,和PropertyChanged事件有完全箱体内各的目的,但是是一种弱联系方式

     

    2、注册消息的模式

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

    2.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   }

    2.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);

      

    3、跨线程访问

    之前在第8篇《利刃 MVVMLight 8:DispatchHelper在多线程和调度中的使用》中,

    我们有讨论过在异步线程中使用事件来执行和获取相关的执行步骤。但是如果异步线程中的某个方法需要操作主线程(UI线程的时候)的UI是不允许的。

    Windows 中的控件被绑定到特定的UI线程(主线程)中,其他线程是不允许访问的,因为不具备线程安全性和规范性。所以后来MVVM Light才有了调度帮助类(DispatchHelper)来处理不用线程中的调度方案。

    从这边可以衍生到异步线程下,对UI线程(主线程)的信息发送和接收。所以之前的代码 DispatchHelper 可以改装如下:

     注册模块(ViewModel中):

     1  public class MessengerForDispatchViewModel:ViewModelBase
     2     {
     3         /// <summary>
     4         /// 构造函数
     5         /// </summary>
     6         public MessengerForDispatchViewModel()
     7         {
     8             InitData();
     9             DispatcherHelper.Initialize();
    10 
    11             ///Messenger:信使
    12             ///Recipient:收件人          
    13             Messenger.Default.Register<TopUserInfo>(this, "UserMessenger", FeedBack);
    14         }
    15 }

     发送模块(异步线程中代码):

     1   private void Start()
     2         {
     3             TopUserInfo ui = new TopUserInfo();
     4 
     5             //ToDo:编写创建用户的DataAccess代码
     6             for (Int32 idx = 1; idx <= 9; idx++)
     7             {
     8                 Thread.Sleep(1000);
     9                 ui = new TopUserInfo() {
    10                     isFinish = false,
    11                     process = idx*10,
    12                      userInfo =null
    13                 };
    14                 DispatcherHelper.CheckBeginInvokeOnUI(() =>
    15                 {
    16                     Messenger.Default.Send<TopUserInfo>(ui, "UserMessenger");
    17                 });
    18             }
    19             Thread.Sleep(1000);
    20             ui = new TopUserInfo()
    21             {
    22                 isFinish = true,
    23                 process = 100,
    24                 userInfo = up
    25             };
    26             DispatcherHelper.CheckBeginInvokeOnUI(() =>
    27             {
    28                 Messenger.Default.Send<TopUserInfo>(ui, "UserMessenger");
    29             });
    30         }

     结果:

     

     

    4、释放注册信息:

     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  }

      

    5、释放注册信息和内存处理

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

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

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

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

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

     

     

    6、专有信道和广播信道

     6.1 过滤Messenger发送端(通过判断发送端来确认是否是发送给自己的):

     1  public class ForSourceSenderViewModel:ViewModelBase
     2     {
     3         public ForSourceSenderViewModel(){}
     4 
     5         #region 全局命令
     6         private RelayCommand sendMsg;
     7         /// <summary>
     8         /// 发送消息
     9         /// </summary>
    10         public RelayCommand SendMsg
    11         {
    12             get
    13             {
    14                 if (sendMsg == null) sendMsg = new RelayCommand(() => ExcuteSendMsh());
    15                 return sendMsg;
    16             }
    17 
    18             set
    19             {
    20                 sendMsg = value;
    21             }
    22         }
    23 
    24         #endregion
    25 
    26         #region 附属方法
    27         private void ExcuteSendMsh()
    28         {
    29             NotificationMessage nm = new NotificationMessage(this,"发送源消息");
    30             Messenger.Default.Send<NotificationMessage>(nm);
    31         }
    32         #endregion
    33 
    34     }
    1             Messenger.Default.Register<NotificationMessage>(this, message =>
    2             {
    3                 if (message.Sender is ForSourceSenderViewModel)
    4                 {
    5                     // 判断来源来接受消息
    6                     MsgInfo = message.Notification;
    7                 }
    8             });          

      

    6.2 开设专用的Messenger通道:

     1   private Messenger myMessenger;
     2         public MessengerForSourceViewModel()
     3         {
     4             //构造函数    
     5             myMessenger = new Messenger();
     6             SimpleIoc.Default.Register(() => myMessenger, "MyMessenger"); //注入一个Key为MyMessenger的Messenger对象
     7 
     8             myMessenger.Register<NotificationMessage>(this, message =>  //注册myMessenger,开启监听
     9             {
    10                 // 判断来源来接受消息
    11                 MsgInfo = message.Notification;
    12             });
    13
     1    #region 全局命令
     2         private RelayCommand sendMsg;
     3         /// <summary>
     4         /// 发送消息
     5         /// </summary>
     6         public RelayCommand SendMsg
     7         {
     8             get
     9             {
    10                 if (sendMsg == null) sendMsg = new RelayCommand(() => ExcuteSendMsh());
    11                 return sendMsg;
    12             }
    13             set
    14             {
    15                 sendMsg = value;
    16             }
    17         }
    18                 #endregion
    19 
    20         #region 附属方法
    21         private void ExcuteSendMsh()
    22         {
    23             NotificationMessage nm = new NotificationMessage(this,String.Format("发送消息:{0}",DateTime.Now));
    24             Messenger myMessenger = SimpleIoc.Default.GetInstance<Messenger>("MyMessenger");//获取已存在的Messenger实例
    25             myMessenger.Send<NotificationMessage>(nm);//消息发送
    26         }
    27         #endregion

     

    6.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         }

     7、使用内置消息

    比如我们上面用到的 NotificationMessage<T> ,以及PropertyChanged­Message<T>。

     Notification是一种消息通知机制;而PropertyChanged­Message主要指的是当属性改变的时候,执行通知操作。

     1   public class PropertyChangedViewModel:ViewModelBase
     2     {
     3         public const string PropertyName = "UserName"; //注册为该属性,该属性变化时进行消息发送
     4         public PropertyChangedViewModel() { }
     5 
     6         #region 全局变量
     7         private String userName;
     8         /// <summary>
     9         /// 用户名称
    10         /// </summary>
    11         public string UserName
    12         {
    13             get
    14             {
    15                 return userName;
    16             }
    17 
    18             set
    19             {
    20                 String oldValue = userName;
    21                 userName = value;
    22                 RaisePropertyChanged(()=>UserName,oldValue,value,true);//这边相应配置上发送参数
    23             }
    24         }
    25         #endregion
    1             Messenger.Default.Register<PropertyChangedMessage<String>>(this, message =>
    2             {
    3                 if (message.PropertyName == PropertyChangedViewModel.PropertyName) //接受特定属性值相关信道的消息
    4                 {
    5                     PropertyChangedInfo = (message.OldValue + " --> " + message.NewValue);//输出旧值到新值的内容
    6                 }
    7             });

    结果:

     示例代码下载

     转载请注明出处,谢谢

     

  • 相关阅读:
    android webView使用
    Android开发者必知的开发资源(转载)
    Android中的消息推送(转载)
    Android消息处理系统——Looper、Handler、Thread(转载)
    Android之单元测试学习(转载)
    ndroid获得Bitmap的三种方法(转载)
    Android性能调优(转载)
    Android的开机流程(转载)
    Android命名规范(自定义)(转载)
    无法解决 equal to 操作中 Latin1_General_CI_AI 和 Chinese_PRC
  • 原文地址:https://www.cnblogs.com/wzh2010/p/6689423.html
Copyright © 2011-2022 走看看