MVVMToolkit深入

MVVMToolkit深入

MVVMToolkit深入

初步了解MVVMToolkit可以访问MVVMToolkit入门教程

ObservableObject

简单用法

实现INotifyPropertyChangedINotifyPropertyChanging接口

使用方法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);//属性名称通过[CallerMemberName]自动捕获到
}
}

包装不可观测模型

如果想要没有实现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;//必须设置为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

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

消息具有StrongReferenceMessengerWeakReferenceMessenger两种类型,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;
//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方法

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_lowerCamelm_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)]//同时生成一个DoWorkCancelCommand命令,以便于用户取消操作
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;
}
作者

步步为营

发布于

2024-05-08

更新于

2025-03-15

许可协议