初步了解MVVMToolkit可以访问MVVMToolkit入门教程。
ObservableObject
简单用法
实现INotifyPropertyChanged
和INotifyPropertyChanging
接口
使用方法SetProperty<T>(ref T, T, string)
1 2 3 4 5 6 7 8 9 10
| public class User : ObservableObject { private string name;
public string Name { get => name; set => SetProperty(ref name, value); } }
|
包装不可观测模型
如果想要没有实现INotifyPropertyChanged接口的实例属性进行通知,可以使用SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)
1 2 3 4 5 6 7 8 9 10 11 12
| public class ObservableUser : ObservableObject { private readonly User user;
public ObservableUser(User user) => this.user = user;
public string Name { get => user.Name; set => SetProperty(user.Name, value, user, (u, n) => u.Name = n); } }
|
以上案例的User没有实现INotifyPropertyChanged接口,但仍然可以引发属性更改通知。
Task属性
如果属性是一个Task属性,而且要在任务完成后引发通知,使用SetPropertyAndNotifyOnCompletion<T>(ref TaskNotifier<T>, Task<T>, string)
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class MyModel : ObservableObject { private TaskNotifier<int>? requestTask;
public Task<int>? RequestTask { get => requestTask; set => SetPropertyAndNotifyOnCompletion(ref requestTask, value); } public void RequestValue() { RequestTask = WebService.LoadMyValueAsync(); } }
|
ObservableValidator
简单使用
SetProperty<T>(ref T, T, bool, string)
其中的bool值表示希望在属性值更新的时候验证属性。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class RegistrationForm : ObservableValidator { private string name;
[Required] [MinLength(2)] [MaxLength(100)] public string Name { get => name; set => SetProperty(ref name, value, true); } }
|
自定义验证方法
使用[CustomValidationAttribute]
增加自定义验证方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class RegistrationForm : ObservableValidator { private readonly IFancyService service;
public RegistrationForm(IFancyService service) { this.service = service; }
private string name;
[Required] [MinLength(2)] [MaxLength(100)] [CustomValidation(typeof(RegistrationForm), nameof(ValidateName))] public string Name { get => this.name; set => SetProperty(ref this.name, value, true); }
public static ValidationResult ValidateName(string name, ValidationContext context) { RegistrationForm instance = (RegistrationForm)context.ObjectInstance; bool isValid = instance.service.Validate(name);
if (isValid) { return ValidationResult.Success; }
return new("The name was not validated by the fancy service"); } }
|
自定义验证属性
可以自定义一个attribute,将验证逻辑卸载IsValid方法中
自定义验证属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public sealed class GreaterThanAttribute : ValidationAttribute { public GreaterThanAttribute(string propertyName) { PropertyName = propertyName; }
public string PropertyName { get; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext) { object instance = validationContext.ObjectInstance, otherValue = instance.GetType().GetProperty(PropertyName).GetValue(instance);
if (((IComparable)value).CompareTo(otherValue) > 0) { return ValidationResult.Success; }
return new("The current value is smaller than the other one"); } }
|
使用自定义属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class ComparableModel : ObservableValidator { private int a;
[Range(10, 100)] [GreaterThan(nameof(B))] public int A { get => this.a; set => SetProperty(ref this.a, value, true); }
private int b;
[Range(20, 80)] public int B { get => this.b; set { SetProperty(ref this.b, value, true); ValidateProperty(A, nameof(A)); } } }
|
ObservableRecipient
可作为消息接受者的可观察对象,首先看下文中消息Messenger
再来看该内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ReceiveViewModel rv = new(); rv.IsActive = true; SenderViewModel sv = new();
class ReceiveViewModel : ObservableRecipient,IRecipient<string> { public void Receive(string message) { message.Dump(); }
}
class SenderViewModel { public SenderViewModel() { WeakReferenceMessenger.Default.Send("hello"); } }
|
命令Command
RelayCommand
使用和基本的Command一致
AsyncRelayCommand
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class MyViewModel : ObservableObject { public MyViewModel() { DownloadTextCommand = new AsyncRelayCommand(DownloadText); }
public IAsyncRelayCommand DownloadTextCommand { get; }
private Task<string> DownloadText() { return WebService.LoadMyTextAsync(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <Page x:Class="MyApp.Views.MyPage" xmlns:viewModels="using:MyApp.ViewModels" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters"> <Page.DataContext> <viewModels:MyViewModel x:Name="ViewModel"/> </Page.DataContext>
<StackPanel Spacing="8" xml:space="default"> <TextBlock> <Run Text="任务状态:"/> <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask.Status, Mode=OneWay}"/> <LineBreak/> <Run Text="Result:"/> <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask, Converter={StaticResource TaskResultConverter}, Mode=OneWay}"/> </TextBlock> <Button Content="Click me!" Command="{x:Bind ViewModel.DownloadTextCommand}"/> <ProgressRing HorizontalAlignment="Left" IsActive="{x:Bind ViewModel.DownloadTextCommand.IsRunning, Mode=OneWay}"/> </StackPanel> </Page>
|
消息Messenger
消息具有StrongReferenceMessenger
和WeakReferenceMessenger
两种类型,WeakReferenceMessenger
可以自动取消消息订阅。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| SenderViewModel sv = new(); ReceiveViewModel rv = new(); sv.Number=10;
WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>("aaaa"));
class ReceiveViewModel { public ReceiveViewModel() {
WeakReferenceMessenger.Default.Register<ValueChangedMessage<string>>(this, Receive); WeakReferenceMessenger.Default.Register<PropertyChangedMessage<int>>(this, Receive); }
void Receive(object recipient, PropertyChangedMessage<int> message) { message.PropertyName.Dump(); }
void Receive(object recipient, ValueChangedMessage<string> message) { message.Value.Dump(); } }
class SenderViewModel:ObservableObject { int number; public int Number { get{return number;} set{ if(SetProperty(ref number,value)) { WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<int>(this,nameof(Number),default,value)); } } } }
|
IRecipient接口
用法和上面一样,只不过接口必须强制实现Receive方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| ReceiveViewModel rv = new(); SenderViewModel sv = new();
class ReceiveViewModel : IRecipient<string>,IRecipient<user> { public ReceiveViewModel() { WeakReferenceMessenger.Default.Register<user>(this); WeakReferenceMessenger.Default.Register<string>(this); } public void Receive(string message) { message.Dump(); }
public void Receive(user message) { message.name.Dump(); } }
class SenderViewModel { public SenderViewModel() { WeakReferenceMessenger.Default.Send("dd"); WeakReferenceMessenger.Default.Send(new user("小明")); } } record user(string name);
|
RequestMessage
用于发送者向接受者请求信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ReceiveViewModel rv = new(); SenderViewModel sv = new();
class ReceiveViewModel : IRecipient<RequestMessage<string>> { public ReceiveViewModel() { WeakReferenceMessenger.Default.Register(this); }
public void Receive(RequestMessage<string> message) { message.Reply("hello"); } }
class SenderViewModel { public SenderViewModel() { var res = WeakReferenceMessenger.Default.Send(new RequestMessage<string>()); res.Response.Dump(); } }
|
AsyncRequestMessage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| ReceiveViewModel rv = new(); SenderViewModel sv = new();
class ReceiveViewModel : IRecipient<AsyncRequestMessage<string>> { public ReceiveViewModel() { WeakReferenceMessenger.Default.Register(this); }
public void Receive(AsyncRequestMessage<string> message) { message.Reply(work()); } async Task<string> work() { Task.Delay(2000).Wait(); return "hello"; } }
class SenderViewModel { public SenderViewModel() { Do(); } public async void Do() {
var res = await WeakReferenceMessenger.Default.Send(new AsyncRequestMessage<string>()); } }
|
MVVM源生成器
从8.0版本开始MVVMToolkit支持源生成器,通过源生成器MVVM工具包可以自动为应用程序生成相应代码,用户只需要使用Attribute进行标记即可。想要使用生成器,必须使用partial
类
属性
不使用生成器
1 2 3 4 5 6 7
| private string? name;
public string? Name { get => name; set => SetProperty(ref name, value); }
|
使用生成器
1 2
| [ObservableProperty] private string? name;
|
生成器会自动生成Name属性,生成器假定字段命名 lowerCamel
、 _lowerCamel
或 m_lowerCamel
,并将转换为 UpperCamel
。最终的属性为Public,但是字段可以是任意可见性。
通知依赖属性
一个属性要依赖于另一个属性,也就是另一个属性更改时,该属性也要发出更改通知。
未使用生成器
1 2 3 4 5 6 7 8 9 10 11
| public string? Name { get => name; set { if (SetProperty(ref name, value)) { OnPropertyChanged("FullName"); } } }
|
使用生成器
1 2 3
| [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] private string? name;
|
通知依赖命令
一个命令执行状态取决于此属性的值,也就是说,当属性更改时,要验证命令是否可执行。
不使用生成器
1 2 3 4 5 6 7 8 9 10 11
| public string? Name { get => name; set { if (SetProperty(ref name, value)) { MyCommand.NotifyCanExecuteChanged(); } } }
|
使用生成器
1 2 3
| [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(MyCommand))] private string? name;
|
属性验证
如果属性是在继承自ObservableValidator
的类型中声明的,还可以使用任何验证属性对其进行批注
不使用生成器
1 2 3 4 5 6 7 8 9 10 11
| public string? Name { get => name; set { if (SetProperty(ref name, value)) { ValidateProperty(value, "Value2"); } } }
|
使用生成器
1 2 3 4 5
| [ObservableProperty] [NotifyDataErrorInfo] [Required] [MinLength(2)] private string? name;
|
只有继承自 ValidationAttribute
的字段属性才能使用这种方法,自定义验证属性需要使用传统的方式。
发送通知消息
如果属性是在继承自 ObservableRecipient
的类型中声明的,则可以使用 NotifyPropertyChangedRecipients
特性
不使用生成器
1 2 3 4 5 6 7 8 9 10 11 12 13
| public string? Name { get => name; set { string? oldValue = name;
if (SetProperty(ref name, value)) { Broadcast(oldValue, value); } } }
|
使用生成器
1 2 3
| [ObservableProperty] [NotifyPropertyChangedRecipients] private string? name;
|
命令
不使用生成器
1 2 3 4 5 6 7 8
| private void SayHello() { Console.WriteLine("Hello"); }
private ICommand? sayHelloCommand;
public ICommand SayHelloCommand => sayHelloCommand ??= new RelayCommand(SayHello);
|
使用生成器
1 2 3 4 5
| [RelayCommand] private void SayHello() { Console.WriteLine("Hello"); }
|
生成器会基于方法名称创建命令名称。生成器会自动在末尾加上Command,如果是有异步方法,也会自动删除Async前缀。
命令参数
生成器支持将带参数的方法转换为命令
带参数的方法:
1 2 3 4 5
| [RelayCommand] private void GreetUser(User user) { Console.WriteLine($"Hello {user.Name}!"); }
|
自动转换为(带User泛型参数)
1 2 3
| private RelayCommand<User>? greetUserCommand;
public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);
|
异步命令
1 2 3 4 5 6 7
| [RelayCommand] private async Task GreetUserAsync() { User user = await userService.GetCurrentUserAsync();
Console.WriteLine($"Hello {user.Name}!"); }
|
自动生成
1 2 3
| private AsyncRelayCommand? greetUserCommand;
public IAsyncRelayCommand GreetUserCommand => greetUserCommand ??= new AsyncRelayCommand(GreetUserAsync);
|
此方法具有 CancellationToken
一个特殊情况,因为该方法将传播到命令以启用取消。 也就是说,如下所示的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| [RelayCommand] private async Task GreetUserAsync(CancellationToken token) { try { User user = await userService.GetCurrentUserAsync(token);
Console.WriteLine($"Hello {user.Name}!"); } catch (OperationCanceledException) { } }
|
如果同时想要同时生成一个取消命令,
1 2 3 4 5
| [RelayCommand(IncludeCancelCommand = true)] private async Task DoWorkAsync(CancellationToken token) { }
|
当命令式异步的可以设置AllowConcurrentExecutions是否为并发执行
启用和禁用命令
1 2 3 4 5 6 7 8 9 10
| [RelayCommand(CanExecute = nameof(CanGreetUser))] private void GreetUser(User? user) { Console.WriteLine($"Hello {user!.Name}!"); }
private bool CanGreetUser(User? user) { return user is not null; }
|