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 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