[CommunityToolkit.Mvvm个人总结]1.生成器和特性

本系列主要介绍微软社区工具包CommunityToolKit.Mvvm,是本人在观看B站UP主十月的寒流视频时的个人总结,真心感谢!学习C#的同学强力建议关注此UP,并观看其全部视频。

用 CommunityToolkit.Mvvm 加速 MVVM 开发流程_哔哩哔哩_bilibili

语言:C# 

IDE:Microsoft Visual Studio Community 2022 

框架:WPF,.net 8.0


目录

一.Mvvm模式下使用频率高的对象

1.1 ViewModelBase

2.1 RelayCommand

二、使用CommunityToolkit后的简化写法

2.1 部分简写

2.2 全部简写

三、Demo演示

3.1 Demo1,基础写法

3.2 Demo2,进阶写法

3.3 Demo3,异步方法

3.4 Demo4,小结

四、总结

4.1 ObservableObject

4.2 ObservableProperty

4.3 RelayCommand

4.3.1 CanExecute

4.3.1.1 属性名

4.3.1.2 方法名

4.3.2 AllowConcurrentExecutions

4.4 NotifyPropertyChangedFor

4.5 OnChanged,OnChanging


一、Mvvm模式下使用频率高的对象

1.1 ViewModelBase

主要用于UI绑定,通过PropertyChangedEventHandler事件通知前台,以下是一个简单实现:


class ViewModelBase : INotifyPropertyChanged
{
	public event PropertyChangedEventHandler? PropertyChanged;
	
	public void OnPropertyChanged(string propertyName)
	{
		PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}
}

class MainViewModel:ViewModelBase
{
	private string title;
	public string Title
	{
		get => title;
		set 
		{
			if (title != value)
			{
				title = value;
				OnPropertyChanged(nameof(Title));
			}
		}
	}
}

可以通过CallerMemberNameAttribute进行简化


class ViewModelBase : INotifyPropertyChanged
{
	public event PropertyChangedEventHandler? PropertyChanged;
	
	public void OnPropertyChanged([CallerMemberName]string propertyName = "")
	{
		PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}
}

class MainViewModel:ViewModelBase
{
	private string title;
	public string Title
	{
		get => title;
		set 
		{
			if (title != value)
			{
				title = value;
				OnPropertyChanged();
			}
		}
	}
}

2.1 RelayCommand

绑定至UI控件的Command,用于执行事件及判断事件是否可以执行,以下是一个简单的实现:


class RelayCommand:ICommand
{
	private readonly Action<object> execute;
	private readonly Predicate<object> canExecute;
	
	public RelayCommand(Action<object> execute, Predicate<object> canExecute)
	{
		this.execute = execute;
		this.canExecute = canExecute;
	}
	
	public RelayCommand(Action<object> execute) : this(excecute,null)
	{
		
	}
	
	public bool CanExecute(object parameter)
	{
		return canExecute == null || canExecute(parameter);
	}
	
	public void Execute(object parameter)
	{
		execute(parameter);
	}
	
	public event EventHandler? CanExecuteChanged;
	
	public void RaiseCanExecuteChanged()
	{
		CanExecuteChanged?.Invoke(this, EventArgs.Empty);
	}
}

二、使用CommunityToolkit后的简化写法

2.1 部分简写

class MainViewModel : ObservableObject
{
	private string title;
	public string Title
	{
		get => title;
		set => SetProperty(ref title, value);	
	}

    public RelayCommand ButtonClickCommand{get;}
}

2.2 全部简写

[ObservableProperty]
string title = "Hello world";


[RelayCommand()]
void ButtonClick()
{
    Title += "Goodbye";
}

三、Demo演示

3.1 Demo1,基础写法

显示效果:CheckBox控制Button的IsEnable,Button点击后改变TextBlock的内容。

