前言
之前一直用Stylet
,寫過兩篇乘風破浪,遇見Stylet超清爽WPF御用MVVM框架,愛不釋手的.Net Core輕量級MVVM框架、乘風破浪,超清爽WPF御用MVVM框架Stylet,啓動到登錄設計的高階實戰,用這確實很爽,在MVVM這塊非常省心,用起來有點在寫UWP的感覺。
但是這個玩意自帶了IOC,我又想引入MediatR
來實現面向消息事件的編程(這個做客戶端的同學可能不熟悉哈哈,一般大家都是面向接口編程),它只針對.Net Core原汁原味的DI有擴展支持,所以不得不最終棄用Stylet
。
離開了Stylet
,我就沒有了MVVM的配套支持,怎麼辦呢?想起了之前寫的乘風破浪,遇見MVVM Toolkit官方社區首推MVVM框架,後UWP時代的拯救版MVVM框架,這是有社區維護的一個MVVM框架,用它肯定沒錯了,牛逼的是,微軟改名部這麼快就把它從Microsoft.Toolkit.Mvvm
改名爲CommunityToolkit.Mvvm
了。
所以最終的組合是:
.Net 6
Microsoft.Extensions.DependencyInjection
MediatR.Extensions.Microsoft.DependencyInjection
CommunityToolkit.Mvvm
PropertyChanged.Fody
引入官方依賴注入(DI)
準備示例項目
這個倒是不難,這裏我們使用.Net 6
/5
/3.1
來創建WPF應用哈。
引入依賴包
依賴包
https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.DependencyInjection
基本使用
剛創建完的項目模板中,App.Xaml.cs
中很簡單,啥都沒有。
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
我們準備一個熟悉的函數ConfigureServices
來得到IServiceProvider
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
return services.BuildServiceProvider();
}
這裏我們利用它的構造函數來調用這個函數
public IServiceProvider Services { get; }
public App()
{
InitializeComponent();
Services = ConfigureServices();
}
這裏定義一個Services
對象來託管住IServiceProvider
,方便後面做個全局使用。
基於DI實現頁面窗體展示
註冊窗體到容器中
既然有了DI,那我們就要用起來,把窗體丟進去,從容器中拿出來用。
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddSingleton<MainWindow>();
return services.BuildServiceProvider();
}
這裏把MainWindow
頁面做個單實例注入。
使用容器中窗體
我們在定一個啓動函數OnStartup
,把它取出來,並且展示出來。
private void OnStartup(object sender, StartupEventArgs e)
{
var mainWindow = Services.GetService<MainWindow>();
mainWindow?.Show();
}
改造啓動入口
上哪去調用這個啓動函數OnStartup
呢?我們去App.xaml.cs
對應的App.xaml
改動下。
改動前,它是使用StartupUri
直接導向MainWindow.xaml
的,這裏我們把它換成Startup
函數指定。
<Application x:Class="demoForWpfApp60.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:demoForWpfApp60"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
<Application x:Class="demoForWpfApp60.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:demoForWpfApp60"
Startup="OnStartup">
<Application.Resources>
</Application.Resources>
</Application>
這樣就使用了容器來管理界面了。
.Net 5或者.Net Core 3.1也可以
雖然在.Net 5
和.Net Core 3.1
會遇到一些不友好的警告提示,但是還是能成功運行起來。
Microsoft.Extensions.DependencyInjection.Abstractions 7.0.0 doesn't support net5.0-windows and has not been tested with it. Consider upgrading your TargetFramework to net6.0 or later. You may also set <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> in the project file to ignore this warning and attempt to run in this unsupported configuration at your own risk.
Microsoft.Extensions.DependencyInjection.Abstractions 7.0.0 doesn't support netcoreapp3.1 and has not been tested with it. Consider upgrading your TargetFramework to net6.0 or later. You may also set <SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings> in the project file to ignore this warning and attempt to run in this unsupported configuration at your own risk.
引入Mvvm支持
引入CommunityToolkit.Mvvm包
依賴包
CommunityToolkit.Mvvm最低也要.Net 6了,其的前身就是Microsoft.Toolkit.Mvvm
,如果是.Net 5或者.NET Standard 2.0可以考慮優先使用Microsoft.Toolkit.Mvvm
dotnet add package CommunityToolkit.Mvvm
dotnet add package Microsoft.Toolkit.Mvvm
添加ViewModel
創建一個簡單的ViewModel示例: MainViewModel
,它繼承了ObservableObject
,這是CommunityToolkit.Mvvm
中一個通過實現INotifyPropertyChanged
和INotifyPropertyChanging
接口可觀察的對象的基類。它可以用作需要支持屬性更改通知的各種對象的起點。
internal class MainViewModel : ObservableObject
{
}
namespace CommunityToolkit.Mvvm.ComponentModel;
/// <summary>
/// A base class for objects of which the properties must be observable.
/// </summary>
public abstract class ObservableObject : INotifyPropertyChanged, INotifyPropertyChanging
{
}
然後在容器中註冊它,這裏我們把它註冊爲瞬時模式
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddTransient<MainViewModel>();
services.AddSingleton<MainWindow>();
return services.BuildServiceProvider();
}
同時,我們構建一個靜態變量來託管當前的App
實例。
public partial class App : Application
{
public new static App Current => (App)Application.Current;
}
然後在View界面使用它作爲數據上下文
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = App.Current.Services.GetService<MainViewModel>();
}
}
這樣就可以了,當前MainViewModel
和MainView
就對應起來了。
添加簡單綁定
接下來我們在MainViewModel
添加簡單屬性和事件及命令。
internal class MainViewModel : ObservableObject
{
private int _count;
public int Count
{
get => _count;
set => SetProperty(ref _count, value);
}
public ICommand AddCountCommand => new RelayCommand(AddCount);
public void AddCount()
{
Count++;
}
}
在界面上我們構建一個按鈕吧
<Window x:Class="demoForWpfApp60.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:demoForWpfApp60"
mc:Ignorable="d"
Title="demoForWpfApp60" Height="450" Width="800">
<Grid>
<Button Content="{Binding Count}" Command="{Binding AddCountCommand}"/>
</Grid>
</Window>
就這麼簡單,就可以運行起來了。
支持異步的命令綁定
public IAsyncRelayCommand RemoveCountCommand => new AsyncRelayCommand(RemoveCount);
public async Task RemoveCount()
{
await Task.Run(() =>
{
Count--;
});
}
<Window x:Class="demoForWpfApp60.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:demoForWpfApp60"
mc:Ignorable="d"
Title="demoForWpfApp60" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Button
Grid.Row="0"
Content="{Binding Count}"
Command="{Binding AddCountCommand}"
/>
<Button
Grid.Row="1"
Content="{Binding Count}"
Command="{Binding RemoveCountCommand}"
/>
</Grid>
</Window>
更優雅的通知界面
之前我們寫一個可觀察且通知界面的屬性是這樣寫的。
private int _count;
public int Count
{
get => _count;
set => SetProperty(ref _count, value);
}
能不能更優雅一點呢,可以,引入PropertyChanged.Fody
包,它會自動在編譯時給已知屬性注入IL代碼,以達到PropertyChanged
通知的效果。
dotnet add package PropertyChanged.Fody
直接寫成
public int Count { get; set; }
它會幫我們在編譯的時候,自動生成前面那種代碼,省去了重複代碼量。
引入MediatR支持
添加MediaR包
有了官方DI,添加對MediatR的支持就簡單了。
依賴包
https://www.nuget.org/packages/MediatR.Extensions.Microsoft.DependencyInjection
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
基本使用
通過程序集掃描來注入MediatR相關服務。
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddMediatR(typeof(MainViewModel).Assembly);
return services.BuildServiceProvider();
}
創建命令和命令處理
internal class RemoveCountCommand: IRequest<int>
{
public int Count { get; private set; }
public RemoveCountCommand(int count) { this.Count = count; }
}
internal class RemoveCountCommandHandler : IRequestHandler<RemoveCountCommand, int>
{
public async Task<int> Handle(RemoveCountCommand request, CancellationToken cancellationToken)
{
return await Task.FromResult(request.Count - 1);
}
}
使用MediatR發送事件。
internal class MainViewModel : ObservableObject
{
private int _count;
public int Count
{
get => _count;
set => SetProperty(ref _count, value);
}
private readonly IMediator _mediator;
public MainViewModel(IMediator mediator)
{
_mediator = mediator;
}
public IAsyncRelayCommand RemoveCountCommand => new AsyncRelayCommand(RemoveCount);
public async Task RemoveCount()
{
Count = await _mediator.Send(new RemoveCountCommand(Count));
}
}
這樣運行也是一樣的,變成了中介者模式,面向消息事件編程了。
參考
- https://github.com/TaylorShi/HelloNetWPF
- 乘風破浪,遇見Stylet超清爽WPF御用MVVM框架,愛不釋手的.Net Core輕量級MVVM框架
- 乘風破浪,超清爽WPF御用MVVM框架Stylet,啓動到登錄設計的高階實戰
- 乘風破浪,遇見MVVM Toolkit官方社區首推MVVM框架,後UWP時代的拯救版MVVM框架
- [WPF]使用MVVM Toolkit構建MVVM程序
- https://www.nuget.org/packages/Microsoft.Toolkit.Mvvm
- https://www.nuget.org/packages/CommunityToolkit.Mvvm
- [WPF 自定義控件]自定義控件庫系列文章
- [WPF 自定義控件]模仿UWP的ProgressRing
- WPF Toolkit.Mvvm框架與IOC注入學習
- https://github.com/CommunityToolkit/WindowsCommunityToolkit
- MVVM工具包簡介
- https://github.com/CommunityToolkit/MVVM-Samples
- https://github.com/maythamfahmi/WpfSampleDi/tree/net6/WpfSampleDi
- Dependency Injection in .NET Core 3.0 for WPF