MVVMToolkit深入
初步了解MVVMToolkit可以访问MVVMToolkit入门教程。
ObservableObject
简单用法
实现INotifyPropertyChanged
和INotifyPropertyChanging
接口
使用方法SetProperty<T>(ref T, T, string)
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);//属性名称通过[CallerMemberName]自动捕获到
}
}
包装不可观测模型
如果想要没有实现INotifyPropertyChanged接口的实例属性进行通知,可以使用SetProperty<TModel, T>(T, T, TModel, Action<TModel, T>, string)
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)
方法。
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值表示希望在属性值更新的时候验证属性。
public class RegistrationForm : ObservableValidator
{
private string name;
[Required]
[MinLength(2)]
[MaxLength(100)]
public string Name
{
get => name;
set => SetProperty(ref name, value, true);
}
}
自定义验证方法
使用[CustomValidationAttribute]
增加自定义验证方法
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方法中
自定义验证属性
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");
}
}
使用自定义属性
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
再来看该内容
ReceiveViewModel rv = new();
rv.IsActive = true;//必须设置为true
SenderViewModel sv = new();
//使用ObservableRecipient的意义在于不用手动注册消息,因为默认调用了Messenger.RegisterAll(this)
//如果注册指定消息可以重写OnActivated和OnDeactivated
class ReceiveViewModel : ObservableRecipient,IRecipient<string>
{
public void Receive(string message)
{
message.Dump();
}
}
class SenderViewModel
{
public SenderViewModel()
{
WeakReferenceMessenger.Default.Send("hello");
}
}
命令Command
RelayCommand
使用和基本的Command一致
AsyncRelayCommand
public class MyViewModel : ObservableObject
{
public MyViewModel()
{
DownloadTextCommand = new AsyncRelayCommand(DownloadText);
}
public IAsyncRelayCommand DownloadTextCommand { get; }
private Task<string> DownloadText()
{
return WebService.LoadMyTextAsync();
}
}
<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
可以自动取消消息订阅。
SenderViewModel sv = new();
ReceiveViewModel rv = new();
sv.Number=10;
//ValueChangedMessage是一个消息基类
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))
{
//PropertyChangedMessage用于在ObservableObject类中,当属性改变时而发出广播
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<int>(this,nameof(Number),default,value));
}
}
}
}
IRecipient接口
用法和上面一样,只不过接口必须强制实现Receive方法
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
用于发送者向接受者请求信息
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
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
类
属性
不使用生成器
private string? name;
public string? Name
{
get => name;
set => SetProperty(ref name, value);
}
使用生成器
[ObservableProperty]
private string? name;
生成器会自动生成Name属性,生成器假定字段命名 lowerCamel
、 _lowerCamel
或 m_lowerCamel
,并将转换为 UpperCamel
。最终的属性为Public,但是字段可以是任意可见性。
通知依赖属性
一个属性要依赖于另一个属性,也就是另一个属性更改时,该属性也要发出更改通知。
未使用生成器
public string? Name
{
get => name;
set
{
if (SetProperty(ref name, value))
{
OnPropertyChanged("FullName");
}
}
}
使用生成器
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullName))]
private string? name;
通知依赖命令
一个命令执行状态取决于此属性的值,也就是说,当属性更改时,要验证命令是否可执行。
不使用生成器
public string? Name
{
get => name;
set
{
if (SetProperty(ref name, value))
{
MyCommand.NotifyCanExecuteChanged();
}
}
}
使用生成器
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(MyCommand))]
private string? name;
属性验证
如果属性是在继承自ObservableValidator
的类型中声明的,还可以使用任何验证属性对其进行批注
不使用生成器
public string? Name
{
get => name;
set
{
if (SetProperty(ref name, value))
{
ValidateProperty(value, "Value2");//验证属性
}
}
}
使用生成器
[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
[MinLength(2)]
private string? name;
只有继承自 ValidationAttribute 的字段属性才能使用这种方法,自定义验证属性需要使用传统的方式。
发送通知消息
如果属性是在继承自 ObservableRecipient
的类型中声明的,则可以使用 NotifyPropertyChangedRecipients
特性
不使用生成器
public string? Name
{
get => name;
set
{
string? oldValue = name;
if (SetProperty(ref name, value))
{
Broadcast(oldValue, value);
}
}
}
使用生成器
[ObservableProperty]
[NotifyPropertyChangedRecipients]
private string? name;
命令
不使用生成器
private void SayHello()
{
Console.WriteLine("Hello");
}
private ICommand? sayHelloCommand;
public ICommand SayHelloCommand => sayHelloCommand ??= new RelayCommand(SayHello);
使用生成器
[RelayCommand]
private void SayHello()
{
Console.WriteLine("Hello");
}
生成器会基于方法名称创建命令名称。生成器会自动在末尾加上Command,如果是有异步方法,也会自动删除Async前缀。
命令参数
生成器支持将带参数的方法转换为命令
带参数的方法:
[RelayCommand]
private void GreetUser(User user)
{
Console.WriteLine($"Hello {user.Name}!");
}
自动转换为(带User泛型参数)
private RelayCommand<User>? greetUserCommand;
public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);
异步命令
[RelayCommand]
private async Task GreetUserAsync()
{
User user = await userService.GetCurrentUserAsync();
Console.WriteLine($"Hello {user.Name}!");
}
自动生成
private AsyncRelayCommand? greetUserCommand;
public IAsyncRelayCommand GreetUserCommand => greetUserCommand ??= new AsyncRelayCommand(GreetUserAsync);
此方法具有 CancellationToken一个特殊情况,因为该方法将传播到命令以启用取消。 也就是说,如下所示的方法:
[RelayCommand]
private async Task GreetUserAsync(CancellationToken token)
{
try
{
User user = await userService.GetCurrentUserAsync(token);
Console.WriteLine($"Hello {user.Name}!");
}
catch (OperationCanceledException)
{
}
}
如果同时想要同时生成一个取消命令,
[RelayCommand(IncludeCancelCommand = true)]//同时生成一个DoWorkCancelCommand命令,以便于用户取消操作
private async Task DoWorkAsync(CancellationToken token)
{
}
当命令式异步的可以设置AllowConcurrentExecutions是否为并发执行
启用和禁用命令
[RelayCommand(CanExecute = nameof(CanGreetUser))]
private void GreetUser(User? user)
{
Console.WriteLine($"Hello {user!.Name}!");
}
private bool CanGreetUser(User? user)
{
return user is not null;
}