MVVM 工具包源生成器的简单使用,简化WPF/Avalonia的绑定代码

CommunityToolkit.MvvmMVVM 工具包)是微软把之前的Microsoft.Toolkit.Mvvm复活后的一个项目,最早的时候还在研究ReactiveX,不过在发现微软复活了这个老项目,并且还有源生成器的这种新特性后,直接转投研究MVVM 工具包

先来个例子

代码

先来个简单的例子演示一下这个包的几个小功能

比如我现在又个小项目,下面是它的部分源码

MainWindow.xamlWindow里,只添加一个StackPanel

<StackPanel VerticalAlignment="Center">
    <TextBlock Text="{Binding Text}" />
    <TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>

新建个MainWindowModel.cs拿来放要被绑定的数据

internal class MainWindowModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    private string _text = "Hello World!";
    public string Text
    {
        get => _text;
        set
        {
            _text = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
        }
    }
}

MainWindow.xaml.csInitializeComponent();下面加一行绑定

this.DataContext = new MainWindowModel();

实现了什么功能

show gif

这一小段代码,简单的将一个TextBlockTextBoxText属性的值,都绑定到了Text这个变量上面:

  • TextBoxText属性的值被更改
  • Text被赋予新的值,调用set访问器
  • set访问器在更新_text值后,触发PropertyChanged.Invoke
  • TextBlockText属性的值被更新

MVVM 源生成器

在开始之前,首先要在nuget里装上CommunityToolkit.Mvvm这个包

要使用这个功能,我们先要把MainWindowModel类改成分布类,只需要在声明的地方加个partial前缀就好

[INotifyPropertyChanged]
internal partial class MainWindowModel

在最上方,引用一下CommunityToolkit.Mvvm的类

//using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;

ObservableProperty自动生成可观察属性

我们可以直接用ObservableProperty,于是MainWindowModel类的代码变成了这样:

[INotifyPropertyChanged]
internal partial class MainWindowModel
{
    [ObservableProperty]
    private string _text = "Hello World!";
}

这里的源生成器会自动按大驼峰命名规范来转换你的变量名,如lowerCamel、 、_lowerCamelm_lowerCamel都会被转换为UpperCamel,并且将被声明为public以便使用

可以看到代码量大大减少了,当代码编译时,会自动编译成下面的样子(来自ILSpy的反编译结果,我精简掉了很多代码,以便大家可以清晰看懂):

internal class MainWindowModel : INotifyPropertyChanged
{
    private string _text = "Hello World!";

    public string Text
    {
        get
        {
            return _text;
        }
        set
        {
            if (!EqualityComparer<string>.Default.Equals(_text, value))
            {
                _text = value;
                OnPropertyChanged(__KnownINotifyPropertyChangedArgs.Text);
            }
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        this.PropertyChanged?.Invoke(this, e);
    }

    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }
}

可以看到,会精简掉很大部分的代码,让逻辑更清晰,同时减轻工作量

依赖其他值的属性

比如我们在MainWindowModel类添加另一个属性OtherText

public string OtherText
{
    get
    {
        return "Other:" + _text;
    }
}

然后让TextBlock的绑定更改为它:

<TextBlock Text="{Binding OtherText}" />

这时我们知道,当Text变化后,OtherText也应该变化,那么如何触发OtherText的更新呢?
我们可以使用NotifyPropertyChangedFor,像这样:

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(OtherText))]
private string _text = "Hello World!";

这样写之后,当Text变化后,会通知OtherText也变化了,从而实现及时的更改:

changed image

绑定命令

MVVM 工具包的源生成器同样可以绑定命令,比如我们在MainWindowModel类添加一个函数Hello,使用RelayCommand即可简便的实现该功能

[RelayCommand]
private void Hello()
{
    Text = "Hello!";
}

RelayCommand会自动生成一个名为HelloCommand的函数用于绑定,我们可以在StackPanel内添加一个Button来测试这个功能

<Button Command="{Binding HelloCommand}" Content="123" />

点击按钮后,可以看到功能正常

hello cmd

绑定异步命令

同时我们绑定的命令也可以是异步的,可以把函数Hello改成下面这样,继续测试

[RelayCommand]
private async void Hello()
{
    for (int i = 1; i <= 10; i++)
    {
        Text = $"wait {i}";
        await Task.Delay(500);
    }
}

这样写不会卡住UI线程,效果如下:

async command

其他功能

其他还有很多功能,这里就不一一列举了,大家可以前往MVVM 工具包官方文档查看

发表评论

您的电子邮箱地址不会被公开。