1、Messager交互結構和消息類型 銜接上篇,Messeger是信使的意思,顧名思義,他的目是用於View和ViewModel 以及 ViewModel和ViewModel 之間的消息通知和接收。 Messenger類用於應用程式的通信,接受者只能接受註冊的消息類型,另外目標類型可以被指定,用S ...
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> ,以及PropertyChangedMessage<T>。
Notification是一種消息通知機制;而PropertyChangedMessage主要指的是當屬性改變的時候,執行通知操作。
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 });
結果:
轉載請註明出處,謝謝