WPF/MVVM系列(6)——消息传递

WPF/MVVM系列文章的前面几篇将焦点聚集在一个ViewModel中,但在实际WPF项目中每一个窗体都对应一个ViewModel,如何在多个ViewModel之间传递数据是一个重要话题。本文介绍CommunityToolkit.MVVM框架的事件机制。

基础使用

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;

// 创建消息接收者
var rec = new ViewModel();
// 创建一个属性变化消息的发送者
var rec2 = new ViewModel2();

// 发送ValueChangedMessage消息
var r1 = WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>("Tom"));
WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>("Tim"));
// 发送RequestMessage消息
var reply = WeakReferenceMessenger.Default.Send(new RequestMessage<int>());
if (reply.HasReceivedResponse)
Console.WriteLine("Reply is: " + reply.Response);
// 修改属性发送属性变化消息
rec2.Number = 100;
// 发送CollectionRequestMessage消息,可以返回一个数据集合
var reply2 = WeakReferenceMessenger.Default.Send(new CollectionRequestMessage<int>());
Console.WriteLine(reply2.Responses.Count);

/// <summary>
/// 消息接收者
/// </summary>
class ViewModel {
public ViewModel() {
WeakReferenceMessenger.Default.Register<ValueChangedMessage<string>>(this, Receive);
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<int>>(this, Receive2);
WeakReferenceMessenger.Default.Register<RequestMessage<int>>(this, Receive3);
WeakReferenceMessenger.Default.Register<CollectionRequestMessage<int>>(this, Receive4);
}

private static int i = 0;

private void Receive(object recipient, ValueChangedMessage<string> message) {
Console.WriteLine($"{message.Value}");
}

private void Receive2(object recipient, PropertyChangedMessage<int> message) {
Console.WriteLine($"Prperty {message.PropertyName} from {message.Sender.GetType().Name} received, newValue={message.NewValue}");
}

private void Receive3(object recipient, RequestMessage<int> message) {
message.Reply(999);
}

private void Receive4(object recipient, CollectionRequestMessage<int> message) {
for (int i = 0; i < 100; i++) {
message.Reply(i);
}
}
}

/// <summary>
/// 消息发送者
/// </summary>
class ViewModel2 : ObservableObject {
private int number;
public int Number {
get => number;
set {
if (SetProperty(ref number, value)) {
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<int>(this, nameof(Number), default, value));
}
}
}
}

使用CommunityToolkit.MVVM框架中的消息机制,最关键的只有两点:

  • 注册消息:当一个对象想监听某消息时,注册该消息即可,注册消息意味着当该消息来临时该对象可以执行某些动作,因此需要定义消息处理函数。上面例子中ViewModel就是消息接收者,注册了四种内置消息,并定义了对应消息的处理函数。
  • 发送消息:调用WeakReferenceMessenger.Default.Send()函数就可以发送消息,发送者不需要关心有哪些消息接收者,接收者也不需要关心有哪些发送者,两者高度解耦,事件转发和维护由事件聚集器总体协调。

代码简化

上面的实例已经把CommunityToolkit.MVVM消息机制的核心用法清楚了,余下内容只是基于该框架提供的机制进行代码简化而已。

消息注册的简化

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
class ViewModel : IRecipient<ValueChangedMessage<string>>, IRecipient<PropertyChangedMessage<int>>,
IRecipient<RequestMessage<int>>, IRecipient<CollectionRequestMessage<int>> {
public ViewModel() {
WeakReferenceMessenger.Default.RegisterAll(this);
}

private static int i = 0;

public void Receive(ValueChangedMessage<string> message) {
Console.WriteLine($"{message.Value}");
}

public void Receive(PropertyChangedMessage<int> message) {
Console.WriteLine($"Prperty {message.PropertyName} from {message.Sender.GetType().Name} received, newValue={message.NewValue}");
}

public void Receive(RequestMessage<int> message) {
message.Reply(999);
}

public void Receive(CollectionRequestMessage<int> message) {
for (int i = 0; i < 100; i++) {
message.Reply(i);
}
}
}
  • 消息接收者实现IRecipient<TMessage>接口,并实现相应的消息处理函数。
  • 调用WeakReferenceMessenger.Default.RegisterAll(this)就可以自动注册所有IRecipient<TMessage>指定的消息。

