WPF MVVM进阶系列教程(四、ViewModel通信)
方式一、通过依赖注入直接调用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 SendViewModel2 {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 : INotifyPropertyChanged2 {3 private ObservableCollection<string> messageList = new ObservableCollection<string>();4 5 public ObservableCollection<string> MessageList6 {7 get8 {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 : Application2 {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 : ViewModelBase5 {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 : ViewModelBase6 {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 : INotifyPropertyChanged2 {3 private ObservableCollection<string> messageList = new ObservableCollection<string>();4 5 public ObservableCollection<string> MessageList6 {7 get8 {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 SendViewModel2 {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 : ObservableRecipient2 {3 private ObservableCollection<string> messageList = new ObservableCollection<string>();4 5 public ObservableCollection<string> MessageList6 {7 get8 {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: ObservableRecipient2 {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