ViewModel:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace Lesson1.ViewModels
{
    public class MainViewModel1 : ObservableObject
    {
        private string title = "Hello,world";

        public string Title
        {
            get => title;
            set => SetProperty(ref title, value);
        }

        private bool isEnabled;

        public bool IsEnabled
        {
            get => isEnabled;
            set
            {
                SetProperty(ref isEnabled, value);
                ButtonClickCommad.NotifyCanExecuteChanged();
            }
        }

        public RelayCommand ButtonClickCommad { get; }

        public MainViewModel1()
        {
            ButtonClickCommad = new RelayCommand(() => Title = "Goodbye", () => IsEnabled);
        }
    }
}

View:

<UserControl x:Class="Lesson1.Views.MainView1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
             xmlns:ViewModels="clr-namespace:Lesson1.ViewModels"
             d:DesignHeight="300"
             d:DesignWidth="300"
             mc:Ignorable="d">
    <UserControl.DataContext>
        <ViewModels:MainViewModel1 />
    </UserControl.DataContext>
    <Grid TextElement.FontSize="32">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBox Width="300" Text="{Binding Title}" />
            <CheckBox Content="Is Enabled" IsChecked="{Binding IsEnabled}" />
            <Separator Margin="5" />
            <Button Command="{Binding ButtonClickCommad}" Content="Click Me" />
        </StackPanel>
    </Grid>
</UserControl>

3.2 Demo2,进阶写法

显示效果:

CheckBox1控制Button1的IsEnable,Button1点击后改变TextBlock内容+=1,

CheckBox2控制Button2的IsEnable,Button2点击后删除TextBlock的最后一位。

ViewModel:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace Lesson1.ViewModels
{
    public partial class MainViewModel2 : ObservableObject
    {
        [ObservableProperty]
        string title = "Hello world";

        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(ButtonClick1Command))]
        bool isEnabled1 = false;

        [RelayCommand(CanExecute = nameof(CanButton1Click))]
        void ButtonClick1()
        {
            Title += "1";
        }

        bool CanButton1Click() => IsEnabled1;

        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(ButtonClick2Command))]
        bool isEnabled2 = false;

        [RelayCommand(CanExecute = nameof(IsEnabled2))]
        void ButtonClick2()
        {
            if (Title.Length > 0) Title = Title.Remove(Title.Length - 1);
        }

    }
}

View:

<UserControl x:Class="Lesson1.Views.MainView2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:ViewModels="clr-namespace:Lesson1.ViewModels"
             xmlns:local="clr-namespace:Lesson1.Views"
             d:DesignHeight="450"
             d:DesignWidth="800"
             mc:Ignorable="d">
    <UserControl.DataContext>
        <ViewModels:MainViewModel2 />
    </UserControl.DataContext>
    <Grid TextElement.FontSize="32">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBox Width="300" Text="{Binding Title}" />
            <CheckBox Content="Is Enabled 1" IsChecked="{Binding IsEnabled1}" />
            <Button Command="{Binding ButtonClick1Command}" Content="Click Me 1" />
            <CheckBox Content="Is Enabled 2" IsChecked="{Binding IsEnabled2}" />
            <Button Command="{Binding ButtonClick2Command}" Content="Click Me 2" />
        </StackPanel>
    </Grid>
</UserControl>

3.3 Demo3,异步方法

显示效果:

CheckBox1控制Button的IsEnable,Button点击后启动一个异步任务,任务内容为延时2秒,任务结束后改变TextBlock的内容。

CheckBox2可显示异步任务的状态。

ViewModel:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Threading.Tasks;

namespace Lesson1.ViewModels
{
    public partial class MainViewModel3 : ObservableObject
    {
        [ObservableProperty]
        string title = "Hello world";

        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(ButtonClickCommand))]
        bool isEnabled = false;

        [RelayCommand(CanExecute = nameof(CanButtonClick))]
        async Task ButtonClickAsync()
        {
            await Task.Delay(2000);
            Title = "Goodbye";
        }

        bool CanButtonClick() => IsEnabled;
    }
}

View:

<UserControl x:Class="Lesson1.Views.MainView3"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:ViewModels="clr-namespace:Lesson1.ViewModels"
             xmlns:local="clr-namespace:Lesson1.Views"
             d:DesignHeight="450"
             d:DesignWidth="800"
             mc:Ignorable="d">
    <UserControl.DataContext>
        <ViewModels:MainViewModel3 />
    </UserControl.DataContext>
    <Grid TextElement.FontSize="32">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBox Width="300" Text="{Binding Title}" />
            <CheckBox Content="Is Enabled" IsChecked="{Binding IsEnabled}" />
            <Separator Margin="5" />
            <Button Command="{Binding ButtonClickCommand}" Content="Click Me" />
            <CheckBox Content="Is running" IsChecked="{Binding ButtonClickCommand.IsRunning, Mode=OneWay}" />
        </StackPanel>
    </Grid>
</UserControl>

3.4 Demo4,小结

显示效果:

CheckBox1控制Button的IsEnable,Button点击后启动一个异步任务,任务内容为延时2秒,任务结束后改变TextBlock1的内容。同时还会改变TextBlock2的内容。

CheckBox2可显示异步任务的状态。

ViewModel:

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Threading.Tasks;

namespace Lesson1.ViewModels
{
    public partial class MainViewModel4 : ObservableObject
    {
        [ObservableProperty]
        //[NotifyPropertyChangedFor(nameof(Caption))]
        private string title = $"{defaultTitle}";

        private const string defaultTitle = "Hello world";

        partial void OnTitleChanged(string value)
        {
            OnPropertyChanged(nameof(Caption));
        }

        private int index = 1;

        public string Caption => $"Title: {Title}";

        [ObservableProperty]
        [NotifyCanExecuteChangedFor(nameof(ButtonClickCommand))]
        private bool isEnabled = false;


        [RelayCommand(CanExecute = nameof(CanButtonClick))]
        private async Task ButtonClickAsync()
        {
            await Task.Delay(2000);
            Title = $"{defaultTitle}:{index++}";
        }

        private bool CanButtonClick() => IsEnabled;
    }
}

View:

<UserControl x:Class="Lesson1.Views.MainView4"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Lesson1.Views"
             xmlns:ViewModels="clr-namespace:Lesson1.ViewModels"
             d:DesignHeight="450"
             d:DesignWidth="800"
             mc:Ignorable="d">
    <UserControl.DataContext>
        <ViewModels:MainViewModel4 />
    </UserControl.DataContext>
    <Grid>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" TextElement.FontSize="32">
            <TextBox Width="400" Text="{Binding Title}" />
            <TextBox Width="400" Text="{Binding Caption, Mode=OneWay}" />
            <CheckBox Content="Is Enabled" IsChecked="{Binding IsEnabled}" />
            <Button Command="{Binding ButtonClickCommand}" Content="Click Me" />
            <CheckBox Content="Is running" IsChecked="{Binding ButtonClickCommand.IsRunning, Mode=OneWay}" />
        </StackPanel>
    </Grid>
</UserControl>

四、总结

4.1 ObservableObject

class需要继承基类ObservableObject,且需要添加partial修饰符

public partial class MainViewModel : ObservableObject
{
}

4.2 ObservableProperty

需要绑定到UI的字段添加特性ObservableProperty,会自动生成同名属性

首字母小写或开头添加_或开头添加m_

4.3 RelayCommand

需要绑定到UI的命令添加特性RelayCommand,会自动生成同名命令

4.3.1 CanExecute

为Command是否可以执行的属性或者方法的名称

4.3.1.1 属性名
public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(buttonClickCommand))]
    bool isEnabled;

    [RelayCommand(CanExecute = nameof(IsEnabled))]
    void ButtonClick()
    {
    }
}
4.3.1.2 方法名
public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(buttonClickCommand))]
    bool isEnabled;

    [RelayCommand(CanExecute = nameof(CanButtonClick))]
    void ButtonClick()
    {

    }
    bool CanButtonClick() => IsEnabled;
}

4.3.2 AllowConcurrentExecutions

是否允许并发执行,参考以下代码

ViewModel:

