在物件導向開發中,觀察者模式經常應用於模組化與封裝的設計中。它能讓物件以彈性的方式傳遞訊息,是實現鬆耦合的一種常見做法。
而在 C# 中,常用 event
、Action
/ Func
、RX
等方式實作觀察者模式,看似達成了解耦。但實際上,訂閱者仍需知道事件來源,這造成一種隱性耦合,讓物件之間仍有依賴。
CommunityToolkit.Mvvm
套件中,除了提供 MVVM 架構下常用的 ObservableObject
與 ObservableValidator
外,還支援基於中介者模式的訊息傳遞機制 Messenger
。
該機制讓訊息的發送者與觀察者無需彼此參照,只需註冊並透過 Messenger
發送訊息,就能由框架轉發給所有訂閱者。為了整合這套機制,套件也提供了 ObservableRecipient
類別,讓 ViewModel 更容易註冊與接收訊息。
這邊要注意的是,使用 Messenger
並不代表完全沒有耦合,而是將依賴轉向了中介物件 ( Messenger
本身),達成相對鬆耦合的設計。
本次將使用 WPF 並搭配 CommunityToolkit.Mvvm
套件的ObservableRecipient
物件與 Messenger
機制,實作訊息傳遞的示範。
本篇文章為 CommunityToolkit.Mvvm 套件系列的第三部分,若想了解關於該套件的基礎屬性通知或資料驗證的使用,可從下列網址前往。
使用ToolKit.MVVM實現ViewModel – Part 1 (基礎 MVVM 架構)
使用ToolKit.MVVM實現ViewModel – Part 2 (資料驗證)
本次的開發環境,如下
- IDE : Visual Studio 2022
- .NET版本 : . NET 6.0
- Nuget套件: CommunityToolkit.Mvvm 8.2.2
常見的發布/訂閱機制
在正式使用 Messenger 之前,首先回顧一下常用的訊息傳遞機制。
如最開頭所提,在 C# 開發中,無論是自行實作觀察者模式,或是使用現成機制,如 event
、Action
/ Func
或是 RX
等,這些方式都能在物件之間建立彈性的通知機制。
實作時,通常是讓觀察者主動訂閱發送者的訊息,建立起一種動態但仍有方向性的聯繫關係。
以下將透過一個簡單範例,說明這類機制的基本運作方式。
雖然上述機制可廣泛應用於各種情境中,本範例則是模擬 ViewModel 之間的訊息溝通。
首先來看扮演發送者角色的 ViewModel ,
此 ViewModel 中包含:
- Msg 屬性,供 UI Binding 使用的 。
- OnMessageSend,用來通知訂閱者的委派。
- SendStrClick(),透過 Command 觸發通知的函式 。
Window0_SubSendViewModel
1 | public partial class Window0_SubSendViewModel : ObservableObject |
接下來是扮演觀察者角色的 ViewModel ,
- 提供公開方法 Receive(string message),用來接收訊息。
- Receive 方法直接將訊息輸出到 Console。
Window0_SubReceiveViewModel
1 | public partial class Window0_SubReceiveViewModel : ObservableObject |
此範例中,為求精簡只對應一個 View ,
- 為簡化示範,集中管理發送者與觀察者兩個 ViewModel 。
- 在建構函式中,將發送者的 OnMessageSend 委派指向觀察者的 Receive 方法,建立訂閱關係。
Window0_MainViewModel
1 | public partial class Window0_MainViewModel : ObservableObject |
最後是 View 的部分,
- 直接在 Window.DataContext 中建立 MainViewModel 實例。
- 使用 StackPanel 並將資料來源綁定到發送者的 SendViewModel。
- 畫面包含一個文字輸入框與一個按鈕,分別用於輸入訊息和觸發發送命令。
- 觀察者 ViewModel 僅在 Console 輸出訊息,未綁定任何 UI 元件。
Window0
1 | <Window.DataContext> |
執行的畫面與結果,如下,
在 UI 中輸入要傳送的字串,
觀察者將接收到的訊息,顯示在 Console 上,
實務上,ViewModel(或一般物件)的建立方式可能相當複雜,可能透過不同的流程、由 DI 容器產生,或分散在不同模組中。
然而,不論它們是如何建立、位於何處,只要有觀察需求,就必須在某處讓觀察者直接訂閱發送者的訊息。
開始使用 ObservableRecipient
在 CommunityToolkit.Mvvm
套件中,撰寫 ViewModel 通常會繼承 ObservableObject
,以支援屬性變更通知等基本功能。
若在此基礎上,還需要實作 ViewModel 之間的訊息傳遞,套件也提供了 ObservableRecipient
類別。該類別不僅繼承自 ObservableObject
,還封裝了與 Messenger
搭配使用的相關機制,能更方便地註冊與接收訊息。
以下將透過一個簡單範例,展示 ObservableRecipient
的基本使用方式。
首先是發送者的 ViewModel ,
- 繼承
ObservableRecipient
讓 ViewModel 具備Messenger
的功能。 - Msg 屬性,提供 UI Binding 並作為要傳送的資料內容。
- SendStrClick() 方法,供 UI Binding 的 Command,觸發後透過 Messenger.Send() 廣播 Msg 給所有訂閱者。
Window1_SubSendViewModel
1 | public partial class Window1_SubSendViewModel : ObservableRecipient |
在這個範例中,發送者是繼承自 ObservableRecipient,這裡做個部份說明:
protected IMessenger Messenger { get; }
這個屬性就是發送者在呼叫
Messenger.Send()
時所使用的 Messenger 實例。ObservableRecipient(IMessenger messenger)
這個建構式會要求傳入一個 IMessenger 介面的物件,並將它指定給上面的
Messenger
屬性。ObservableRecipient()
這個無參數建構式會自動使用
WeakReferenceMessenger.Default
作為 Messenger 實例。
接著是觀察者的 ViewModel,
- 繼承
ObservableRecipient
讓 ViewModel 具備Messenger
的功能。 - 實作
IRecipient<string>
,用於接收發送端傳來的 string 訊息,只要設為 IsActive = true , Messenger 就會自動把它註冊為該型別的觀察者。 - Receive(string message) 方法,介面實作,接收到訊息後,將其輸出至 Console 。
Window1_SubReceiveViewModel
1 | public partial class Window1_SubReceiveViewModel |
上面的程式簡潔得幾乎不可思議,初看之下可能會讓人摸不著頭緒。以下針對「觀察者」部分的關鍵機制做一些補充說明:
IsActive
屬性當將
IsActive
設為 true 時,會觸發OnActivated()
方法;反之,設為 false 則會呼叫OnDeactivated()
。簡單來說,這個屬性決定了物件是否會去接收來自 Messenger 的訊息。
OnActivated()
與OnDeactivated()
方法這兩個方法分別負責呼叫 Messenger 的
RegisterAll
與UnregisterAll
方法。顧名思義,前者會將物件註冊到 Messenger 以便接收訊息,後者則會取消註冊。
IRecipient<T>
的作用從
OnActivated()
方法的 API 註解可以看到,只要類別實作了IRecipient<T>
介面,就能在IsActive
設為 true 時自動完成註冊,無需額外手動呼叫 Messenger 的註冊方法。
與上個範例相同,
使用 MainViewModel 集中管理發送者與觀察者兩個 ViewModel 實例。
Window1_MainViewModel
1 | public partial class Window1_MainViewModel : ObservableObject |
最後是 View 的部分,
這部分與上一個範例相同,因此不再重複說明。
Window1
1 | <Window.DataContext> |
執行的畫面與結果,如下,
基本上與前個範例相同,
到這邊,已經完成 ObservableRecipient
的基礎使用範例,示範了具備發送者與觀察者的 ViewModel 實作。
與一般的訂閱機制相比,這種做法具備以下優勢:
- 利用
Messenger
作為中介者,無需手動建立訂閱關係。 - 發送者與觀察者的 ViewModel 不需存在於相同範疇,即使分屬不同模組或畫面,也能順利完成訊息傳遞。
訊息類型與註冊控制
在前一個範例中,已經了解如何使用 ObservableRecipient
傳送與接收 string 型別的訊息,並體驗到自動註冊 Messenger
的功能。
接下來將進入進階應用,包含:
- 傳送不同型別的訊息
- 內建的
ValueChangedMessage<T>
傳遞任意型別資料。 - 自定義類別。
- 內建的
- 手動註冊與取消註冊
Messenger
- 讓物件可以訂閱多種不同型別的訊息。
- 手動管理註冊與取消註冊。
這些功能不僅能讓 Messenger
應對更複雜的情況,也能對底層運作有更深一層的認識。
首先,定義了一個自訂類別 CustomizeClass 作為傳送的物件。
這就像在一般的發布/訂閱模式中,會傳遞某個物件給觀察者一樣。
CustomizeClass
1 | public class CustomizeClass |
發送者 ViewModel 的部分,示範三個不同的 Command,傳送三種類型的訊息。
分別說明如下:
- SendStrClick():傳送 string 的訊息。
- SendCount1Click():傳送 ValueChangedMessage
訊息。 ValueChangedMessage<T>
是 Toolkit 提供的內建訊息類別,用來包裝單一資料。
因為 Messenger.Send() 只能傳送類別,若要傳遞像 int 或 double 這類結構型別,就需要用ValueChangedMessage<T>
來包起來。
- SendCount2Click:傳送自定義類別 CustomizeClass 訊息。
Window2_SubSendViewModel
1 | public partial class Window2_SubSendViewModel : ObservableRecipient |
此範例有兩個觀察者 ViewModel,
首先第一個觀察者 ViewModel 的部分:
- 覆寫 OnActivated()
- 手動註冊(訂閱) string 與 ValueChangedMessage
兩種型別的訊息。 - 訊息接收後,便會呼叫對應的 Receive 方法,並將內容輸出到 Console 。
- 手動註冊(訂閱) string 與 ValueChangedMessage
- 覆寫 OnDeactivated(),用來取消對 Messenger 的手動註冊。
實務上,Messenger 的註冊與取消註冊可以自行控制,
比方說,可以直接在建構子裡註冊,或是在需要的時候再註冊,
不過覆寫 OnActivated
/ OnDeactivated
搭配 IsActive
屬性的好處是,能用一個布林值一次性管理整個註冊與取消註冊的流程。
Window2_SubReceiveViewModel
1 | public partial class Window2_SubReceiveViewModel : ObservableRecipient |
第二個觀察者 ViewModel 的部分:
- 覆寫 OnActivated()
- 手動註冊(訂閱) string 與 CustomizeClass 兩種型別的訊息。
- 訊息接收後,便會呼叫對應的 Receive 方法,並將內容輸出到 Console,為了與第一個觀察者 ViewModel 區分,這裡使用 ConsoleWriteStyled 方法,讓輸出文字呈現不同顏色。
- 覆寫 OnDeactivated(),用來取消對 Messenger 的手動註冊。
Window2_SubReceiveAnotherViewModel
1 | public partial class Window2_SubReceiveAnotherViewModel : ObservableRecipient |
MainViewModel 部分,集中管理發送者與兩個觀察者的 ViewModel 實例。
Window2_MainViewModel
1 | public partial class Window2_MainViewModel : ObservableObject |
View 的部分,為配合發送者 ViewModel 的接口,撰寫相應的 UI 元件。
Window2
1 | <Window.DataContext> |
執行的畫面與結果,如下,
依序按下 View 中從上到下的按鈕,
從 Console 輸出的內容可以觀察到,兩個觀察者都成功接收到對應型別的訊息,並顯示出來。
到此為止,已經學會如何使用 ObservableRecipient
傳送不同型別的訊息,以及手動註冊與取消註冊。同時也看到,Messenger 會自動將訊息傳遞給對應型別的觀察者。
使用 Token 機制區隔同類別訊息
Messenger
會依照訊息的型別自動配對發送與接收方。但若有多個來源傳送相同型別的訊息,且希望由不同的接收者各自處理,這時就可能發生混淆或誤收的問題。
為此,Messenger 提供了 Token 機制,可將相同型別的訊息依據不同的情況加以區隔,讓訊息傳遞更精確。
本節將針對此情況 Token 的部分進行演示。
發送者 ViewModel 的部分說明如下:
- SendStrClick():傳送 string 訊息,未指定 Token,實際上會使用預設 Token ( Unit.Default )。
- SendToken01Click():傳送 string 訊息,並指定 Token 為 string 型別的 “Token01”。
- SendToken02Click():傳送 string 訊息,並指定 Token 為 “Token02”,與其他 string 訊息做區隔。
Window3_SubSendViewModel
1 | public partial class Window3_SubSendViewModel : ObservableRecipient |
第一個觀察者 ViewModel 的部分:
- 接收未指定 Token (即預設 Unit.Default) 的 string 訊息。
- 接收指定 Token 為 “Token01” 的 string 訊息。
Window3_SubReceiveViewModel
1 | public partial class Window3_SubReceiveViewModel : ObservableRecipient |
第二個觀察者 ViewModel 的部分:
- 接收未指定 Token (即預設 Unit.Default) 的 string 訊息。
- 接收指定 Token 為 “Token02” 的 string 訊息。
- 使用 ConsoleWriteStyled 方法,讓輸出文字呈現不同顏色。
Window3_SubReceiveAnotherViewModel
1 | public partial class Window3_SubReceiveAnotherViewModel : ObservableRecipient |
MainViewModel 部分,集中管理發送者與兩個觀察者的 ViewModel 實例。
Window3_MainViewModel
1 | public partial class Window3_MainViewModel : ObservableObject |
View 的部分,為配合發送者 ViewModel 的接口,撰寫相應的 UI 元件。
Window3
1 | <Window.DataContext> |
執行的畫面與結果,如下,
依序按下 View 中從上到下的按鈕,
從 Console 輸出的內容可以觀察到,兩個觀察者都成功接收到對應 Token 的訊息,並顯示出來。
在 Messenger
應用中,可以透過兩種方式來避免訊息混淆:
- 為每種訊息定義不同的傳送類型。
- 使用 Token 機制進行區別。
其中 Token 並不限使用 string,實際上它是以泛型 T (Class) 實作,因此也可以使用自訂物件作為 Token,只是該類別需實作 IEquatable
介面。使用 string 作為 Token 是最簡單直接的做法。
屬性變更時的通知
就像在 ObservableObject
中,可以透過 NotifyCanExecuteChangedFor
Attribute 來通知指令狀態改變
在 ObservableRecipient
中,也提供了一個專屬 Attribute :NotifyPropertyChangedRecipients
,這個 Attribute 可在屬性變更時,自動透過 Messenger 通知其他註冊的觀察者。
發送者 ViewModel 的部分說明如下:
- RealtimeStr 使用
[ObservableProperty]
可自動通知 Viwe 屬性已變更,同時套用[NotifyPropertyChangedRecipients]
,使該屬性在變動時,也會自動傳送訊息給已註冊的觀察者。
Window4_SubSendViewModel
1 | public partial class Window4_SubSendViewModel : ObservableRecipient |
首先前往 RealtimeStr 屬性在變更時所觸發的程式碼,大部分內容與單純使用 [ObservableProperty]
時相同,都是 MVVM 中用來通知 View 屬性已更新的機制。
比較特別的是,在程式碼的最後,多了 Broadcast
方法的呼叫。
接著查看 Broadcast
方法的定義,可以看到它會將屬性變更的相關資訊封裝進 PropertyChangedMessage<T>
物件中,並透過 Messenger.Send()
將訊息發送出去。
簡單來說,其運作原理是:當屬性發生變更時,系統會自動透過 Messenger.Send(new PropertyChangedMessage<T>())
發送通知。若有需要接收該通知的觀察者,只要註冊 PropertyChangedMessage<T>
即可取得更新資訊。
觀察者 ViewModel 的部分說明如下:
- 為了對應
[NotifyPropertyChangedRecipients]
所發送的訊息,觀察者需要註冊PropertyChangedMessage<T>
類型的監聽。 - 從
PropertyChangedMessage<T>
中,可以取得變更的屬性名稱、舊值、新值,以及變更來源的物件。若有需要,便可依這些資訊進行對應處理,這邊僅將它們簡單地輸出至 Console。
Window4_SubReceiveViewModel
1 | public partial class Window4_SubReceiveViewModel : ObservableRecipient |
MainViewModel 部分,集中管理發送者與觀察者的 ViewModel 實例。
Window4_MainViewModel
1 | public partial class Window4_MainViewModel : ObservableObject |
View 的部分,為配合發送者 ViewModel 的接口,撰寫相應的 UI 元件。
其中,RealtimeStr 的 Binding 使用了 UpdateSourceTrigger=PropertyChanged,這樣可以即時觸發 [NotifyPropertyChangedRecipients]
屬性,觀察其通知效果。
Window4
1 | <Window.DataContext> |
執行的畫面與結果,如下,
最下方的 TextBox 在輸入文字時,會即時變更 RealtimeStr 屬性。
從 Console 輸出可觀察到,當 RealtimeStr 屬性發生變化時,觀察者所接收到的訊息內容與細節。
無法繼承 ObservableRecipient 時的替代方案
在物件導向程式設計中,繼承是一個重要的設計手段。
在前面的範例中,ViewModel 都透過繼承 ObservableRecipient
,不僅實作了 INotifyPropertyChanged
,滿足 WPF MVVM 所需的資料綁定需求,同時也內建支援 Messenger
的訊息傳遞功能。
不過,由於 C# 不支援多重繼承,當 ViewModel 已經繼承了其他基底類別時,就無法再繼承 ObservableRecipient
,進而無法直接使用其封裝好的 Messenger 功能。
為此可以使用 CommunityToolkit.Mvvm 提供的其他方案:
[ObservableRecipient]
Attribute ,標註在類別上後,即可獲得與繼承ObservableRecipient
類似的行為。IRecipient<T>
,適用於簡單場景,可讓物件訂閱並接收特定型別的訊息。
發送者 ViewModel 的部分說明如下:
- 類別上標註了
[ObservableRecipient]
Attribute ,使其具備與繼承ObservableRecipient
類別相同的功能。 - 繼承
ObservableValidator
,該類別與ObservableRecipient
同樣繼承自ObservableObject
,但兩者彼此無關,無法同時繼承。若需要同時具備驗證與Messenger
功能,就必須透過[ObservableRecipient]
Attribute 來補足。 - 本範例雖然是以
ObservableValidator
為基底類別,但實務上可以替換為任何其他類別,並不限定於此狀況。 - 當使用
[ObservableRecipient]
時,必須手動指定Messenger
實例( Messenger = WeakReferenceMessenger.Default )。相較之下,若是直接繼承ObservableRecipient
,則建構函式會自動初始化Messenger
。
Window5_SubSendViewModel
1 | [ ] |
第一個觀察者 ViewModel 的部分說明如下:
- 類別上同樣標註了
[ObservableRecipient]
Attribute 。 - 依然需要手動指定
Messenger
實例。 - OnActivated() 的部分,需要改用 partial 形式來擴充方法定義,與繼承
ObservableRecipient
時直接覆寫 OnActivated() 方法的情況不同。
Window5_SubReceiveViewModel
1 | [ ] |
第二個觀察者 ViewModel 的部分說明如下:
- 繼承了
IRecipient<T>
介面,因為沒有繼承ObservableRecipient
,所以需要自行使用Messenger
進行註冊。 - 由於實作了
IRecipient<T>
,在註冊時不需額外指定接收方法,Messenger
會自動呼叫介面中定義的 Receive 方法。
Window5_SubReceiveAnotherViewModel
1 | public partial class Window5_SubReceiveAnotherViewModel |
MainViewModel 部分,集中管理發送者與兩個觀察者的 ViewModel 實例。
Window5_MainViewModel
1 | public partial class Window5_MainViewModel : ObservableObject |
View 的部分,為配合發送者 ViewModel 的接口,撰寫相應的 UI 元件。
Window5
1 | <Window.DataContext> |
執行的畫面與結果,如下,
發送者的部分,
觀察者接收到的訊息部分,
其行為與繼承 ObservableRecipient
時的情況基本相同。
直接使用 Messenger 機制
到目前為止的範例,主要都是在 ViewModel 中使用 Messenger
,並對不同情境提供相應的處理,那麼一般的類別是否也能使用呢?
答案是可以的,Messenger
提供了靜態的功能,能讓任何物件使用,而 ObservableRecipient
則是對此功能的封裝與簡化。
接下來,將直接使用 Messenger
,其中也包含讓 View 直接使用的範例。
首先,定義了一個自訂類別 ScrollIdxMessage 作為傳送的物件。
ScrollIdxMessage
1 | public class ScrollIdxMessage |
發送者 ViewModel 的部分說明如下:
- StringCollection,供 UI 綁定的字串集合。
- SendStrClick(),與前面範例類似,傳送字串訊息,但改為使用
WeakReferenceMessenger
進行發送。 - SendScrollClick(),根據 StringCollection 的數量,隨機產生一個索引,並以 ScrollIdxMessage 封裝後,透過
WeakReferenceMessenger
傳送該訊息。
Window6_SubSendViewModel
1 | public partial class Window6_SubSendViewModel : ObservableObject |
觀察者普通類別的部分說明如下:
- 在建構函式中,使用
WeakReferenceMessenger
註冊接收 string 類型的訊息。
Window6_SubReceiveClass
1 | public class Window6_SubReceiveClass |
MainViewModel 部分,集中管理發送者與觀察者的實例。
Window6_MainViewModel
1 | public partial class Window6_MainViewModel : ObservableObject |
View 的 XAML 部分,為配合發送者 ViewModel 的接口,撰寫相應的 UI 元件。
其中包含一個名為 listBox 的 ListBox 控制項,用來顯示發送者 ViewModel 中的字串集合 StringCollection。
Window6
1 | <Window.DataContext> |
習慣使用 MVVM 架構後,大多會在 XAML 中處理各種互動行為。
除了 Button
支援 Command
綁定外,其他控制項通常會透過 EventTrigger
將事件轉換為 Command
,或在較複雜的情況下,使用 Behavior
來實現對應的功能。
不過,有些控制項的方法並不是 event,因此無法用 EventTrigger
對應,例如 ListBox
中的 ScrollIntoView()
方法,可用來將視圖捲動至特定項目,但它並不屬於事件,也不是命令,因此在 MVVM 架構下較難直接使用。
雖然可以透過 Behavior
來解決,但實作起來可能較為繁瑣,另一種作法是讓 ViewModel 與 View 在初始化時透過事件或委派建立直接關聯。
現在既然已經熟悉了 Messenger
機制,就多了一種新的方式來實現這類需求,讓 View 也成為訊息的接收者,執行對應的操作。
View 的 CS 部分說明如下:
- 在建構函式中,使用
WeakReferenceMessenger
註冊接收自定義的 ScrollIdxMessage 類型的訊息。 - 當接收到訊息時,則呼叫 ScrollItemToIdx() 方法,進而觸發 listBox.ScrollIntoView(),使畫面自動捲動至指定項目。
Window6
1 | public partial class Window6 : Window |
稍微看看 View 中的普通方法:
- 例如
ListBox
,有ScrollIntoView
、SelectAll
等方法。
- 又例如
TextBox
,有ScrollToLine
、Select
等方法。
這些方法本身不是 command,也不是 event,因此在 MVVM 架構下無法直接從 ViewModel 呼叫;那麼可以使用 Messenger
機制,就可以讓 ViewModel 發送訊息,通知 View 執行這些方法。
執行的畫面與結果,如下,
首先是普通類別作為觀察者的情況,與先前的範例都相同。
接著在範例中產生一個隨機數字,讓 ListBox 對應索引的項目自動捲動到畫面中。下圖為索引 11 的結果。
再次產生隨機數字,這次讓 ListBox 捲動到索引 14 的結果。
額外補充:可接收回應的 Messenger
基本上,CommunityToolkit.Mvvm 套件中的 Messenger
機制,類似一般的訂閱機制 ,發送者傳遞一份訊息物件,觀察者接收後進行處理,整個過程就結束了。
不過,套件中也提供了一種特殊的訊息類型,讓觀察者在接收到訊息後,能夠回傳一個結果給發送者,這種「請求並取得回應」的場景,就可以透過 RequestMessage<T>
來實現。
以下範例將說明其用法。
發送者 ViewModel 的部分說明如下:
- SendMegClick() ,建立一個
RequestMessage<double>
實例,這與一般Messenger
傳送資料的情況不同,這邊是主動向觀察者「請求」一筆 double 型別的資料。 - 將
RequestMessage
透過Messenger.Send()
傳送出去後,接著透過HasReceivedResponse
屬性檢查是否有收到觀察者的回應。 - 若有回應,則可使用 Response 屬性取得觀察者所回傳的資料,並在 Console 中印出對應結果,若未收到回應,也會顯示相應訊息。
Window7_SubSendViewModel
1 | public partial class Window7_SubSendViewModel : ObservableRecipient |
觀察者 ViewModel 的部分說明如下:
- 註冊接收
RequestMessage<double>
類型的訊息,當接收到請求時,代表有其他物件主動要求一筆 double 資料。 - 在
Receive
方法中,隨機產生一個 double 數值,並透過Reply()
將此值回傳給請求者,完成一次有回應的訊息傳遞。
Window7_SubReceiveViewModel
1 | public partial class Window7_SubReceiveViewModel : ObservableRecipient |
MainViewModel 部分,集中管理發送者與觀察者的實例。
Window7_MainViewModel
1 | public partial class Window7_MainViewModel : ObservableObject |
View 的部分,為配合發送者 ViewModel 的接口,撰寫相應的 UI 元件。
Window7
1 | <Window.DataContext> |
執行的畫面與結果如下,
按下按鈕後,可在 Console 中看到完整的訊息流程:
- 白字部分:代表發送者,顯示了訊息的發送過程與收到回應後的結果。
- 綠字部分:代表觀察者,顯示接收到的請求訊息,以及隨機產生並回覆的數值。
總結
到此,已經對 CommunityToolkit.Mvvm
套件中 Messenger
的使用方式,以及在一些特殊情境下的應對方式,已有相當的了解。
不論是在 ViewModel、一般類別,甚至直接在 View 中,都能靈活運用 Messenger
進行訊息傳遞。
該套件中,關於 Messenger
的部分還有其他物件。
而由於篇幅限制,加上這些功能較為進階,所以未深入探討,例如:
Messaging 相關類別:
StrongReferenceMessenger
MessageHandler<TRecipient, TMessage>
Messages 相關類別:
AsyncRequestMessage<T>
CollectionRequestMessage<T>
AsyncCollectionRequestMessage<T>
這些進階功能可用於非同步請求、集合型訊息,或需要 Strong 參考與自訂訊息處理的情境。
整體而言,Messenger
相較於一般的發布/訂閱機制,有其特別之處,例如自動型別匹配、支援回應訊息以及輕鬆與 ViewModel 或 View 整合等功能。但這並不代表它是適用於所有場合的最佳方案,建議仍依照專案架構與實際需求來選擇使用方式。
自言自語543
回想剛接觸 WPF 的 MVVM 架構時,除了要實作 INotifyPropertyChanged、ICommand 這兩個介面外,ViewModel 與 View 的 Binding 屬性還要寫一堆重複度極高的程式碼。
在使用 CommunityToolkit.Mvvm 套件之後,不僅免去了自己尋找介面實作的麻煩,還有一些特別的 Attribute 可以減少大量重複的程式。
用了套件後,考試都考 100 分呢 (不要瞎掰好嗎?)
套件裡除了必用的 ObservableObject,還有依情況會用到的 ObservableValidator。其實也曾注意過 ObservableRecipient,但一開始以為它只是普通的發布/訂閱機制,所以沒有特別去深入了解。直到某次需要在 View 裡呼叫一般方法時,才發現它其實有特別之處。
接著開始做相關測試,沒想到居然是坑最大的一個……,結果篇幅寫得有點長,也因此有點亂,未來應該會稍微控制一下。