首页 > 其他分享 >CommunityToolkit从入门到精通(详细版)

CommunityToolkit从入门到精通(详细版)

时间:2024-05-16 21:11:23浏览次数:21  
标签:精通 CommunityToolkit 入门 private class 方法 public string 属性

MVVM基础

  •  Model:负责存储数据,以及对数据的处理。
  • View:负责展示数据,以及与用户的交互。
  • ViewModel:负责将 Model 的数据在 View 中显示出来,同时也负责将 View 中的事件传递给 Model。可以说,ViewModel 是 Model 和 View 之间的桥梁。

组件模型(ComponentModel)

ObservableObject

ObservableObject 是工具包为我们提供的一个实现了 INotifyPropertyChanged 接口的基类。

拥有两个子类:

  • ObservableValidator
  • ObservableRecipient

ObservableObject 类提供的方法:

ObservableObject 除了实现了 INotifyPropertyChanged 接口之外,还提供了一些方法来帮助我们实现通知功能。

  • OnPropertyChanged(拥有两个方法重载)
  • SetProperty(包含六个方法重载)
  • SetPropertyAndNotifyOnCompletion(包含五个重载,用于 TaskNotifier

OnPropertyChanged

简易版:

 1 class MyViewModel : ObservableObject
 2 {
 3     private string _name;
 4     public string Name
 5     {
 6         get => _name;
 7         set
 8         {
 9             _name = value;
10             OnPropertyChanged(); 
11         }
12     }
13 }

完整版:

 1 class MyViewModel : ObservableObject
 2 {
 3     private string _name;
 4     public string Name
 5     {
 6         get => _name;
 7         set
 8         {
 9             // 检查值是否发生变化
10             if (_name == value ) return;
11 
12             // 触发属性值即将发生变化的事件
13             OnPropertyChanging();
14 
15             // 设置属性值
16             _name = value;
17 
18             // 触发属性值已经发生变化的事件
19             OnPropertyChanged();
20         }
21     }
22 }

 

SetProperty

SetProperty 方法用于设置属性值,并在属性值发生变化时触发 PropertyChanged 事件。该方法的底层其实就是在调用 OnPropertyChanged。这里是一个最简单的 SetProperty 方法的实现:

 1 protected bool SetProperty<T>([NotNullIfNotNull(nameof(newValue))] ref T field, T newValue, [CallerMemberName] string? propertyName = null)
 2 {
 3     // 采用默认的 Comparer 来比较新旧值是否相等
 4     if (EqualityComparer<T>.Default.Equals(field, newValue))
 5     {
 6         return false;
 7     }
 8 
 9     OnPropertyChanging(propertyName);
10     field = newValue;
11     OnPropertyChanged(propertyName);
12     return true;
13 }

SetProperty 方法的返回值是一个 bool 类型的值,用于表示属性值是否发生变化。如果属性值发生变化,则返回 true,否则返回 false。所以,如果希望在属性值发生变化时执行一些额外的操作,可以通过判断 SetProperty 方法的返回值来实现。比如:

 1 private string _name;
 2 
 3 public string Name
 4 {
 5     get => _name;
 6     set
 7     {
 8         if (SetProperty(ref _name, value, nameof(Name)))
 9         {
10             // 属性值发生变化时,执行一些额外的操作
11         }
12     }
13 }

 

ObservableRecipient

ObservableValidator

中继指令(RelayCommand)

WPF 等框架中,如果希望为一个按钮控件绑定一个命令,正确的做法是为它的 Command 属性绑定一个 ICommand 接口类型的对象。但是,ICommand 接口的实现类通常需要自己实现 CanExecute 和 Execute 方法,这样会导致代码冗余。为了解决这个问题,于是有了“中继指令”(RelayCommand)这个概念。

ICommand 接口的定义如下:

1 public interface ICommand
2 {
3     bool CanExecute(object parameter);
4     void Execute(object parameter);
5     event EventHandler CanExecuteChanged;
6 }

但是工具包并不满足于此,而是又给出了几个额外的接口,包括:

  • IRelayCommand
  • IRelayCommand<T>
  • IAsyncRelayCommand
  • IAsyncRelayCommand<T>

其中,泛型版本为表示该中继指令可以传入一个参数,通常也就是按钮等控件的 CommandParameter 属性。

IRelayCommand 与 IRelayCommmand<T> 接口的定义分别为:

 1 public interface IRelayCommand : ICommand
 2 {
 3     void NotifyCanExecuteChanged();
 4 }
 5 
 6 public interface IRelayCommand<in T> : IRelayCommand
 7 {
 8     bool CanExecute(T? parameter);
 9 
10     void Execute(T? parameter);
11 }

它在 ICommand 的基础上增加了一个 NotifyCanExecuteChanged 方法,用于通知命令的可执行状态发生了变化。这样,我们就可以更便捷地实现这一功能了。比如我们可以在某个属性的 setter 中调用这个方法,这样当属性值发生变化时,命令的可执行状态也会及时得到更新。

IAsyncRelayCommand接口定义如下:

 1 public interface IAsyncRelayCommand : IRelayCommand, INotifyPropertyChanged
 2 {
 3     Task? ExecutionTask { get; }
 4 
 5     bool CanBeCanceled { get; }
 6 
 7     bool IsCancellationRequested { get; }
 8 
 9     bool IsRunning { get; }
10 
11     Task ExecuteAsync(object? parameter);
12 
13     void Cancel();
14 }

它在 IRelayCommand 的基础上增加了一些异步操作相关的属性和方法,比如 ExecutionTask 属性用于获取当前的执行任务,Cancel 方法用于取消当前的执行任务,IsRunning 属性用于判断当前异步任务是否正在执行等。

RelayCommand

工具包为我们提供了一个 RelayCommand 类,用于快捷地实现中继指令。它最朴素的用法是:

 1 public class ViewModel : ObservableObject
 2 {
 3     public IRelayCommand MyCommand1 { get; } //通常我们会将类型声明为 IRelayCommand,而不是 RelayCommand,从而让使用者只关注接口暴露的方法,而不关心其实现;并且通常我们会在 VM 的构造函数中初始化这些属性,所以只提供 getter 即可
 4     public IRelayCommand MyCommand2 { get; }
 5 
 6     public ViewModel()
 7     {
 8         MyCommand1 = new RelayCommand(Execute, CanExecute); //RelayCommand 的构造函数可以接受两个参数:Execute 和 CanExecute,分别表示执行和判断是否可执行的方法
 9         MyCommand2 = new (() => {
10             DoJob();
11             Debug.WriteLine();
12         }); //RelayCommand 的构造函数也支持只传入一个参数,表示执行的方法,且不提供 CanExecute 的逻辑。而且这里除了直接传入符合委托类型的方法,还可以使用一个 Lambda 表达式简单地进行实现。
13     }
14 
15     private void Execute()
16     {
17         DoJob();
18     }
19 
20     private bool CanExecute()
21     {
22         return true;
23     }
24 
25     private void DoJob() { }
26 }

但是在实际使用工具包进行开发时,我们基本不会采用上述朴素的方式进行实现,而是会借助源生成器。方式如下:

 1 public partial class ViewModel : ObservableObject
 2 {
 3     [ObservableProperty]
 4     private string name;
 5 
 6     private bool CanSubmit() => !string.IsNullOrEmpty(name);
 7 
 8     [RelayCommand(CanExecute = nameof(CanSubmit))]
 9     private void Submit()
10     {
11         Debug.WriteLine($"Name: {name}");
12     }
13 }

后台生成的代码为:

1 partial class MainViewModel
2 {
3     private RelayCommand? submitCommand;
4     public IRelayCommand SubmitCommand
5         => submitCommand ??= new RelayCommand(new Action(Submit), CanSubmit);
6 }

 

AsyncRelayCommand

异步中继指令更加强大,因为它背后可以是一个异步任务。这个异步任务的状态默认会对按钮控件的可用性进行控制,并且异步任务有办法取消。

源生成器基本用法:

partial class MainViewModel : ObservableValidator
{
    [ObservableProperty]
    private string name = "";

    private bool CanSubmit() => !string.IsNullOrEmpty(Name);

    [RelayCommand(CanExecute = nameof(CanSubmit))]
    private async Task SubmitAsync()
    {
        await Task.Delay(1000);
    }
}

后台会为我们生成名为 SubmitCommand 的属性。工具包在生成这个属性时,会自动去掉原始方法名的 Async 结尾

取消任务

如果我们想要为这个异步中继指令添加取消功能,除了手动实现,我们还可以借助源生成器。只需要将上例修改为:

 1 [RelayCommand(CanExecute = nameof(CanSubmit), IncludeCancelCommand = true)]
 2 private async Task SubmitAsync(CancellationToken token)
 3 {
 4     try
 5     {
 6         await Task.Delay(2000, token);
 7     }
 8     catch (OperationCanceledException)
 9     {
10 
11     }
12 }

即可。此时后台会为我们额外生成一个名为 SubmitCancelCommand 的中继指令。此时只需要将该中继指令绑定到一个按钮,即可使用。并且运行程序可以发现,这个取消按钮只有在异步任务执行时才会处于可用状态。

此时XAML的内容如:

1 <StackPanel>
2     <TextBox Text="{Binding Name"} />
3     <Button Content="Submit" Command="{Binding SubmitCommand}" />
4     <Button Content="Cancel" Command="{Binding SubmitCancelCommand}" />
5 </StackPanel>

