WPF MVVM Community Toolkit. Mvvm 社区框架

Community Toolkit. Mvvm 社区框架

微软官方文档

主要内容:CommunityToolkit.Mvvm 框架

  1. 概念,安装,使用(重要API:ObservableObject,RelayCommand)
  2. 源生成器([ObservableProperty],[RelayCommand],[INotifyPropertyChanged])
  3. 命令详解(异步命令:AsyncRelayCommand 和 AsyncRelayCommand)
  4. 依赖注入(IServiceProvider,ServiceCollection)
  5. 信使(IMessenger,WeakReferenceMessenger、StrongReferenceMessenger 等)

ViewModel 支持通知:继承 ObservableObject

属性支持通知:SetProperty(ref name, value) 或 OnPropertyChanged( )

创建命令:直接使用类型 RelayCommand


核心功能

Community Toolkit. Mvvm 内置源生成器可以使用特性生成代码

1. [INotifyPropertyChanged] 和 [ObservableProperty]

ObservableObject 是 ViewModel 的基类,支持 INotifyPropertyChanged INotifyPropertyChanged

要 .Net 8.0 版本以后才可以使用源生成器这种语法

 [INotifyPropertyChanged] //相当于 类继承 :ObservableObject
public partial class PersonViewModel  //🔴!需要 partial 部分类
{[ObservableProperty] //[ObservableProperty] 会生成一个: 基于 name的字段的属性Nameprivate string name; //🔴调用时,要调:Name 大写
//  相当于: public string Name
//         {
//             get => name;
//             set => SetProperty(ref name, value);
//         }[ObservableProperty]private int age;
}

这段代码会自动生成 NameAge 的属性,同时自动实现 INotifyPropertyChanged 的通知。

  1. SetProperty(ref name, value):支持通知

    • SetProperty(ref name, value) //通知的方法,相当于: //   ⬇⬇⬇ 相当于if (_name != value)
      {_name = value;OnPropertyChanged(); // 或 OnPropertyChanged(nameof(Name));
      }
      

2. RelayCommand 命令

你可以轻松创建命令而不需要手动实现 ICommand 接口。

using CommunityToolkit.Mvvm.Input;[INotifyPropertyChanged]
public partial class PersonViewModel //🔴需要 partial 部分类
{1️⃣//同步命令[RelayCommand] //在编译时会自动在方法后面加:Commandprivate void SayHello() //🔴调用时要调用:SayHelloCommand{MessageBox.Show("Hello!");}2️⃣//异步命令[RelayCommand]private async Task GreetUserAsync() //🔴编译时会变成:GreetUserCommand{await Task.Delay(3000);MessageBox.Show($"Hello 源生器!");}3️⃣//带 CanExecute 命令[RelayCommand(CanExecute = nameof(CanGreetUser))] //加入 CanExecute 判断执行private void SayHello() {MessageBox.Show("Hello!");}private bool CanGreetUser(User? user){return user is not null;}}

使用 [RelayCommand] 会自动生成一个 SayHelloCommand 属性,供 XAML 使用。

异步方法会 去掉 Async ,添加 Command ,变成 SayHelloCommand


3. ObservableValidator 验证

如果你需要验证输入数据(比如表单),可以继承 ObservableValidator

public partial class FormViewModel : ObservableValidator
{[ObservableProperty][Required][MinLength(3)]private string name;
}

XAML 示例绑定命令

<Button Content="Say Hello" Command="{Binding SayHelloCommand}" />
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />

信使

“信使”,在 MVVM 架构中指的是 Messenger(消息通信机制),用于实现 ViewModel 之间的解耦通信。

MVVM 工具包提供两种现用的实现:

  1. WeakReferenceMessenger
    • 在内部使用弱引用,为收件人提供自动内存管理
  2. StrongReferenceMessenger
    • 使用强引用,并要求开发人员在不再需要收件人时手动取消订阅收件人

使用场景

