MAUI 中使用 DI 及 MVVM

news2025/1/20 19:10:58

MAUI 中使用 DI 及 MVVM

  • 为什么要使用 依赖注入 和 MVVM
  • 如何在 MAUI 中使用依赖注入
  • 如何使用 MVVM
    • 不使用框架或组件
      • 定义一个 BaseViewModel
      • MainViewModel 的实现
      • 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 才能够实现 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));
    }
}

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中引入命名空间进行 CommandProperty 的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库,其中包含以下帮助程序:

  • 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;
    }
}
  • 同样的 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++;
        }
    }
}

基项目的效果

效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/40899.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1535_TriCore编译器Tasking使用_汇编分区、内置函数以及伪指令

全部学习汇总&#xff1a; GreyZhang/TriCore_Tasking_Compiler_Skills: Some skills for Tasking compiler on AURIX platform. Happy hacking! (github.com) 看了一下这个章节的内容&#xff0c;原本看着页数很多拆分成了两次学习。后面发现剩下的这部分内容主要并不是框架性…

项目实战——项目上线

ps : 项目要在云服务器上部署&#xff0c;博主自己是用的腾讯云&#xff0c;大家可以选择购买合适的服务器进行部署 目录 一、AC终端操 1、ssh登录服务器 2、创建新用户 3、分配用户 sudo 权限 4、配置免密登录&#xff08;SSH&#xff09; 5、传递祖传文件给服务器 6、安…

JetpackCompose从入门到实战学习笔记2——Modifier的简单使用

JetpackCompose从入门到实战学习笔记2——Modifier的简单使用 1.Image的使用&#xff1a; Composable fun Image(modifier: Modifier) {Row {Image(painterResource(id R.mipmap.iv_pic),contentDescription stringResource(R.string.description),modifier modifier.size…

阿里P8熬了一个月肝出这份32W字Java面试手册,传到Git上目前star数达到了30K+

互联网行业竞争越来越严峻&#xff0c;面试也是越来越难&#xff0c;一直以来我都想整理一套完美的面试宝典&#xff0c;奈何难抽出时间&#xff0c;这套1000道的Java面试手册我整理了整整1个月&#xff0c;上传到Git上目前star数达到了30K这套互联网Java工程师面试题包括了&am…

【iOS】UITableView的动态Cell高度(Masonry)

动态cell高度评论长度不同引出的问题实现评论长度不同引出的问题 对于之前写的项目的评论部分&#xff0c;由于评论文字字数的不同会导致label高度不同&#xff0c;所以需要设定不同的cell高度来展示。 一开始使用了 CGSize labelSize [label.text boundingRectWithSize:CG…

数据结构与算法_AVL平衡二叉树_四种旋转,插入和删除

1 AVL平衡二叉树的概念 平衡二叉树在BST树基础上加了平衡操作。 BST树特点 &#xff1a;在BST树的基础上&#xff0c;引入了节点“平衡”的概念&#xff0c;任意一个节点的左右子树高度差不超过 1 &#xff0c;为了维持节点的平衡&#xff0c;引入了四种旋转操作&#xff0c;如…

MySQL的时区引起的前后端数据交互不畅的问题解决

MySQL的时区问题 一、问题起源 在使用swagger2进行代码测试时&#xff0c;执行完成后显示的时间与国内时间少了8个小时 强迫症的原因&#xff0c;就手贱了如下操作 ① 修改MySQL内的时间 set global time_zone 8:00; flush privileges;② show variables like “%time_zone%…

整数除法不用除号

给定两个整数 a 和 b &#xff0c;求它们的除法的商 a/b &#xff0c;要求不得使用乘号 *、除号 / 以及求余符号 % 。 注意&#xff1a; 整数除法的结果应当截去&#xff08;truncate&#xff09;其小数部分&#xff0c;例如&#xff1a;truncate(8.345) 8 以及 truncate(-2…

【路径规划】(2) A* 算法求解最短路,附python完整代码

大家好&#xff0c;今天和各位分享一下机器人路径规划中非常经典的 A* 算法&#xff0c;感兴趣的点个关注&#xff0c;文末有 python 代码&#xff0c;那我么开始吧。 1. 算法介绍 A* 算法是 1968 年 P.E.Hart[1]等人所提出的在全局地图环境中所有已知情形下求解最短路径问题的…

部分gcc预定义宏和函数栈帧的内存分布

