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();
var r1 = WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>("Tom")); WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>("Tim"));
var reply = WeakReferenceMessenger.Default.Send(new RequestMessage<int>()); if (reply.HasReceivedResponse) Console.WriteLine("Reply is: " + reply.Response);
rec2.Number = 100;
var reply2 = WeakReferenceMessenger.Default.Send(new CollectionRequestMessage<int>()); Console.WriteLine(reply2.Responses.Count);
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); } } }
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
,开启接收消息模式。
属性变化消息的发送简化
当消息发送者继承自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"));
class MessageReceiver { public MessageReceiver() { EventAggregator.Instance.Register<StringMessage>(this, Receive); }
void Receive(StringMessage m) { Console.WriteLine(m.Message); } }
record StringMessage(string Message);
class EventAggregator { public static EventAggregator Instance { get; } = new();
record MessageReceiver(object Receiver, Action<object> Method);
private Dictionary<Type, List<MessageReceiver>> messages = new();
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))); }
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
框架中的使用方法已经非常相似。