方式一、通过依赖注入直接调用ViewModel
在前面的文章中,我们介绍了使用DI容器。
在注入ViewModel时,将它的生命周期配置为单例,这样我们就可以在任意的ViewModel中进行互相调用。
这里我们创建一个Send窗口和一个Receive窗口。
Send窗口用于发送消息,Receive窗口用于接收消息。
Send.xaml
1 <Window>
2 <Grid>
3 <Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="Send" Command="{Binding SendMessageCommand}"></Button>
4 </Grid>
5 </Window>
SendViewModel
这里我们将接收的ViewModel直接以参数传入,这样就可以直接操作Receive窗口中的列表。
1 public class SendViewModel
2 {
3 public RelayCommand SendMessageCommand { get; private set; }
4
5 protected ReceiveViewModel receiveViewModel;
6
7 public SendViewModel(ReceiveViewModel receiveViewModel)
8 {
9 SendMessageCommand = new RelayCommand(SendMessage);
10
11 this.receiveViewModel = receiveViewModel;
12 }
13
14 private void SendMessage()
15 {
16 DateTimeMessage dateTimeMessage = new DateTimeMessage();
17 dateTimeMessage.DatetimeNow = DateTime.Now.ToString();
18 receiveViewModel.AddDateTimeMessage(dateTimeMessage);
19 }
20 }
Receive.xaml
1 <Window>
2 <Grid>
3 <ListBox ItemsSource="{Binding MessageList}"></ListBox>
4 </Grid>
5 </Window>
ReceiveViewModel
1 public class ReceiveViewModel : INotifyPropertyChanged
2 {
3 private ObservableCollection<string> messageList = new ObservableCollection<string>();
4
5 public ObservableCollection<string> MessageList
6 {
7 get
8 {
9 return messageList;
10 }
11
12 set
13 {
14 messageList = value;
15 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MessageList"));
16 }
17 }
18 public event PropertyChangedEventHandler PropertyChanged;
19
20 public ReceiveViewModel()
21 {
22
23 }
24
25
26 public void AddDateTimeMessage(DateTimeMessage message)
27 {
28 messageList.Add(message.DatetimeNow);
29 }
30 }
然后我们配置DI容器
App.xaml.cs
1 public partial class App : Application
2 {
3 public static IUnityContainer Container { get; private set; }
4
5 protected override void OnStartup(StartupEventArgs e)
6 {
7 base.OnStartup(e);
8
9 Container = new UnityContainer();
10 RegisterTypes(Container);
11
12 Receive receive = new Receive();
13 receive.Show();
14 }
15
16 private void RegisterTypes(IUnityContainer container)
17 {
18 container.RegisterType<SendViewModel>();
19 container.RegisterType<ReceiveViewModel>(new ContainerControlledLifetimeManager());
20 }
21 }
最后再通过DI容器获取ViewModel并绑定到DataContext
以Receive窗口为例
Receive.xaml.cs
1 public partial class Receive : Window
2 {
3 public Receive()
4 {
5 InitializeComponent();
6
7 this.DataContext = App.Container.Resolve<ReceiveViewModel>();
8 }
9 }
方式二、通过回调
以前我们在使用MVVMLight
包时,里面有一个Messenger
类,它可以在ViewModel中订阅消息,当在其它位置发送这个消息时,订阅的ViewModel就可以接收到这个消息并进行处理。
MVVMLight示例如下:
ReceiveViewModel
(创建订阅)
1 using GalaSoft.MvvmLight;
2 using GalaSoft.MvvmLight.Messaging;
3
4 public class ReceiveViewModel : ViewModelBase
5 {
6 public ReceiverViewModel()
7 {
8 // 注册消息:指定消息类型、接收者(通常是自身)、处理方法
9 Messenger.Default.Register<UserMessage>(this, OnUserMessageReceived);
10 }
11
12 // 消息处理方法
13 private void OnUserMessageReceived(UserMessage message)
14 {
15 // 处理接收到的消息数据
16 var userId = message.UserId;
17 var userName = message.UserName;
18 }
19 }
SendViewModel
(发送)
1 using GalaSoft.MvvmLight;
2 using GalaSoft.MvvmLight.Command;
3 using GalaSoft.MvvmLight.Messaging;
4
5 public class SenderViewModel : ViewModelBase
6 {
7 public RelayCommand SendMessageCommand { get; private set; }
8
9 public SenderViewModel()
10 {
11 SendMessageCommand = new RelayCommand(SendMessage);
12 }
13
14 private void SendMessage()
15 {
16 // 创建消息实例并设置数据
17 var message = new UserMessage
18 {
19 UserId = 1001,
20 UserName = "张三"
21 };
22
23 // 发送消息
24 Messenger.Default.Send(message);
25 }
26 }
它这里内部的原理并不是非常复杂,就是利用回调的机制。
核心步骤如下:
1、创建一个字典类型,以类型名称(消息对象)作为Key,回调列表作为值
2、注册时,根据类型名称,将回调添加到列表中
3、发送时,循环列表判断是否注册了对应类型(消息对象),如果有,调用回调函数。
接下来我们自己实现一个简单的Messenger
类,重在讲解原理。
public class Messager
{
private static object obj = new object();
private static Messager instance;
private Dictionary<Type, List<GenericAction>> recipientsActions = new Dictionary<Type, List<GenericAction>>();
//单例
public static Messager Instance
{
get
{
if (instance == null)
{
lock (obj)
{
if (instance == null)
instance = new Messager();
}
}
return instance;
}
}
//注册
public void Register<TMessage>(Action<TMessage> action)
{
var messageType = typeof(TMessage);
List<GenericAction> list;
if (!recipientsActions.ContainsKey(messageType))
{
list = new List<GenericAction>();
recipientsActions.Add(messageType, list);
}
else
{
list = recipientsActions[messageType];
}
//将回调放到列表中
list.Add(new GenericAction<TMessage>(action));
}
public void Send<TMessage>(TMessage message)
{
var messageType = typeof(TMessage);
List<GenericAction> list = new List<GenericAction>();
if (recipientsActions.ContainsKey(messageType))
{
//获取当前消息对应的回调列表
list = recipientsActions[messageType];
}
//循环回调列表并调用
foreach (GenericAction<TMessage> action in list)
{
action.Invoke(message);
}
}
}
在ReceiveViewModel
进行注册
1 public class ReceiveViewModel : INotifyPropertyChanged
2 {
3 private ObservableCollection<string> messageList = new ObservableCollection<string>();
4
5 public ObservableCollection<string> MessageList
6 {
7 get
8 {
9 return messageList;
10 }
11
12 set
13 {
14 messageList = value;
15 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MessageList"));
16 }
17 }
18 public event PropertyChangedEventHandler PropertyChanged;
19
20 public ReceiveViewModel()
21 {
22 //注册消息
23 Messager.Messager.Instance.Register<DateTimeMessage>(ReceiveDateTimeMessage);
24 }
25
26 private void ReceiveDateTimeMessage(DateTimeMessage message)
27 {
28 messageList.Add(message.DatetimeNow);
29 }
30 }
在SendViewModel
中发送消息
1 public class SendViewModel
2 {
3 public RelayCommand SendMessageCommand { get; private set; }
4
5 public SendViewModel()
6 {
7 SendMessageCommand = new RelayCommand(SendMessage);
8 }
9
10 private void SendMessage()
11 {
12 DateTimeMessage dateTimeMessage = new DateTimeMessage();
13 dateTimeMessage.DatetimeNow = DateTime.Now.ToString();
14 //发送消息
15 Messager.Messager.Instance.Send<DateTimeMessage>(dateTimeMessage);
16 }
17 }
完整代码可以参考文末链接
方式三、使用三方包提供的Messenger
注意:方式二重在了解原理,除非有特殊要求,尽量还是使用软件包提供的Messenger类,功能会更加完整和稳定。
这里我们以CommunityToolkit.MVVM
包中的WeakReferenceMessenger
类进行演示。
使用CommunityToolkit.MVVM
包时,可通知的对象一般都会继承自ObservableRecipient
类型,
这个类型的内部会注入一个WeakReferenceMessenger
对象,所以使用起来就比较方便。
ReceiveViewModel
1 public class ReceiveViewModel : ObservableRecipient
2 {
3 private ObservableCollection<string> messageList = new ObservableCollection<string>();
4
5 public ObservableCollection<string> MessageList
6 {
7 get
8 {
9 return messageList;
10 }
11
12 set
13 {
14 SetProperty(ref messageList, value);
15 }
16 }
17
18 public ReceiveViewModel()
19 {
20 //注册
21 this.Messenger.Register<ReceiveViewModel, DateTimeMessage>(this, OnReceiveDateTimeChangedMessage);
22 }
23
24 //接收到消息时的回调函数
25 private void OnReceiveDateTimeChangedMessage(ReceiveViewModel recipient, DateTimeMessage message)
26 {
27 messageList.Add(message.Value.DatetimeNow);
28 }
29 }
SendViewModel
1 public class SendViewModel: ObservableRecipient
2 {
3 public RelayCommand SendMessageCommand { get; private set; }
4
5 public SendViewModel()
6 {
7 SendMessageCommand = new RelayCommand(SendMessage);
8 }
9
10 private void SendMessage()
11 {
12 //发送消息
13 this.Messenger.Send<DateTimeMessage>(new DateTimeMessage(new DateTimeDisplay() { DatetimeNow = DateTime.Now.ToString() }));
14 }
15 }
示例代码
https://github.com/zhaotianff/WPF-MVVM-Beginner/tree/main/10_ViewModelCommunication