• MAUI 中使用 DI 及 MVVM


    为什么要使用 依赖注入 和 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 在 builder 中注入需要的页面或服务即可

    如何使用 MVVM

    不使用框架或组件

    • 先说下不使用任何框架或组件是如何实现的。

    定义一个 BaseViewModel

    BaseViewModel 会被后来的 viewmodel 所继承,并且所有的公共属性、方法都将再次进行声明。
    BaseViewModel 需要继承和实现 INotifyPropertyChanged 才能够实现 viewviewmodel 之间的通讯。

    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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    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++;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在 BaseViewModel 声明的属性 MainViewModel 中就可以直接使用了。
    Command 的声明相比于通知属性就简便很多。声明 Command 然后在构造函数中调用即可。

    MainPage 中进行 Binding

    在 xaml 中需要引入命名空间:
    xmlns:viewmodel="clr-namespace:CrossPlatformTools.ViewModel"

    只引入命名空间这时想要使用属性是无法提示的,还需要设置 DataType
    x:DataType="viewmodel:MainViewModel"

    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    你以为这样就完了吗,当然不是,只在xaml中引入命名空间进行 CommandProperty 的Binding 是不行了,还要进行上下文绑定。

    using CrossPlatformTools.ViewModel;
    
    namespace CrossPlatformTools.View;
    
    public partial class MainPage : ContentPage
    {
        public MainPage(MainViewModel mainViewModel)
        {
            InitializeComponent();
            BindingContext = mainViewModel;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里有的一不同点,一般来说 BindingContext 是这样做的 BindingContext = new MainViewModel() 但是我这里使用的是构造函数注入。
    不能直接使用 需要在 MauiProgram.cs 中注入才能使用。

    使用组件优化前面的 ViewModel 代码

    原生的方式实现 MVVM 时, ViewModel 中的属性定义非常需要很多的代码,并且要手动实现 PropertyChanged 事件。

    • 使用 CommunityToolkit.Mvvm 实现

    官方描述:
    此包包含一个.NET MVVM库,其中包含以下帮助程序:

    • ObservableObject:实现INotifyPropertyChanged接口的对象的基类。
    • ObservableRecipient:支持IMessenger服务的可观察对象的基类。
    • ObservableValidator:实现INotifyDataErrorInfo接口的对象的基类。
    • RelayCommand:一个实现ICommand接口的简单委托命令。
    • AsyncRelayCommand:支持异步操作和取消的委托命令。
    • WeakReferenceMessenger:通过不同的松散耦合对象交换消息的消息传递系统。
    • StrongReferenceMessenger:一个高性能的消息传递系统,以弱引用换取速度。
    • Ioc:用于配置依赖注入服务容器的助手类。
    • CommunityToolkit.Mvvm 是基于 .NET Standard 实现,理论上适用于所有 .NET 平台应用。

    需要引入 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 同样的 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++;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    基项目的效果

    效果

  • 相关阅读:
    Spring Cloud Gateway负载均衡
    高级查询与优化:提升查询性能的技巧与策略
    大前端html基础学习03-定位锚点透明
    【Python实战】零基础实战教程(一) Hello World!
    Java基础知识篇之类的基本概念
    【论文阅读】Deep Reinforcement Learning amidst Continual Structured Non-Stationarity
    前端ui组件库
    linux 内核 dump_page
    ChatGPT的大致原理
    JVMの内存泄漏&内存溢出案例分析
  • 原文地址:https://blog.csdn.net/qq_43562262/article/details/128065217