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的字段的属性Name
    private 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] //在编译时会自动在方法后面加: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

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, 图片等资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值