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 版本以后才可以使用源生成器这种语法

csharp 复制代码
 

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

    [ObservableProperty]
    private int age;
}

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

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

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

2. RelayCommand 命令

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

csharp 复制代码
using CommunityToolkit.Mvvm.Input;

[INotifyPropertyChanged]
public partial class PersonViewModel //🔴需要 partial 部分类
{
    1️⃣//同步命令
    [RelayCommand] //在编译时会自动在方法后面加:Command
    private 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:

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

XAML 示例绑定命令

xml 复制代码
<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>)
csharp 复制代码
public class NameChangedMessage : ValueChangedMessage<string>
{
  public NameChangedMessage(string value) : base(value) 
  { 
    
  }
}

2. 接收消息的 ViewModel
csharp 复制代码
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
csharp 复制代码
public class SenderViewModel
{
    public void SendMessage()
    {
        WeakReferenceMessenger.Default.Send(new NameChangedMessage("新名字张三"));
    }
}

使用 DI 注入 IMessenger(可选)

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

csharp 复制代码
public class SenderViewModel
{
    private readonly IMessenger _messenger;

    public SenderViewModel(IMessenger messenger)
    {
        _messenger = messenger;
    }

    public void SendMessage()
    {
        _messenger.Send(new NameChangedMessage("李四"));
    }
}

在 App.xaml.cs 中注册:

csharp 复制代码
services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default);

注销收件人

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

csharp 复制代码
//取消对消息类型的接收者的注册
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.Register 和 Send
解耦 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 中设置依赖注入容器:

csharp 复制代码
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. 你注册的时候:

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

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

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

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


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

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

csharp 复制代码
public class MainViewModel : ObservableObject
{
    private readonly IMyService _service;

    public MainViewModel(IMyService service)
    {
        _service = service;
    }
}

3. 获取服务

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

csharp 复制代码
var vm = App.Services.GetRequiredService<MainViewModel>();
csharp 复制代码
//1. 如: App.xaml.cs 中启动主窗口
var mainWindow = Services.GetRequiredService<MainWindow>();
mainWindow.Show();
csharp 复制代码
//2. 如:View 构造函数中注入 ViewModel
public partial class MainWindow : Window
{
    public MainWindow(MainViewModel viewModel)
    {
        InitializeComponent();
        DataContext = viewModel;
    }
}
csharp 复制代码
//3. ViewModel 构造函数中注入服务
public class MainViewModel : ObservableObject
{
    private readonly IMyService _myService;

    public MainViewModel(IMyService myService)
    {
        _myService = myService;
    }

    // 使用 _myService 来执行操作
}
csharp 复制代码
//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 Host 或 Microsoft.Extensions.DependencyInjection,就能构建清晰的 MVVM 架构。

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

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

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

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

各文件夹用途说明:

文件夹 用途说明
Models 定义实体类、DTO、业务模型,如 User.cs、Order.cs 等
ViewModels 每个 View 对应一个 ViewModel,例如 MainViewModel.cs
Views 存放 XAML 视图文件,如 MainView.xaml 和 MainView.xaml.cs
Services 例如 INavigationService, IDataService 等
Converters 转换器类,如 BoolToVisibilityConverter.cs
Behaviors 附加行为(Blend SDK 或自定义)
Helpers 静态辅助类、扩展方法
Resources 包括 Styles.xaml, Themes, 图片等资源
相关推荐
光泽雨7 小时前
WPF中的依赖属性
wpf
海马7 小时前
Spring Cloud 开发知识整理
spring·spring cloud·wpf
闻哥8 小时前
分布式任务调度框架:XXL-Job / ElasticJob / DolphinScheduler / SchedulerX 架构对比与选型
分布式·架构·wpf
预立科技20 小时前
CDN(内容分发网络)知识总结
网络·wpf·cdn
FuckPatience2 天前
WPF DataGrid 行根据某个属性循环变色动画
wpf
一条闲鱼_mytube4 天前
高并发短链接系统设计
wpf
一个天蝎座 白勺 程序猿5 天前
IoTDB集群扩容:从慌乱到从容的经验分享
数据库·wpf·时序数据库·iotdb
samble6 天前
从零搭建灌装监控系统(十三):报警系统设计,生命周期与通知
c#·wpf·mvvm·modbus·工业控制
samble6 天前
从零搭建灌装监控系统(十二):配置系统,原子写入与容错
c#·wpf·mvvm·modbus·工业控制
蜗牛互联网9 天前
OLMo-core 3的token gerrymandering提醒:MoE路由要按时间窗验收
java·人工智能·wpf