  • 不同 ViewModel 之间发送和接收消息
  • 让 ViewModel 通知其他模块某件事发生了
  • 解耦:不通过事件或引用直接交互

示例:发送与接收消息

1. 创建消息类(推荐继承 ValueChangedMessage<T>
public class NameChangedMessage : ValueChangedMessage<string>
{public NameChangedMessage(string value) : base(value) { }
}

2. 接收消息的 ViewModel
public class ReceiverViewModel : ObservableRecipient
{public ReceiverViewModel(){// 必须开启 IsActive 才能自动注册接收器IsActive = true;1️⃣ Messenger.Register<ReceiverViewModel, NameChangedMessage>(this, (r, m) =>{ // 处理消息//在这里处理消息,r为接收方,m为发送方//输入消息。使用传递的接收者作为输入使得// lambda表达式不会捕获“this”,从而提高性能。Console.WriteLine($"接收到新名字:{m.Value}");});}
}2️⃣ //不继承ObservableRecipient 的方案
WeakReferenceMessenger.Default.Register<ReceiverViewModel>(this, (r, m) =>
{});

Messenger 基类自带的属性,

不继承 ObservableRecipient 也可以直接用 WeakReferenceMessenger.Default.Register


3. 发送消息的 ViewModel
public class SenderViewModel
{public void SendMessage(){WeakReferenceMessenger.Default.Send(new NameChangedMessage("新名字张三"));}
}

使用 DI 注入 IMessenger(可选)

你也可以将 Messenger 注入进来,而不是使用静态默认实例:

public class SenderViewModel
{private readonly IMessenger _messenger;public SenderViewModel(IMessenger messenger){_messenger = messenger;}public void SendMessage(){_messenger.Send(new NameChangedMessage("李四"));}
}

App.xaml.cs 中注册:

services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default);

注销收件人

当不再需要收件人时,应将其注销,以便停止接收消息。 可以按消息类型、注册令牌或收件人取消注册:

//取消对消息类型的接收者的注册
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage>(this);//取消指定通道中消息类型的接收者的注册
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage, int>(this, 42);//取消所有通道中所有消息的接收方注册
WeakReferenceMessenger.Default.UnregisterAll(this);
说明
ObservableRecipient让 ViewModel 支持消息监听,需设置 IsActive = true
WeakReferenceMessenger默认静态全局信使,可直接用
Register<TMessage>注册监听消息
Send<TMessage>发送消息

总结:

功能实现方式
ViewModel 通信使用 Messenger.RegisterSend
解耦 ViewModel不直接引用其他 ViewModel 或服务
支持 DI可注入 IMessenger



IServiceProvider 服务提供者接口

IServiceProvider:服务提供者接口

在 WPF + MVVM 应用中,IServiceProvider 常用于实现 依赖注入(DI),尤其是在使用 CommunityToolkit.Mvvm 配合 .NET Core/5+/6+ 的 WPF 应用时,这个接口是核心组成之一。

什么是 IServiceProvider:

  1. IServiceProvider服务器提供者接口,主要给VM提供各种服务(数据库操作服务,日志服务,定位服务等)
  2. IServiceProvider接口中只有一个GetService()方法,用来获取服务
  3. 它通常与 Microsoft.Extensions.DependencyInjection 一起使用,用于解析依赖项,比如 ViewModel、服务类等。

使用示例

需要安装 Microsoft.Extensions.DependencyInjection

后引用:using Microsoft.Extensions.DependencyInjection;

通用依赖注入容器库,可以:

  • 注册服务:services.AddSingleton<T>() / AddTransient<T>()
  • 构建服务提供器:services.BuildServiceProvider()
  • 获取服务:provider.GetRequiredService<T>()
  • 与 MVVM 模式完美配合
1. 注册依赖(Startup)

你需要在 App.xaml.cs 中设置依赖注入容器:

using Microsoft.Extensions.DependencyInjection;public partial class App : Application
{public App(){Services = ConfigureServices();this.InitializeComponent();  }//获取当前应用程序实例并转换为App类型//在全项目范围通过 App.Current 安全访问自定义的 App 对象public new static App Current => (App)Application.Current; public IServiceProvider Services { get; } //用来接收 服务提供器 的字段// 配置服务public static IServiceProvider ConfigureServices(){//1️⃣ 定义一个服务集合,服务集合编译之后,产生一个ServiceProvider// 而ServiceProvider类型实现了IServiceProvider接口var services = new ServiceCollection();//2️⃣ 注册 ViewModel 和服务// services.AddTransient<MainWindowViewModel>();// Transient 瞬间模式;// Singleton 单例模式services.AddSingleton<MainWindowViewModel>();services.AddSingleton<AnimalWindowViewModel>();// 注册服务//services.AddSingleton<接口, 实现类>();//AddSingleton 使用时会自动创建一个唯一的实现实例,注入后直接调用接口就可以使用其实现类实现的方法services.AddSingleton<IMyService, MyService>();services.AddSingleton<IPerson<Person,ViewPerson>, PersonService>();services.AddSingleton<IAnimal<Animal,ViewAnimal>, AnimalService>();//3️⃣ 构建服务提供器 services.BuildServiceProvider() 并返回return services.BuildServiceProvider();}
}
  1. 你注册的时候:

    //这表示 MainViewModel 需要的服务(IMyService)会自动注入。
    services.AddSingleton<IMyService, MyService>();
    services.AddSingleton<MainViewModel>();
    
  2. 在上面代码中:

    //是 WPF 应用中为了更方便地获取当前应用程序实例的一种做法。
    public new static App Current => (App)Application.Current;
    
    • Application.Current 是一个静态属性,返回当前运行的 WPF 应用实例,其类型Application

    • 你自定义的 App 类一般是继承自 Application

    • 为了能够在程序其他地方方便地访问你自定义的 App 类型,而不是基类 Application,你需要做一次类型转换。


2. 如:在构造函数中使用依赖注入

比如在 MainViewModel 中注入一个服务:

public class MainViewModel : ObservableObject
{private readonly IMyService _service;public MainViewModel(IMyService service){_service = service;}
}

3. 获取服务

你可以随时通过 App.Services.GetService() 获取已注册的对象:

var vm = App.Services.GetRequiredService<MainViewModel>();
//1. 如: App.xaml.cs 中启动主窗口
var mainWindow = Services.GetRequiredService<MainWindow>();
mainWindow.Show();
//2. 如:View 构造函数中注入 ViewModel
public partial class MainWindow : Window
{public MainWindow(MainViewModel viewModel){InitializeComponent();DataContext = viewModel;}
}
//3. ViewModel 构造函数中注入服务
public class MainViewModel : ObservableObject
{private readonly IMyService _myService;public MainViewModel(IMyService myService){_myService = myService;}// 使用 _myService 来执行操作
}
//4. 绑定上下文// MainWindowViewModel视图模型和MainWindow窗体耦合了。
//this.DataContext = new MainWindowViewModel();// 依赖注入,但没有完全解耦。
//this.DataContext = App.Current.Services.GetService(typeof(MainWindowViewModel));// 完全解耦了
Type type = Type.GetType("_1.CommunityToolkit.Mvvm基本使用.ViewModels.MainWindowViewModel");
this.DataContext = App.Current.Services.GetService(type);
//this.DataContext = new MainWindowViewModel(new PersonService(), new AnimalService());

搭配 MVVM Toolkit 的建议:

虽然 CommunityToolkit.Mvvm 本身没有内建 DI 容器,但它 完全支持 DI 的设计理念。只要你配合 .NET Generic HostMicrosoft.Extensions.DependencyInjection,就能构建清晰的 MVVM 架构。

  • 注册 IServiceProvider
  • 自动注入 ViewModel
  • 使用 RelayCommand 和 ObservableProperty



📁 附:推荐的文件夹命名约定

推荐的文件夹命名结构(基于 MVVM 模式):

MyWpfApp/
├── Models/              # 业务模型、数据结构类
├── ViewModels/          # 视图模型(含 ObservableObject / RelayCommand 等)
├── Views/               # XAML 视图文件(*.xaml + *.xaml.cs)
├── Services/            # 服务类(如导航、API 调用、本地存储等)
├── Converters/          # IValueConverter 实现
├── Behaviors/           # 附加行为(例如交互逻辑)
├── Helpers/             # 帮助类、扩展方法
├── Resources/           # 资源文件(样式、模板、图片等)
└── App.xaml             # 应用入口

各文件夹用途说明:

文件夹用途说明
Models定义实体类、DTO、业务模型,如 User.csOrder.cs
ViewModels每个 View 对应一个 ViewModel,例如 MainViewModel.cs
Views存放 XAML 视图文件,如 MainView.xamlMainView.xaml.cs
Services例如 INavigationService, IDataService
Converters转换器类,如 BoolToVisibilityConverter.cs
Behaviors附加行为(Blend SDK 或自定义)
Helpers静态辅助类、扩展方法
Resources包括 Styles.xaml, Themes, 图片等资源

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

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

相关文章

Mcu_Bsdiff_Upgrade

系统架构 概述 MCU BSDiff 升级系统通过使用二进制差分技术&#xff0c;提供了一种在资源受限的微控制器上进行高效固件更新的机制。系统不传输和存储完整的固件映像&#xff0c;而是只处理固件版本之间的差异&#xff0c;从而显著缩小更新包并降低带宽要求。 该架构遵循一个…

vscode连接WSL卡住

原因&#xff1a;打开防火墙 解决&#xff1a; 使用sudo ufw disable关闭防火墙

FreeSWITCH rtcp-mux 测试

rtcp 跟 rtp 占用同一个端口&#xff0c;这就是 rtcp 复用 Fs 呼出是这样的&#xff1a; originate [rtcp_muxtrue][rtcp_audio_interval_msec5000]user/1001 &echo 需要同时指定 rtcp_audio_interval_msec&#xff0c;否则 rtcp_mux 不能生效 Fs 呼入不需要配置&#xf…

CI/CD的演进之路

CI/CD的演进之路 一、CI/CD的成长演变 早期起源与初步实践&#xff1a;CI/CD的概念可以追溯到软件开发的早期阶段&#xff0c;但真正开始受到关注是在敏捷开发方法兴起之后。在传统的瀑布模型开发模式下&#xff0c;软件开发周期长、发布频率低&#xff0c;更新往往需要数月甚…

Docker 镜像打包到本地

保存镜像 使用 docker save 命令将镜像保存为一个 tar 文件。命令格式如下&#xff1a; docker save [options] IMAGE [IMAGE...]示例&#xff1a;docker save -o centos.tar centos:latest--output 或 -o&#xff1a;将输出保存到指定的文件中。 加载镜像 如果需要在其他机器…

在 Excel xll 自动注册操作 中使用东方仙盟软件2————仙盟创梦IDE

// 获取当前工作表名称string sheetName (string)XlCall.Excel(XlCall.xlfGetDocument, 7);// 构造动态名称&#xff08;例如&#xff1a;Sheet1!MyNamedCell&#xff09;string fullName $"{sheetName}!MyNamedCell";// 获取引用并设置值var namedRange (ExcelRe…

云计算与大数据进阶 | 28、存储系统如何突破容量天花板?可扩展架构的核心技术与实践—— 分布式、弹性扩展、高可用的底层逻辑(下)

在上篇中&#xff0c;我们围绕存储系统可扩展架构详细探讨了基础技术原理与典型实践。然而&#xff0c;在实际应用场景中&#xff0c;存储系统面临的挑战远不止于此。随着数据规模呈指数级增长&#xff0c;业务需求日益复杂多变&#xff0c;存储系统还需不断优化升级&#xff0…

从0到1打造AI Copilot:用SpringBoot + ChatGPT API实现智能开发助手

本文将从0到1系统性地讲解如何基于SpringBoot与OpenAI ChatGPT API打造一款智能开发助手&#xff08;AI Copilot&#xff09;。文章首先介绍AI Copilot的背景与价值&#xff0c;接着深入架构设计与环境准备&#xff0c;然后通过详尽的代码示例演示SpringBoot项目的搭建、依赖配…

C# AI(Trae工具+claude3.5-sonnet) 写前后端

这是一个AI 写的前后端分离项目,通过AI编程&#xff0c;开发电商管理系统&#xff08;登陆、注册&#xff09; 使用的AI工具为 Trae工具(字节国际版)claude3.5-sonnet(目前代码最强模型) 前端为 vue3Bootstrap 后端为 C# net5.0(因为我电脑里面已经安装了这个新版更好) do…

Vue3 Element Plus 对话框加载实现

在 Vue3 Element Plus 中实现对话框加载效果&#xff0c;可以通过以下两种方式实现&#xff1a; 方式一&#xff1a;使用 v-loading 指令&#xff08;推荐&#xff09; vue 复制 下载 <template><el-button click"openDialog">打开对话框</el-b…

VsCode开发环境之Node.js离线部署

1.下载node部署文件 地址为&#xff1a;CNPM Binaries Mirror 2.下载后解压 3.验证版本 4.配置环境变量 5.外网寻找一个对应项目的npm文件--node_modules 6.node_modules文件夹复制到node.js的路径下 7.接着就可以正常运行了。

MySQL中的重要常见知识点(入门到入土!)

基础篇 基础语法 添加数据 -- 完整语法 INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...);-- 示例 insert into employee(id,workno,name,gender,age,idcard,entrydate) values(1,1,Itcast,男,10,123456789012345678,2000-01-01) 修改数据 -- 完整语法 UPDA…

【PRB】1.5w字深度解析GaN中最浅的受主缺陷

2025 年 1 月 16 日,Virginia Commonwealth University 的 M. A. Reshchikov 和 SUNY–Albany 的 B. McEwen 等人在《Physical Review B》期刊发表了题为《Identity of the shallowest acceptor in GaN》的文章,基于对 50 多个 Be 掺杂 GaN 样品的光致发光实验以及 Heyd-Scus…

前端开发遇到 Bug,怎么办?如何利用 AI 高效解决问题

前端开发遇到 Bug&#xff0c;怎么办&#xff1f;如何利用 AI 高效解决问题 作为前端开发者&#xff0c;遇到 Bug 几乎是日常。无论是样式错乱、功能异常&#xff0c;还是接口数据不对&#xff0c;Bug 总能让人头疼。但随着人工智能&#xff08;AI&#xff09;技术的发展&…

深挖navigator.webdriver浏览器自动化检测的底层分析

本文将带你深入探索并实践如何从底层层面破解浏览器 navigator.webdriver 检测&#xff0c;结合爬虫代理等策略伪装、多线程加速等技术&#xff0c;在豆瓣图书搜索页面上批量采集图书评分、简介、作者等信息。文章面向初学者&#xff0c;采用分步教程型结构&#xff0c;并增设「…

如何实现从网页一键启动你的 Electron 桌面应用(zxjapp://)

在现代桌面应用开发中&#xff0c;Electron 凭借其跨平台能力和前端友好的特性&#xff0c;受到了越来越多开发者的青睐。但你是否想过&#xff0c;如何让用户从网页上一键启动你本地的 Electron 应用&#xff1f;比如像某些云盘客户端那样&#xff0c;点击网页上的按钮就能直接…

Java安全-Servlet内存马

内存马简介 内存马是指将恶意代码注入到内存中&#xff0c;达到无文件落地的效果&#xff0c;使得被攻击方难以察觉。由于是无文件的形式&#xff0c;可以绕过部分基于文件检测的杀软。而 Servlet 内存马是基于 Java Servlet 技术&#xff0c;动态将恶意代码注入到 Tomcat 内存…

LeetCode-前缀和-和为K的子数组

LeetCode-前缀和-和为K的子数组 ✏️ 关于专栏&#xff1a;专栏用于记录 prepare for the coding test。 文章目录 LeetCode-前缀和-和为K的子数组&#x1f4dd; 和为K的子数组&#x1f3af;题目描述&#x1f50d; 输入输出示例&#x1f9e9;题目提示&#x1f9ea;前缀和❓什么…

动态神经网络(Dynamic NN)在边缘设备的算力分配策略:MoE架构实战分析

一、边缘计算场景的算力困境 在NVIDIA Jetson Orin NX&#xff08;64TOPS INT8&#xff09;平台上部署视频分析任务时&#xff0c;开发者面临三重挑战&#xff1a; 动态负载波动 视频流分辨率从480p到4K实时变化&#xff0c;帧率波动范围20-60FPS 能效约束 设备功耗需控制在1…

算法优选系列(9.BFS 解决拓扑排序)

目录 拓扑排序简介&#xff1a; ​编辑 课程表&#xff08;medium&#xff09;&#xff1a; 课程表II&#xff08;medium&#xff09;: 火星词典&#xff08;hard&#xff09;&#xff1a; 拓扑排序简介&#xff1a; 有向无环图&#xff08;DAG图&#xff09; 如上图每条边…