C# WPF的MVVM模式中使用消息的订阅和发布
C#的WPF开发中经常会遇到需要在不同窗口或者界面间传递消息,如果要严格遵循Model-View-ViewModel前后台分离的原则来开发的话,数据的跨界面传输就会比较麻烦,尤其涉及到多个不同界面间的相互交互。
PubSub库的订阅和发布可以比较方便的解决这个问题。
1. Model: 消息模型
using CommunityToolkit.Mvvm.ComponentModel; namespace WpfPubSubDemo { public partial class MessageModel: ObservableObject { [ObservableProperty] public string? sender; [ObservableProperty] public string message; public MessageModel() { Sender = null; Message = string.Empty; } } }
2. View: 界面
2.1 主窗口MainWindow
Xaml
<Window x:Class="WpfPubSubDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfPubSubDemo" d:DataContext="{d:DesignInstance Type=local:MainViewModel}" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Frame x:Name="PageFrame"></Frame> <TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="0,134,0,0" TextWrapping="Wrap" d:Text="TextBlock" Text="{Binding MsgModel.Message}" VerticalAlignment="Top"/> <Label Grid.Column="1" Content="订阅者" HorizontalAlignment="Center" Margin="0,95,0,0" VerticalAlignment="Top"/> </Grid> </Window>
CS
using System.Windows; namespace WpfPubSubDemo { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainViewModel viewModel { get; set; } public MainWindow() { InitializeComponent(); viewModel = new MainViewModel(); DataContext = viewModel; PageFrame.Navigate(new PageView()); } } }
2.2 子窗口PageView
Xaml
<Page x:Class="WpfPubSubDemo.PageView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfPubSubDemo" d:DataContext="{d:DesignInstance Type=local:PageViewModel}" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" Title="PageView"> <Grid Background="#FFFEFEFE"> <TextBox x:Name="MsgTextBox" HorizontalAlignment="Center" Margin="0,113,0,0" TextWrapping="Wrap" Text="{Binding Msg.Message, Mode=TwoWay}" VerticalAlignment="Top" Width="120"/> <Button Content="发布" Command="{Binding PublishCommand}" HorizontalAlignment="Center" Margin="0,165,0,0" VerticalAlignment="Top"/> <Label Content="发布者" HorizontalAlignment="Center" Margin="0,73,0,0" VerticalAlignment="Top"/> </Grid> </Page>
CS
using System.Windows.Controls; namespace WpfPubSubDemo { /// <summary> /// PageView.xaml 的交互逻辑 /// </summary> public partial class PageView : Page { public PageViewModel pageViewModel; public PageView() { InitializeComponent(); pageViewModel = new PageViewModel(); DataContext = pageViewModel; } } }
3. ViewModel: 逻辑调度
3.1 主窗体MainViewModel:
using CommunityToolkit.Mvvm.ComponentModel; using PubSub; namespace WpfPubSubDemo { public partial class MainViewModel: ObservableObject { [ObservableProperty] public MessageModel msgModel; public Hub hub { get; set; } public MainViewModel() { MsgModel = new MessageModel(); hub = Hub.Default; hub.Subscribe<MessageModel>((m) => MsgModel = m); } } }
3.2 子窗口PageViewModel:
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using PubSub; namespace WpfPubSubDemo { public partial class PageViewModel: ObservableObject { [ObservableProperty] public MessageModel msg; public PageViewModel() { Msg = new MessageModel(); Msg.Message = "Init"; } [RelayCommand] public void Publish() { Hub.Default.Publish(Msg); } } }
回复#1 @admin :
WPF其实和WinForm非常不一样了,尤其是用了MVVM模式以后要做到完全的前后端分离,不通过消息的订阅和发布几乎就不可能在界面之间交互了。
其实CommunityToolkit.Mvvm是自带了Message消息的发布和订阅的,只不过需要做很多消息注册以及实现各种接口的操作,相对来说用起来比较麻烦。
PubSub这个库就挺傻瓜式的,Publish和Subscribe一共就2句就实现了消息的传递。
如果在主程序的MainViewModel中建一个类似全局变量的对象来存放共享数据也是可以的,只需要注册一个OnPropertyChangedEvent 事件,当共享数据发生变化时,同步发布出去,就可以通知到所有订阅了该消息的对象。而WPF的Binding表达式更简化了这个操作,直接可以绑定到共享数据的对象上。
定制了一个项目模板,包含WPF + CommunityToolkit.Mvvm + Behaviors + PubSub + HandyControls
都已经配置好了,直接导入VS模板即可
上楼中的HandyControls是3.4.5版本,好像有些问题 - 在VS中的静态资源无法自动提示,但显示是正常的,用起来不是很爽。
降到3.4.0版本,更新了App.xaml的资源包
<Application x:Class="WpfApplicationBase.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplicationBase" xmlns:hc="https://handyorg.github.io/handycontrol" StartupUri="Views\MainWindow.xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" /> <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" /> <hc:Theme /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>
现在可以正常提示了
更新后的模板文件
多说一句,上面那个VS模板WpfApplicationBase.zip文件应当放到
C:\Users\你的电脑名称\Documents\Visual Studio 2022\Templates\ProjectTemplates
然后在VS中新建项目的时候就可以直接选择这个模板了,千万不要解压zip后去打开啊,那样是运行不了的
登录后方可回帖
如果在winform中我还是喜欢用Easy.MessageHub通过定义个全局静态类实现这个, 用的时候就和aardio里一样了. 主要是之前习惯了aar这种写法了.哈