1. 什么是 MVVM Toolkit#
模型-視圖-視圖模型 (MVVM) 是用於解耦 UI 代碼和非 UI 代碼的 UI 體系結構設計模式。 借助 MVVM,可以在 XAML 中以聲明方式定義 UI,並使用數據綁定標記將 UI 鏈接到包含數據和命令的其他層。
微軟雖然提出了 MVVM,但又沒有提供一個官方的 MVVM 庫(多年前有過 Prism,但已經離家出走了)。每次有人提起 MVVM 庫,有些人會推薦 Prism(例如我),有些人會推薦 MVVMLight。可是現在 Prism 已經決定不再支持 UWP , 而 MVVMLight 又不再更新,在這左右為難的時候 Windows Community Toolkit 挺身而出發布了 MVVM Toolkit。 MVVM Toolkit 延續了 MVVMLight 的風格,是一個輕量級的組件,而且它基於 .NET Standard 2.0,可用於UWP, WinForms, WPF, Xamarin, Uno 等多個平台。相比它的前身 MVVMLight,它有以下特點:
- 更高:版本號更高,一出手就是 7.0。
- 更快:速度更快,MVVM Toolkit 從一開始就以高性能為實現目標。
- 更強:后台更強,MVVM Toolkit 的全稱是 'Microsoft.Toolkit.Mvvm',根正苗紅。
目前,MVVM Toolkit 已經更新到 '7.0.2',它的詳細資料可以參考下面鏈接:
Nuget:https://www.nuget.org/packages/Microsoft.Toolkit.Mvvm
文檔:https://docs.microsoft.com/en-us/windows/communitytoolkit/mvvm/introduction
源碼:https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Mvvm
雖然是 Windows Community Toolkit 項目的一部分,但它有獨立的 Sample 和文檔,可以在這里找到:
https://github.com/CommunityToolkit/MVVM-Samples
這篇文章將簡單介紹 MVVM Toolkit 的幾個基本組件。
2. 各個組件#
2.1 ObservableObject#
ObservableObject 實現了 INotifyPropertyChanged
和INotifyPropertyChanging
,並觸發 PropertyChanged
和 PropertyChanging
事件。
public class User : ObservableObject { private string name; public string Name { get => name; set => SetProperty(ref name, value); } }
在這段示例代碼中,如果 name 和 value 的值不同,首先觸發 PropertyChanging
事件,然后觸發 PropertyChanged
。
2.2 RelayCommand#
RelayCommand
和 RelayCommand<T>
實現了 ICommand
接口,INotifyPropertyChanged
和 ICommand
是 MVVM 模式的基礎。下面的代碼使用 ObservableObject
和 RelayCommand
展示一個基本的 ViewModel:
public class MyViewModel : ObservableObject { public MyViewModel() { IncrementCounterCommand = new RelayCommand(IncrementCounter); } private int counter; public int Counter { get => counter; private set => SetProperty(ref counter, value); } public ICommand IncrementCounterCommand { get; } private void IncrementCounter() => Counter++; }
<Page x:Class="MyApp.Views.MyPage" xmlns:viewModels="using:MyApp.ViewModels"> <Page.DataContext> <viewModels:MyViewModel x:Name="ViewModel"/> </Page.DataContext> <StackPanel Spacing="8"> <TextBlock Text="{x:Bind ViewModel.Counter, Mode=OneWay}"/> <Button Content="Click me!" Command="{x:Bind ViewModel.IncrementCounterCommand}"/> </StackPanel> </Page>
在這段示例里 IncrementCounterCommand
包裝了 IncrementCounter
函數提供給 Button 綁定。IncrementCounter
函數更改 Counter
的值並通過 PropertyChanged
事件通知綁定的 TextBlock。
2.3 AsyncRelayCommand#
AsyncRelayCommand
和 AsyncRelayCommand<T>
也實現了 ICommand
,不過它們支持異步操作,提供的 ExecutionTask
和 IsRunning
兩個屬性對監視任務運行狀態十分有用。
例如這個 ViewModel:
public MyViewModel() { DownloadTextCommand = new AsyncRelayCommand(DownloadTextAsync); } public IAsyncRelayCommand DownloadTextCommand { get; } private async Task<string> DownloadTextAsync() { await Task.Delay(3000); // Simulate a web request return "Hello world!"; }
使用相關的 UI 代碼:
<Page.Resources> <converters:TaskResultConverter x:Key="TaskResultConverter"/> </Page.Resources> <StackPanel Spacing="8"> <TextBlock> <Run Text="Task status:"/> <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask.Status, Mode=OneWay}"/> <LineBreak/> <Run Text="Result:"/> <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, Mode=OneWay}"/> </TextBlock> <Button Content="Click me!" Command="{x:Bind ViewModel.DownloadTextCommand}"/> <muxc:ProgressRing HorizontalAlignment="Left" IsActive="{x:Bind ViewModel.DownloadTextCommand.IsRunning, Mode=OneWay}"/> </StackPanel>
點擊 Button
后 DownloadTextAsync
開始運行,在 UI 上 TextBlock 和 ProgressRing 綁定到 ExecutionTask
和 IsRunning
並顯示任務運行狀態,最后通過 TaskResultConverter
顯示任務結果。
2.4 Messenger#
對於主要目的是松耦合的 MVVM 框架,提供一個用於消息交換的系統十分有必要。MVVM Toolkit 中用於消息交換的核心是 WeakReferenceMessenger
類。
// Create a message public class LoggedInUserChangedMessage : ValueChangedMessage<User> { public LoggedInUserChangedMessage(User user) : base(user) { } } // Register a message in some module WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this, (r, m) => { // Handle the message here, with r being the recipient and m being the // input messenger. Using the recipient passed as input makes it so that // the lambda expression doesn't capture "this", improving performance. }); // Send a message from some other module WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));
正如這段代碼所示,WeakReferenceMessenger
主要通過 Register
和 Send
進行信息交換,它的使用方式類似於 MVVMLight 的 messenger 類。MVVM Toolkit 另外還提供了一個 StrongReferenceMessenger
類,更多使用方法可以參考這篇 文檔。Messenger
功能強大且簡單易用,但也由於誤用會帶來風險而引發了一些爭議,有必要更詳細地理解它的原理和用法以避免它帶來的其它風險,這篇文章只是簡單地介紹一下它的用法。
2.5 ObservableRecipient#
ObservableRecipient
繼承了 ObservableObject
並支持從 Messenger
接收信息,可通過 IsActive
屬性激活或停用。它可以用作 ViewModel 的基類,事實上它的作用基本上相遇於 MVVMLight 中的 ViewModelBase :
public class MyViewModel : ObservableRecipient, IRecipient<LoggedInUserRequestMessage> { public void Receive(LoggedInUserRequestMessage message) { // Handle the message here } }
3. The 性能#
MVVM Toolkit 在開發過程中為了追求卓越的性能做了很多努力,例如提供一個 StrongReferenceMessenger
類,性能如上圖所示地有了大幅提升。又例如下面這篇文章所介紹的:
MVVM Toolkit Preview 3 & The Journey of an API
有興趣的話可以通過源碼詳細了解一下。
4. 結語#
這篇文章簡單介紹了 MVVM Toolkit 中的主要功能,更多內容可參考 源碼、單元測試 或 windows-toolkit/MVVM-Samples 中提供的示例應用:
5. 參考#
Microsoft.Toolkit.Mvvm at master
[Feature] Basic MVVM primitives (.NET Standard)
NuGet Gallery _ Microsoft.Toolkit.Mvvm