MVVMToolkit深入

MVVMToolkit深入

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

ObservableObject

简单用法

实现INotifyPropertyChangedINotifyPropertyChanging接口

使用方法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

消息具有StrongReferenceMessengerWeakReferenceMessenger两种类型,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_lowerCamelm_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;
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

步、步、为营

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值