消息注册的再简化

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
var rec = new ViewModel();
rec.IsActive = true;

class ViewModel : ObservableRecipient, IRecipient<ValueChangedMessage<string>>, IRecipient<PropertyChangedMessage<int>>,
IRecipient<RequestMessage<int>>, IRecipient<CollectionRequestMessage<int>> {

private static int i = 0;

public void Receive(ValueChangedMessage<string> message) {
Console.WriteLine($"{message.Value}");
}

public void Receive(PropertyChangedMessage<int> message) {
Console.WriteLine($"Prperty {message.PropertyName} from {message.Sender.GetType().Name} received, newValue={message.NewValue}");
}

public void Receive(RequestMessage<int> message) {
message.Reply(999);
}

public void Receive(CollectionRequestMessage<int> message) {
for (int i = 0; i < 100; i++) {
message.Reply(i);
}
}
}
  • 当消息接收者继承自ObservableRecipient时,甚至无需手动调用WeakReferenceMessenger.Default.RegisterAll(this)进行消息注册,会自动注册。
  • 在接收消息之前必须将接收者的IsActive属性为true,开启接收消息模式。

属性变化消息的发送简化

  • SetProperty方法

当消息发送者继承自ObservableRecipient类时,可以为SetProperty()方法的broadcast参数传参以控制是否发送属性变化消息。也可以使用注解写法,更简洁!

1
2
3
4
5
6
7
class ViewModel2 : ObservableRecipient {
private int number;
public int Number {
get => number;
set => SetProperty(ref number, value, broadcast: true);
}
}
  • 注解写法
1
2
3
4
5
partial class ViewModel2 : ObservableRecipient {
[ObservableProperty]
[NotifyPropertyChangedRecipients]
private int number;
}

消息聚合器的原理

消息聚合器是一种发布/订阅(Publish/Subscribe)模式,消息聚合器可以对消息进行集中管理,消息发送者和消息接收者互不感知、高度解耦。

与之相似的还有观察者模式,观察者模式中消息发送者需要亲自维护接收者,当发送者的状态发生改变时手动通知其接收者。

下面简单实现下消息聚集器,从而能更好理解CommunityToolkit.MVVM中的消息发送接收机制。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var rec = new MessageReceiver();
EventAggregator.Instance.Send(new StringMessage("Hello world"));

/// <summary>
/// 定义消息接收者
/// </summary>
class MessageReceiver {
/// <summary>
/// 在构造函数中注册StringMessage消息
/// </summary>
public MessageReceiver() {
EventAggregator.Instance.Register<StringMessage>(this, Receive);
}

/// <summary>
/// 消息处理函数
/// </summary>
void Receive(StringMessage m) {
Console.WriteLine(m.Message);
}
}

/// <summary>
/// 自定义消息
/// </summary>
record StringMessage(string Message);

/// <summary>
/// 核心:消息聚合器
/// </summary>
class EventAggregator {
/// <summary>
/// 单例模式:饿汉式
/// </summary>
public static EventAggregator Instance { get; } = new();

/// <summary>
/// 消息接收者类型
/// </summary>
/// <param name="Receiver"></param>
/// <param name="Method"></param>
record MessageReceiver(object Receiver, Action<object> Method);

/// <summary>
/// 管理消息和消息的接收者
/// </summary>
private Dictionary<Type, List<MessageReceiver>> messages = new();

/// <summary>
/// 注册消息
/// </summary>
public void Register<TMessage>(Object receiver, Action<TMessage> method) {
var type = typeof(TMessage);
if (!messages.ContainsKey(type))
messages[type] = new();
messages[type].Add(new(receiver, o => method((TMessage)o)));
}

/// <summary>
/// 发送消息
/// </summary>
public void Send<TMessage>(TMessage message) {
var type = typeof(TMessage);
if (!messages.ContainsKey(type))
return;
foreach (var rec in messages[type]) {
rec.Method.Invoke(message);
}
}
}

消息聚合器对每一种消息及其接收者进行集中管理,体现在messages字典上。与CommunityToolkit.MVVM框架中的使用方法已经非常相似。

评论