异步任务可以多次发起

如果我们希望异步任务可以多次发起,也就是按钮不需要等待上一个任务结束才能继续点击,那么只需要将特性的 AllowConcurrentExecutions 参数设为 true 即可。

源生成器(Source Generator)

源生成器对项目有一定的要求:
  1. 项目文件(.csproj)必须是 SDK 风格的项目文件
  2. .NET 版本必须是 .NET 5.0+,或 .NET Standard 2.1+

适用于字段(Field)的特性

如果希望一个字段拥有一个名称对应的包含通知功能的属性,只需要为字段添加一个 ObservableProperty 特性即可。

1 public partial class MainViewModel : ObservableObject
2 {
3     [ObservableProperty]
4     private string name = "";
5 }

下面是编译器在后台生成的代码。这里展示的代码是完整版。后面为了简洁,会省略或简化一些行。

 1 /// <inheritdoc cref="name"/>
 2 [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
 3 [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
 4 public string Name
 5 {
 6     get => name;
 7     [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
 8     set
 9     {
10         if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(name, value))
11         {
12             OnNameChanging(value);
13             OnNameChanging(default, value);
14             OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
15             name = value;
16             OnNameChanged(value);
17             OnNameChanged(default, value);
18             OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
19         }
20     }
21 }

源生成器可以非常智能地识别字段的名称,并生成相应名称的属性。它支持的命名方式有:

  • 小写字母开头:firstName
  • 下划线开头:_firstName
  • 其他:m_firstName

希望通知另一个熟悉

如果希望在字段的值发生变化时,额外通知另一个属性,可以使用 NotifyPropertyChangedFor 特性。

1 public partial class MainViewModel : ObservableObject
2 {
3     [ObservableProperty]
4     [NotifyPropertyChangedFor(nameof(FullName))] //支持传入多个属性的名称
5     private string firstName = ""; //setter 中会生成形如 OnPropertyChanged("FullName") 的代码
6 
7     public string FullName => firstName + " " + lastName;
8 }

通知中继指令更新其可用状态

如果某个中级指令(RelayCommand)的 CanExecute 与属性有关,并且希望属性的值发生变化后能够通知中继指令更新状态,那么可以使用 NotifyCanExecuteChangedFor 特性:

1 public partial class MainViewModel : ObservableObject
2 {
3     [ObservableProperty]
4     [NotifyCanExecuteChangedFor(nameof(FooCommand))] //支持传入多个中继指令的名称
5     private bool isFooEnabled; //会在 setter 中添加 FooCommand.NotifyCanExecuteChanged();
6 
7     public IRelayCommand FooCommand { get; }
8 }

广播属性值的变化

如果希望在一个 ObservableRecipient 对象中广播广播属性值的变化(即调用 Broadcast 方法),可以为字段添加 NotifyPropertyChangedRecipients 特性:

1 public partial class MainViewModel : ObservableRecipient
2 {
3     [ObservableProperty]
4     [NotifyPropertyChangedRecipients]
5     private string name = ""; //会在 setter 中添加形如 Broadcast(oldValue, value, "FirstName") 的代码
6 }

验证属性的值是否有效

如果希望在一个 ObservableValidator 对象中验证属性的值是否有效,可以为字段添加 NotifyDataErrorInfo 特性:

1 public partial class SignUpViewModel : ObservableValidator
2 {
3     [Required]
4     [EmailAddress]
5     [ObservableProperty]
6     [NotifyDataErrorInfo]
7     private string email = ""; //会在 setter 中添加 ValidateProperty(value, "Email") 的代码
c

为生成的属性添加特性

如果希望为生成的属性添加额外的特性,尤其比如 JsonIgnore 等,从而在序列化时忽略这个属性,可以使用下面这个特殊语法:

1 public partial class MainViewModel : ObservableObject
2 {
3     [ObservableProperty]
4     [property: JsonIgnore]
5     private string name = "";
6 }

希望在属性值发生变化后执行额外的逻辑

如果希望在属性值发生变化后执行额外的逻辑,还可以借助分部方法来实现。从 基本用法 的中给出的后台生成的完整代码中可以发现,setter 中存在一些分部方法,名称为 `OnChanged。借助这些分部方法,我们就可以实现这个需求。形如:

 1 public partial class MainViewModel : ObservableObject
 2 {
 3     [ObservableProperty]
 4     private string name = "";
 5 
 6     partial void OnNameChanged(string value)
 7     {
 8         // Do something
 9     }
10 
11     partial void OnNameChanged(string oldValue, string newValue)
12     {
13         // Do something
14     }
15 }

工具包在比较新的版本中为我们提供了可以传入旧值新值的分部方法,它最典型的使用场景是,我们希望在这个属性发生改变后执行一些资源释放相关的逻辑,从而保证旧值可以被 GC 回收。比如:

 1 public partial class MainViewModel : ObservableObject
 2 {
 3     [ObservableProperty]
 4     private SubViewModel selectedItem;
 5 
 6     partial void OnSelectedItemChanged(SubViewModel oldValue, SubViewModel newValue)
 7     {
 8         // 这里可以对旧值执行一些逻辑,常见的比如:
 9         oldValue?.PropertyChanged -= OnSelectedItemPropertyChanged;
10         oldValue?.Dispose();
11 
12         // 然后可以对新值执行一些逻辑,比如:
13         newValue?.PropertyChanged += OnSelectedItemPropertyChanged;
14     }
15 }

 

适用于方法(Method)的特性

工具包提供的能够为方法添加的特性只有 RelayCommand。关于它的用法详见:

  • RelayCommand
  • AsyncRelayCommand

适用于类(Class)的特性

虽然并不多,但是工具包依然为我们提供了一些可以用于(分部)类的特性,包括:

  • ObservableObject
  • ObservableRecipient
  • INotifyPropertyChanged

它们看起来与工具包的几个基类,以及 INPC 接口完全同名(除了它们实际的名称是以 Attribute 结尾的)。它们的作用也非常直白,就是让当前类实现 INPC 接口,并为当前类添加这些类的功能,比如:

  • ObservableObject 会使当前类拥有 OnPropertyChangedSetProperty 等方法
  • ObservableRecipient 会使当前类拥有 Broadcast 等方法
  • INotifyPropertyChanged 会使当前类添加最基本的 OnPropertyChanged 方法

(目前工具包并未提供 ObservableValidator 特性)

这些特性基本只适用于一种情形:为一个已经继承了某个基类的类添加通知功能。比如现在已经有一个 Student 类继承了 Person 类,但 Person 类并没有继承自 ObservableObject,此时还希望 Student 能够拥有通知功能。这时可以给 Student 类改为分部类,并添加这个特性即可。

消息中继(Messengers)

Messenger 是一个消息传递机制,它可以让对象之间进行通信,而不需要它们之间有直接的引用关系。从设计模式的角度来说,Messenger 其实是中介者模式的一种实现。

 可以看出,在使用中介者模式之后,个模块之间不再呈现出网状结构,而是变成了星状结构。这样一来,模块之间的耦合就会大大降低,代码也会变得更加简洁。

Messenger 在很多 MVVM 框架中均能找到它的身影。虽然叫法不同,但是背后的原理及逻辑都是相通的。这里列举几个常见框架的叫法:

  • CommunityToolkit.Mvvm:Messenger(信使)
  • Prism:EventAggregator(事件聚合)
  • ReactiveUI:MessageBus(消息总线)

设想一下,平常如果想要实现两个 ViewModel 之间的通信(比如 A 调用 B 的方法,或 A 获取 B 中某个属性的值),我们会采用哪些做法呢?一般来说无外乎下面几种:

  1. 想办法将 B 的实例的引用传递给 A,比如将 B 的实例保存为 A 的成员(属性或字段),然后在 A 中调用访问这个成员(引用的传递通常在 A 的构造函数中完成)
  2. 在 A 中定义一个事件,然后在 B 中订阅这个事件(这里仍然需要想办法传递引用),当 A 中触发这个事件时,B 中的方法就会被调用
  3. 为 B 实现单例模式,然后在 A 中通过 B.Instance 来调用 B 的方法或获取 B 的属性值

但是这些方法均存在一个问题:模块之间存在耦合。比如,如果我们想要将 B 替换成 C,那么就需要修改 A 的代码;更糟的是,如果我们想让 A 能够与更多的类通信,那么就需要在 A 中添加更多的属性、事件或方法。这样一来,类与类之间的耦合就会越来越严重,代码也会变得越来越难以维护。

Messenger背后的原理

Messenger 背后的原理可以想象成一个字典,它的键是消息的类型,值是一个委托列表。在注册时,我们会将类的对象和一个回调函数(委托)作为值添加到字典中;在发送消息时,我们会根据消息的类型从字典中取出对应的委托列表,然后依次调用这些委托。

IMessenger

IMessenger

IMessenger 是工具包中的一个接口,规定了 Messenger 需要实现的一些方法,比如:

  • Register:注册消息
  • Unregister:取消注册
  • UnregisterAll:取消所有注册
  • Send:发送消息

实现了这一接口的类

工具包中提供了两个实现了 IMessenger 接口的类:

  • WeakReferenceMessenger:使用弱引用,避免内存泄漏
  • StrongReferenceMessenger:使用强引用,通常用于希望消息接收者不被 GC 回收的场景

一般来说,我们使用 WeakReferenceMessenger 即可。

基本用法

Messenger 的使用方法基本上分三步走:

  1. 声明一个消息类型,或使用工具包内置的几种类型
  2. 注册消息(IMessneger.Register
  3. 发送消息(IMessenger.Send

MessageSender

 1 class MessageSender
 2 {
 3     private readonly IMessenger _messenger;
 4 
 5     public MessageSender(IMessenger messenger)
 6     {
 7         _messenger = messenger;
 8     }
 9 
10     public void SendMessage()
11     {
12         // 发送消息
13         _messenger.Send(new MyMessage { Content = "Hello, world!" });
14     }
15 }

 

MessageReceiver

 1 class MessageReceiver
 2 {
 3     private readonly IMessenger _messenger;
 4 
 5     public MessageReceiver(IMessenger messenger)
 6     {
 7         _messenger = messenger;
 8 
 9         // 注册指定类型的消息
10         _messenger.Register<MyMessage>(this, OnMessageReceived);
11     }
12 
13     void OnMessageReceived(object recipient, MyMessage message)
14     {
15         Console.WriteLine($"Message received from {recipient.GetType().Name}: {message.Content}");
16     }
17 }

 

MyMessage

1 // 一个自定义消息类型
2 class MyMessage
3 {
4     public string Content { get; init; } = "";
5 }

实例化这些类:

1 IMessenger messenger = WeakReferenceMessenger.Default;
2 var receiver = new MessageReceiver(messenger);
3 var sender = new MessageSender(messenger);
4 
5 sender.SendMessage();

这里我们使用了依赖注入的方式,将 IMessenger 注入到了 MessageSender 和 MessageReceiver 中。这样做的好处是,我们可以很方便地将 IMessenger 替换为其他实现了 IMessenger 接口的类,比如 StrongReferenceMessenger;同时,在类中,我们也不需要关心 IMessenger 的具体实现,只需要知道它是一个 IMessenger,以及它提供的方法即可。

WeakReferenceMessenger

什么是 WeakReference?

在说 WeakReferenceMessenger 之前,我们先要搞清楚什么是弱引用(WeakReference)。

在 C# 中,我们可以通过 new WeakReference(obj) 来创建一个弱引用,这个弱引用会指向 obj,但是不会阻止 obj 被 GC 回收。

1 var obj = new object();
2 var weakReference = new WeakReference(obj);

这一特性在 MVVM 中显得尤为重要。因为我们如果想要借助 Messenger 来实现 MVVM 中的消息传递,那么我们就需要在 ViewModel 中注册事件。一旦我们没有及时取消注册,那么这个 ViewModel 就无法被 GC 回收,这就会导致内存泄漏。而使用 WeakReferenceMessenger 就可以避免这个问题。

WeakReferenceMessenger

在前面的例子中,我们使用 WeakReferenceMessenger.Default 来获取一个默认的 WeakReferenceMessenger 实例。这个实例是一个单例,我们可以在任何地方使用它。如果我们的项目比较简单,比如不需要考虑其他 Messenger 类型,且不使用 IoC 容器,那么我们可以直接使用这个单例。

但是,在使用传统的强引用时,我们需要注意内存泄漏的问题。我们需要在代码中合适的位置调用 Unregister 方法,以取消注册。

Messages

常用消息类型Messages

工具包为我们提供了几个常用的消息类型,包括:

  • ValueChangedMessage
  • PropertyChangedMessage
  • RequestMessage

自定义Message

工具包中的 IMessenger 接口约定的 SendRegister 等泛型方法,并没有要求消息的种类满足任何条件(比如继承自某个基类),所以完全可以自由地实现任何消息类型。

比如这里,我们用 C# 9.0 为我们带来的 record 类型快速声明一个消息类型,并使用:

 1 // 声明一个消息类型
 2 public record MyMessage(string Content);
 3 
 4 // 发送消息
 5 WeakReferenceMessenger.Default.Send(new MyMessage("Hello World!"));
 6 
 7 // 接收消息
 8 WeakReferenceMessenger.Default.Register<MyMessage>(this, message =>
 9 {
10     Console.WriteLine(message.Content);
11 });

ValueChangedMessage

ValueChangedMessage 是一个最基本的消息类型,它包含一个 Value 属性,用于存储消息的值。如果希望发送一个用于通知某个值发生变化的消息,可以使用 ValueChangedMessage

此外,还可以继承这个类,从而实现自己的消息类型。比如:

 1 public class MyMessage : ValueChangedMessage<string>
 2 {
 3     public MyMessage(string value) : base(value)
 4     {
 5     }
 6 }
 7 
 8 // 发送消息
 9 WeakReferenceMessenger.Default.Send(new MyMessage("Hello World!"));
10 
11 // 接收消息
12 WeakReferenceMessenger.Default.Register<MyMessage>(this, message =>
13 {
14     Console.WriteLine(message.Value);
15 });

RequestMessage

这是一个特殊的消息类型。如果 Send 方法发送的是这个类(或它的子类),那么 Send 方法将拥有一个返回值,这个返回值就是消息的接收者回复的消息。此时,消息的接收者也能够通过消息上的 Reply 方法回复消息的发送者。比如:

 1 // 注册消息接收者
 2 WeakReferenceMessenger.Default.Register<MyRequestMessage>(
 3     new object(),
 4     (_, m) =>
 5     {
 6         // 收到消息后,会进行简单的判断,并回复消息
 7         if (m.Content == "Nice to meet you!")
 8             m.Reply("Nice to meet you, too!");
 9         else
10             m.Reply("Yes?");
11     }
12 );
13 
14 // 发送消息并查看回复的消息内容
15 var res1 = WeakReferenceMessenger.Default.Send(new MyRequestMessage("Hello!"));
16 Console.WriteLine(res1.Response);
17 
18 var res2 = WeakReferenceMessenger.Default.Send(new MyRequestMessage("Nice to meet you!"));
19 Console.WriteLine(res2.Response);
20 
21 // 声明一个自定义消息类型
22 public class MyRequestMessage : RequestMessage<string>
23 {
24     public string Content { get; init; }
25 
26     public MyRequestMessage(string content)
27     {
28         Content = content;
29     }
30 }

PropertyChangedMessage

PropertyChangedMessage 是一个用于通知属性发生变化的消息类型,它包含一个 PropertyName 属性,用于存储属性的名称。如果希望发送一个用于通知某个属性发生变化的消息,可以使用 PropertyChangedMessage。这个类通常配合 ObservableRecipient 的 Broadcast 方法使用。

Token

使用方法

频道的使用方法非常简单,我们只需要给 Send 与 Register 两个方法分别添加一个参数即可。这里,我们选择了 string 类型的参数,也就是下面代码中出现的 "token" 参数。

 1 var vm = new ViewModel();
 2 WeakReferenceMessenger.Default.Send(new Message("Hello"), "token");
 3 class ViewModel
 4 {
 5     public ViewModel()
 6     {
 7         WeakReferenceMessenger.Default.Register<Message, string>(this, "token",
 8             (_, m) => { Console.WriteLine(m.Content); });
 9     }
10 }
11 class Message
12 {
13     public Message(string content)
14     {
15         Content = content;
16     }
17 
18     public string Content { get; }
19 }

 

IRecipient

IRecipient<> 接口是一个泛型接口,它的泛型参数是一个 TMessage 类型的消息。这个接口定义了一个 Receive 方法,用于接收消息。我们可以通过实现这个接口来接收消息,形如:

1 public class MyViewModel : ObservableObject, IRecipient<MyMessage>
2 {
3     public void Receive(MyMessage message)
4     {
5         // 处理消息
6     }
7 }

如果这个类需要接收多种 TMessage 类型的消息,那么可以实现多个 IRecipient<> 接口。

在实现了这样的接口后,我们不需要使用传统的方式去注册这些接收消息的方法,而是可以简单地调用 Messenger.RegisterAll(this) 方法,将当前类实现的所有 IRecipient<> 接口的方法注册到信使对象中。类似地,我们可以调用 Messenger.UnregisterAll(this) 方法来注销这些方法。

例如,这里我们使用 WeakReferenceMessenger.Default 作为信使对象。那么,我们的 ViewModel 类可以这样实现:

public class ViewModel : ObservableRecipient, IRecipient<Message>
{
    public ViewModel()
    {
        Messenger.RegisterAll(this);
    }

    public void Receive(Message message)
    {
        // 处理消息
    }
}

RegisterAll 方法还包含一个重载,允许传入一个 Token 对象,用于指定消息的频道。类似地,UnregisterAll 方法也包含一个重载,允许传入一个 Token 对象。但如果不传入 Token 对象,则默认为不考虑频道,注销当前对象注册的全部消息。

RegisterAll 方法写在 IMessengerExtensions.cs 中,是对 IMessenger 对象的扩展。它的大致逻辑是,通过反射,找到传入的对象中实现了 IRecipient<> 接口的所有方法,并将它们注册到信使对象中。

UnregisterAll 方法的实现逻辑与 RegisterAll 类似,只是将注册改为注销。这个方法是写在具体的类的实现中的,比如 WeakReferenceMessenger 类。

 

标签:精通,CommunityToolkit,入门,private,class,方法,public,string,属性
From: https://www.cnblogs.com/davisdabing/p/18196205

相关文章

  • Django 自定义管理命令:从入门到高级
    title:Django自定义管理命令:从入门到高级date:2024/5/1618:34:29updated:2024/5/1618:34:29categories:后端开发tags:Django自定义命令入门教程高级技巧命令创建命令使用自定义管理第1章简介1.1 Django管理命令简介Django是一个流行的Python......
  • React-入门手册-全-
    React入门手册(全)原文:zh.annas-archive.org/md5/2B8E3D6DF41679F5F06756066BE8F7E8译者:飞龙协议:CCBY-NC-SA4.0前言诸如Angular和React之类的项目正在迅速改变开发团队构建和部署Web应用程序到生产环境的方式。在本书中,你将学习到使用React入门所需的基础知识,并应......
  • JavaSE入门学习
    Java入门学习目录Java入门学习Java特征和优势Java三大版本开发环境搭建JDK下载及安装配置环境变量HelloWorld及简单语法规则使用IDE开发1.创建一个Java项目(IDEA)2.在该项目src目录下new一个class文件3.编辑代码4.运行代码Java特征和优势简单性面向对象可移植性高性能......
  • ElasticSearch (ES从入门到精通一篇就够了)
    ES分布式搜索引擎注意:在没有创建库的时候搜索,ES会创建一个库并自动创建该字段并且设置为String类型也就是text什么是elasticsearch?一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能什么是elasticstack(ELK)?是以elasticsearch为核心的技术栈,包......
  • 精通RAG架构:从0到1,基于LLM+RAG构建生产级企业知识库
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......
  • Windows下QEMU虚拟化探索:从入门到精通
    本文背景:大部分云电脑不会开启intelVT-X虚拟技术,导致VM、Vbox等都无法使用。就得靠搭建QEMU版的虚拟机了。一、QEMU简介QEMU是一款开源的虚拟化软件,可以模拟CPU以及其他硬件设备,使你在一台物理机器上运行多个虚拟机。QEMU支持广泛的操作系统,包括Windows、Linux、macOS等。二......
  • vue3的入门--setup
    代码量:200行以上博客量:1时间:2h vue2中的data和methods可以与setup并列写,但是:data和methods可以利用this调用setup中的数据而,setup中,不能调用data和methods中的数据<!--Person.vue--><template><divclass="person"><h2>姓名:{{name}}</h2>&......
  • kettle从入门到精通 第六十课 ETL之kettle for循环处理每条数据,so easy!
    1、kettle原生是支持for循环处理的,无需通过javascript脚本或者java脚本开发for循环控制。当然如果想通过脚本挑战下也是可以的。本节课主要讲解如何通过kettle中的job来实现for循环控制,如下图所示:1)步骤【设置变量】设置单个job级别的变量。2)步骤【转换】加载数据集清单列表,返......
  • C---游戏开发的音频编程入门指南-全-
    C++游戏开发的音频编程入门指南(全)原文:zh.annas-archive.org/md5/DA6F8DEA921C8862289A88F7D7BB3BD8译者:飞龙协议:CCBY-NC-SA4.0前言音频在视频游戏中无疑是我们手头最强大的工具之一,它可以在许多不同的方面发挥作用,比如通过音效提供反馈、通过环境音轨增加沉浸感、通过录......
  • [C#] [WPF] 在MVVM中实现拖拽功能 - 入门
    拖拽功能是使用频率较高的一种交互,用传统方法去写很简单,但是在mvvm规则下,也没必要弄得很麻烦我的入门案例就选择了使用mvvm重写tutorialspoint-Interaction里的调色盘案例,view如下MainWindow.xaml这里的重点是控件要允许拖拽以及对应的事件目标控件,填充色绑定,......