MAUI 中使用 DI 及 MVVM
为什么要使用 依赖注入 和 MVVM如何在 MAUI 中使用依赖注入如何使用 MVVM不使用框架或组件定义一个 BaseViewModelMainViewModel 的实现MainPage 中进行 Binding 使用组件优化前面的 ViewModel 代码 基项目的效果
为什么要使用 依赖注入 和 MVVM
MVVM 和 依赖注入 有助于开发出松耦合可维护以及可测试的应用程序。
如何在 MAUI 中使用依赖注入
依赖注入在MAUI中是原生支持的。
创建一个MAUI APP项目,根目录会有 MauiProgram.cs
public static MauiApp CreateMauiApp(){var builder = MauiApp.CreateBuilder();builder.UseMauiApp<App>().ConfigureFonts(fonts =>{fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");});// 注入页面builder.Services.AddTransient<MainPage>();return builder.Build();}
在 builder 中注入需要的页面或服务即可 如何使用 MVVM
不使用框架或组件
先说下不使用任何框架或组件是如何实现的。定义一个 BaseViewModel
BaseViewModel
会被后来的 viewmodel
所继承,并且所有的公共属性、方法都将再次进行声明。BaseViewModel
需要继承和实现 INotifyPropertyChanged
才能够实现 view
和 viewmodel
之间的通讯。
using System.ComponentModel;using System.Runtime.CompilerServices;namespace CrossPlatformTools.ViewModel{ public class BaseViewModel : INotifyPropertyChanged { private bool isBusy; public bool IsBusy { get => isBusy; set { if (isBusy == value) return; isBusy = value; OnPropertyChanged(); OnPropertyChanged(nameof(IsNoteBusy)); } } public bool IsNoteBusy => !IsBusy; private string title; public string Title { get => title; set { if (title == value) return; title = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged([CallerMemberName] string name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }}
MainViewModel 的实现
MainViewModel
继承 BaseViewModel
但是 MainViewModel
中独有的属性和方法还是需要的MainViewModel
中进行声明的。
namespace CrossPlatformTools.ViewModel{ public class MainViewModel : BaseViewModel { private int count = 0; public int Count { get { return count; } set { if (count == value) return; count = value; OnPropertyChanged(); } } public Command CountCommand { get; } public MainViewModel() { Title = "首页"; CountCommand = new Command(OnCounter); } private void OnCounter() { Count++; } }}
在 BaseViewModel 声明的属性 MainViewModel 中就可以直接使用了。Command
的声明相比于通知属性就简便很多。声明 Command
然后在构造函数中调用即可。
MainPage 中进行 Binding
在 xaml 中需要引入命名空间:xmlns:viewmodel="clr-namespace:CrossPlatformTools.ViewModel"
只引入命名空间这时想要使用属性是无法提示的,还需要设置 DataType
:x:DataType="viewmodel:MainViewModel"
<?xml version="1.0" encoding="utf-8" ?><ContentPage x:Class="CrossPlatformTools.View.MainPage" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewmodel="clr-namespace:CrossPlatformTools.ViewModel" Title="{Binding Title}" x:DataType="viewmodel:MainViewModel"> <Grid> <VerticalStackLayout Padding="30,0" Spacing="25" VerticalOptions="Center"> <Image HeightRequest="200" HorizontalOptions="Center" SemanticProperties.Description="Cute dot net bot waving hi to you!" Source="dotnet_bot.png" /> <Label FontSize="18" HorizontalOptions="Center" SemanticProperties.Description="Welcome to dot net Multi platform App U I" SemanticProperties.HeadingLevel="Level2" Text="Welcome to .NET Multi-platform App UI" /> <Button Command="{Binding CountCommand}" HorizontalOptions="Center" SemanticProperties.Hint="Counts the number of times you click" Text="{Binding Count}" /> </VerticalStackLayout> </Grid></ContentPage>
你以为这样就完了吗,当然不是,只在xaml中引入命名空间进行 Command
和 Property
的Binding 是不行了,还要进行上下文绑定。
using CrossPlatformTools.ViewModel;namespace CrossPlatformTools.View;public partial class MainPage : ContentPage{ public MainPage(MainViewModel mainViewModel) { InitializeComponent(); BindingContext = mainViewModel; }}
这里有的一不同点,一般来说 BindingContext
是这样做的 BindingContext = new MainViewModel()
但是我这里使用的是构造函数注入。
不能直接使用 需要在 MauiProgram.cs
中注入才能使用。
使用组件优化前面的 ViewModel 代码
原生的方式实现 MVVM 时, ViewModel
中的属性定义非常需要很多的代码,并且要手动实现 PropertyChanged
事件。
CommunityToolkit.Mvvm
实现 官方描述:
此包包含一个.NET MVVM库,其中包含以下帮助程序:
需要引入 CommunityToolkit.Mvvm.ComponentModel
命名空间
使用 partial
class 标识符,CommunityToolkit.Mvvm
内部实现使用的是源生成器,所有需要使用 partial
进行标识。
变量的声明必须使用驼峰式命名规则,才可以与生成的以 Pascal 命名规则的属性名不重复。
关于 源生成器 不明白的可以去看前面的文章 C# 源代码生成器 讲解了 源生成器的用法。
基类就变成了下面这样,少了很多的代码
using CommunityToolkit.Mvvm.ComponentModel;namespace CrossPlatformTools.ViewModel{ public partial class BaseViewModel : ObservableObject { [ObservableProperty] [NotifyPropertyChangedFor(nameof(isNoteBusy))] bool isBusy; bool isNoteBusy => !isBusy; [ObservableProperty] string title; }}
同样的 MainViewModel 代码也是要进行更改的,精简了很多。 using CommunityToolkit.Mvvm.ComponentModel;namespace CrossPlatformTools.ViewModel{ public partial class MainViewModel : BaseViewModel { [ObservableProperty] private int count = 0; public Command CountCommand { get; } public MainViewModel() { Title = "首页"; CountCommand = new Command(OnCounter); } private void OnCounter() { Count++; } }}