public partial class MainViewModel5 : ObservableObject
{
    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(ButtonClickCommand))]
    string title = "Count:";

    [RelayCommand]
    async Task ButtonClickAsync()
    {
        await Task.Delay(2000);
        Title += "1";
    }
}

View:

<UserControl x:Class="Lesson1.Views.MainView5"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:ViewModels="clr-namespace:Lesson1.ViewModels"
             xmlns:local="clr-namespace:Lesson1.Views"
             d:DesignHeight="450"
             d:DesignWidth="800"
             mc:Ignorable="d">
    <UserControl.DataContext>
        <ViewModels:MainViewModel5 />
    </UserControl.DataContext>
    <Grid>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBox Width="300" Text="{Binding Title}" />
            <Separator Margin="5" />
            <Button Command="{Binding ButtonClickCommand}" Content="Click Me" />
        </StackPanel>
    </Grid>
</UserControl>

点击Button后,Button变成灰色,2秒后恢复,并且更新TextBox的内容。

将RelayCommand添加AllowConcurrentExecutions = true:

public partial class MainViewModel5 : ObservableObject
{
    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(ButtonClickCommand))]
    string title = "Count:";

    [RelayCommand(AllowConcurrentExecutions = true)]
    async Task ButtonClickAsync()
    {
        await Task.Delay(2000);
        Title += "1";
    }
}

变成:点击Button后,Button仍然可点击,依旧是2秒后更新TextBox内容,且2秒内可多次点击Button,更新的次数与点击次数保持一致 。

4.4 NotifyPropertyChangedFor

添加到实现ObservableProperty特性的字段,在其发生改变时会通知同名参数

public partial class MainViewModel : ObservableObject
{
    /// <summary>
    /// Title发生改变时,UI绑定Caption的控件也会收到通知
    /// </summary>
    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(Caption))]
    string title;

    public string Caption;
}

4.5 OnChanged,OnChanging

实现ObservableProperty的字段会自动新增方法,OnChanged,OnChanging,分别对应其值改变时和改变后

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    string title;

    partial void OnTitleChanged(string oldValue, string newValue)
    {
        Console.WriteLine("Title has changed");
    }

    partial void OnTitleChanging(string oldValue, string newValue)
    {
        Console.WriteLine("Title is changing");
    }        
}

五、使用技巧

5.1 Json相关

现有一个简单DEMO:

public partial class MainView : Window
{
    public MainView()
    {
        InitializeComponent();

        MainViewModel vm = new MainViewModel() { Name = "Jack", Description = "Teacher" };
        var data = JsonSerializer.Serialize(vm);
    }


    public partial class MainViewModel : ObservableObject
    {
        [ObservableProperty]
        string name;

        [ObservableProperty]
        string description;
    }
}

运行后data内容为:

{"Name":"Jack","Description":"Teacher"}

5.1.1 修改字段名

增加name的特性:

[ObservableProperty]
[property:JsonPropertyName("User name")]
string name;

运行后data内容为:

{"User name":"Jack","Description":"Teacher"}

5.1.2 忽略字段名

增加Description的特性

[ObservableProperty]
[property:JsonIgnore]
string description;

运行后data内容为:

{"User name":"Jack"}

5.2 未实现INotifyPropertyChanged的类实现通知功能

假设现在有一个类,它可能是系统生成的,或者外部类,总之他未实现NotifyPropertyChanged接口,属性在改变时无法通知UI:

public class Model
{
    public string Name { get; set; }
    public string Description { get; set; }
}

现在对其进行封装:

public partial class ObservableModel : ObservableObject
{
    private readonly Model model;

    public ObservableModel(Model model)
    {
        this.model = model;
    }

    public string Name
    {
        get => model.Name;
        set => SetProperty(model.Name, value, model, (model, name) => model.Name = name);
    }

    [RelayCommand]
    void ChangedName()
    {
        Name = "Tom";
    }
}

将其绑定后运行发现,当ChangeNameCommand执行后,ObservableModel的属性Name已经变为Tom,而且Model的属性Name也已改变

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值