本文简单基于树莓派8&#xff0c;linux4.4.50版本&#xff0c;32位arm cpu 尝试了解函数调用栈的内存分布的形态。使用gcc内置的宏 __builtin_frame_address 来打印栈帧内存上的信息&#xff0c;以及了解一下常用的gcc 内置的宏的输出。 针对 __builtin_frame_address 在gcc官网…

猴子也能学会的jQuery第十二期——jQuery遍历(上)

&#x1f4da;系列文章—目录&#x1f525; 猴子也能学会的jQuery第一期——什么是jQuery 猴子也能学会的jQuery第二期——引用jQuery 猴子也能学会的jQuery第三期——使用jQuery 猴子也能学会的jQuery第四期——jQuery选择器大全 猴子也能学会的jQuery第五期——jQuery样式操作…

PIC单片机3——外部中断

//RB2&#xff08;INT2&#xff09;作为外中断 #include <p18cxxx.h>/*18F系列单片机头文件*/ void PIC18F_High_isr(void);/*中断服务函数声明*/ void PIC18F_Low_isr(void); #pragma code high_vector_section0x8 /*高优先级中断响应时&#xff0c;会自动跳转到0x8处…

基于三相坐标系状态方程的感应电动机起动动态计算matlab程序

基于三相坐标系状态方程的感应电动机起动动态计算matlab程序 1 异步电动机动态数学模型的性质 电磁耦合是机电能量转换的必要条件&#xff0c;电流与磁通的乘积产生转矩&#xff0c;转速与磁通的乘积得到感应电动势。无论是直流电动机&#xff0c;还是交流电动机均如此。 交、直…

二十七、CANdelaStudio深入-编辑技巧(一致性检查)

本专栏将由浅入深的展开诊断实际开发与测试的数据库编辑,包含大量实际开发过程中的步骤、使用技巧与少量对Autosar标准的解读。希望能对大家有所帮助,与大家共同成长,早日成为一名车载诊断、通信全栈工程师。 本文介绍CANdelaStudio软件的一致性检查,欢迎各位朋友订阅、评论…

『LeetCode|每日一题』---->最小路径和

目录 1.每日一句 2.作者简介 『LeetCode|每日一题』最小路径和 1.每日一题 2.解题思路 2.1 思路分析 2.2 核心代码 2.3 完整代码 2.4 运行结果 1.每日一句 希望冬天的风能吹散一年里所有的遗憾 2.作者简介 &#x1f3e1;个人主页&#xff1a;XiaoXiaoChen-2716 &#x1f…

Vue3框架中CompositionAPI的基本使用(第十课)

1.Setup函数 理解&#xff1a;Vue3.0中一个新的配置项&#xff0c;值为一个函数。 setup是所有Composition API&#xff08;组合API&#xff09;“ 表演的舞台 ”。 组件中所用到的&#xff1a;数据、方法等等&#xff0c;均要配置在setup中。 setup函数的两种返回值&#x…

kubernetes工作负载之控制器

目录 ​一、概述 二、Deployment 控制器 2.1Deployment 部署应用 2.2Deployment滚动升级 2.2.1应用部署完成 2.2.2更新镜像三种方式 2.3 Deployment 发布失败回滚 2.4Deployment 水平扩容 三、DaemonSet控制器 四、Job控制器 4.1Job一次性执行 4.2定时任务&#xf…

查询:按A分组,满足B时对应的C

1.场景 这种问题我自己归纳为“找对应行”问题&#xff0c;例如有下面一场表&#xff08;学生做题&#xff0c;对每个知识点的得分情况&#xff09; 字段&#xff1a;主键id、user_id、score、is_study、knowledgeName、updateTime场景1&#xff1a;按用户分组&#xff0c;求…

Nginx (7):nginx高可用配置

所谓的高可用&#xff0c;就是虽然nginx可以反向代理&#xff0c;如果某个内部服务器down了&#xff0c;可以使用其他的内部服务器&#xff0c;然而万一nginx挂了呢&#xff1f;&#xff1f;&#xff1f;&#xff1f;布置多个nginx再反向代理nginx&#xff1f;&#xff1f;反向…

数据结构学习笔记(V):树与二叉树

目录 1 树 1.1 树的定义和基本术语 1.定义 2.基本术语 1.2 树的性质 2 二叉树 2.1 二叉树的定义和基本术语 1.定义 2.特殊二叉树 2.2 二叉树性质 2.3 二叉树存储结构 1.顺序存储 2.链式存储 3 二叉树进阶 3.1 二叉树顺序遍历 1.先序遍历 2.中序遍历 3.后序遍…