[WPF] 使用 MVVM Toolkit 構建 MVVM 程序

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',它的詳細資料可以參考下面鏈接:

Nugethttps://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 實現了 INotifyPropertyChangedINotifyPropertyChanging,並觸發 PropertyChangedPropertyChanging 事件。

public class User : ObservableObject
{
    private string name;

    public string Name
    {
        get => name;
        set => SetProperty(ref name, value);
    }
}

在這段示例代碼中,如果 name 和 value 的值不同,首先觸發 PropertyChanging 事件,然後觸發 PropertyChanged

2.2 RelayCommand

RelayCommandRelayCommand<T> 實現了 ICommand 接口,INotifyPropertyChangedICommand 是 MVVM 模式的基礎。下面的代碼使用 ObservableObjectRelayCommand 展示一個基本的 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

AsyncRelayCommandAsyncRelayCommand<T> 也實現了 ICommand,不過它們支持異步操作,提供的 ExecutionTaskIsRunning 兩個屬性對監視任務運行狀態十分有用。

例如這個 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>

點擊 ButtonDownloadTextAsync 開始運行,在 UI 上 TextBlock 和 ProgressRing 綁定到 ExecutionTaskIsRunning 並顯示任務運行狀態,最後通過 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 主要通過 RegisterSend 進行信息交換,它的使用方式類似於 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. 參考

Sample repo for MVVM package

Microsoft.Toolkit.Mvvm at master

[Feature] Basic MVVM primitives (.NET Standard)

NuGet Gallery _ Microsoft.Toolkit.Mvvm

MVVM Light Toolkit

數據綁定和 MVVM

[Feature] Microsoft.Toolkit.Mvvm package (Preview 5)

MVVM Toolkit Preview 3 & The Journey of an API

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章