参考文档: Introduction to the MVVM Toolkit - Community Toolkits for .NET | Microsoft Learn
它是一个现代化,快速和模块化的MVVM库, 对应用程序的结构或编译规范没有严格的限制。
NuGet安装包
搜索:CommunityToolkit.Mvvm
导入
using CommunityToolkit.Mvvm;
使用
ObservableObject
public abstract class ObservableObject :
INotifyPropertyChanged,
INotifyPropertyChanging{
}
它实现了两个接口,它可以作为需要支持属性更改通知的所有类的基类,主要有以下功能:
- 实现
INotifyPropertyChanged 和 INotifyPropertyChanging
公开了PropertyChanged 和 PropertyChanging事件 - 提供了一系列的SetProperty方法可以很容易的用来设置属性值,并自动引发相应的事件
- 它提供了SetPropertyAndNotifyOnCompletion方法,该方法类似于SetProperty,但是能够设置Task属性,并在分配的任务完成时自动引发通知事件
- 公开了OnPropertyChanged和OnPropertyChanging方法,这些方法可以在派生类型中重写,以自定义引发通知事件的方式
简单属性
class MainWindowViewModel : ObservableObject
{
{
get { return name; }
set => SetProperty(ref name, value);
}
}
protected bool SetProperty<T>([NotNullIfNotNull("newValue")] ref T field, T newValue, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return false;
}
OnPropertyChanging(propertyName);
field = newValue;
OnPropertyChanged(propertyName);
return true;
}
SetProperty方法内部,设置属性值,并触发两个事件。这两个事件除了给WPF内部框架使用外,我们也可以监听这些事件实现自己的功能。
public partial class MainWindow : Window
{
public MainWindow()
{
var mainWindowViewModel = new MainWindowViewModel();
mainWindowViewModel.PropertyChanged += MainWindowViewModel_PropertyChanged;
mainWindowViewModel.PropertyChanging += MainWindowViewModel_PropertyChanging;
DataContext = mainWindowViewModel;
InitializeComponent();
}
private void MainWindowViewModel_PropertyChanging(object? sender, System.ComponentModel.PropertyChangingEventArgs e)
{
Console.WriteLine(e.PropertyName + " changing");
}
private void MainWindowViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Console.WriteLine(e.PropertyName + " changed");
}
}
包装Model对象
Model对象一般从数据库或其它地方获取,不会继承ObservableObject
public class User
{
public String Name { get; set; } = "";
}
class MainWindowViewModel : ObservableObject
{
public MainWindowViewModel(User user)
{
this.user = user;
}
public string Name
{
get => user.Name;
set => SetProperty(user.Name, value, user, (u, n) => u.Name = n);
}
}
使用特性自动生成代码
internal partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
String title = "hello";
}
必须是partial class, 因为在后台会生成代码对 title 属性进行包装
partial class MainWindowViewModel
{
public string Title
{
get => title;
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("title")]
set
{
if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(title, value))
{
OnTitleChanging(value);
OnTitleChanging(default, value);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Title);
title = value;
OnTitleChanged(value);
OnTitleChanged(default, value);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Title);
}
}
}
}
...
partial void OnTitleChanging(string value);
...
关于 partial 函数的说明:partial method - C# Reference - C# | Microsoft Learn
在一个 partial class 代码中声明函数partial void OnTitleChanging(string value);
在另一个partial class 代码中实现该函数,如果没有实现,则在编译时将会删除相关的声明和调用。
RelayCommand
命令转播,将方法或委托转换成命令提供给View, 主要有以下功能:
- 实现了 ICommand 接口
- 实现 IRelayCommand(和 IRelayCommand<T>)接口, 暴露了NotifyCanExecuteChanged方法用于触发
CanExecuteChanged
事件 - 它们公开了接受Action和Func<T>等委托的构造函数,这些委托允许包装标准方法和lambda表达式
internal partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
int age = 18;
public RelayCommand<String> CommandAddAge => new RelayCommand<string>((v) =>
{
Age += int.Parse(v);
});
}
<Button Width="120" Height="48" Margin="0 300 0 0"
Command="{Binding CommandAddAge}"
CommandParameter="5"
>AddAge</Button>
IOC
工作原理:先将一个类型或对象注册到一指定容器中,在需要时可以通过这个容器获取(或构造对象)。
需要安装Microsoft.Extensions.DependencyInjection, 详细信息请看Dependency injection in ASP.NET Core | Microsoft Learn
配置和注册类型
public class User
{
public String Name { get; set; } = "";
}
internal partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
String title = "hello";
private readonly User user;
public MainWindowViewModel(User user)
{
this.user = user;
}
public string Name
{
get => user.Name;
set => SetProperty(user.Name, value, user, (u, n) => u.Name = n);
}
[ObservableProperty]
int age = 18;
public RelayCommand<String> CommandAddAge => new RelayCommand<string>((v) =>
{
Age += int.Parse(v);
});
}
public partial class App : Application
{
public IServiceProvider Services { get; }
public new static App Current => (App)Application.Current;
public App()
{
Services = ConfigureServices();
this.InitializeComponent();
}
private IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddTransient<User>();
services.AddTransient<MainWindowViewModel>();
return services.BuildServiceProvider();
}
}
// 通过Services 获取对象
var mainWindowViewModel = App.Current.Services.GetService<MainWindowViewModel>();
Messenger
IMessenger 是两个不同对象间传递消息的接口。实现IMessenger的类型负责维护接收者(消息接收者)与其注册的消息类型之间的链接,以及相关的消息处理程序。
有两种方法注册消息:
- IReceiver <TMessage>
- MessageHandler< receiver, TMessage>
发送和接收消息
public class LoggedInUserChangedMessage : ValueChangedMessage<User>
{
public LoggedInUserChangedMessage(User user) : base(user)
{
}
}
// 注册
public MainWindowViewModel(User user)
{
this.user = user;
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this, (r, m) =>
{
Console.WriteLine(r.GetType()); // 接收器,也就是this
Console.WriteLine(m.GetType()); // 消息
});
}
// 发送
private void Button_Click(object sender, RoutedEventArgs e)
{
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(new User()));
}
请求消息
// Create a message
public class LoggedInUserRequestMessage : RequestMessage<User>
{
}
// Register the receiver in a module
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
{
// Assume that "CurrentUser" is a private member in our viewmodel.
// As before, we're accessing it through the recipient passed as
// input to the handler, to avoid capturing "this" in the delegate.
m.Reply(r.CurrentUser);
});
// Request the value from another module
User user = WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();