WPF MVVM进阶系列教程(四、ViewModel通信)
🍠 WPF MVVM进阶系列教程
方式一、通过依赖注入直接调用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

浙公网安备 33010602011771号