找回密码
 立即注册
首页 业界区 安全 笔记:WPF MVVM 模式下通过消息机制获取自定义对话框数 ...

笔记:WPF MVVM 模式下通过消息机制获取自定义对话框数据

锑砖 3 天前
本篇笔记主要记录如何在 MVVM 模式下,通过消息机制获取自定义对话框的数据。
这是在实现一个增删查改的小页面时出现的需求,将原本添加的功能单独放在对话框中,进而精简主界面。
运行环境:

  • .NET 10
  • CommunityToolkit.Mvvm 8.4.0
实现步骤

先定义一个带回复功能的消息类,用作媒介,传递不同的 View 和 ViewModel 之间的数据,然后分别在 View、ViewModel 中订阅、发送消息。
这个消息类的作用是通知接收者该打开对话框,并在对话框关闭后将数据返回。
sequenceDiagram    participant VM as ViewModel    participant Messenger as 消息中介    participant View as View    participant Dialog as 对话框    VM->>Messenger: 发送打开对话框消息    Messenger->>View: 触发消息处理    View->>Dialog: 创建并显示对话框    Note over Dialog: 用户操作并关闭    Dialog-->>View: 返回数据    View-->>Messenger: 回复消息    Messenger-->>VM: 返回结果    VM->>VM: 处理返回数据步骤 1:定义带回复能力的消息类

定义一个普通的类,继承 CommunityToolkit.mvvm 的 RequestMessage,使 Send 方法拥有一个返回值,而这个返回值就是消息订阅者回复的数据。
  1. public class OpenDialogMessage : RequestMessage<string> { }
复制代码
此时,消息的接收者也能够通过消息上的 Reply 方法回复消息的发送者。
如果需要了解更多的消息类型,可以到 此网站 查阅。
步骤 2:主界面 ViewModel 发送 "打开对话框" 消息

创建一个命令,绑定到对应的按钮或其他触发器上,当触发后,会发送"打开对话框"的消息,并接收订阅者的回复,然后打印在控制台中。
  1. public partial class MainWindowViewModel : ObservableObject
  2. {
  3.     [RelayCommand]
  4.     private void OpenDialog()
  5.     {
  6.         var result = WeakReferenceMessenger
  7.             .Default.Send<OpenDialogMessage>();
  8.         
  9.         if (result != null)
  10.         {
  11.             Console.Out.WriteLine("result = {0}", result.Response);
  12.         }
  13.     }
  14. }
复制代码
步骤 3:主界面后置代码订阅消息
  1. public partial class MainWindow : Window
  2. {
  3.     public MainWindow()
  4.     {
  5.         InitializeComponent();
  6.         DataContext = new MainWindowViewModel();
  7.         WeakReferenceMessenger.Default
  8.             .Register<MainWindow, OpenDialogMessage>(this, (receiver, message) => { });
  9.     }
  10. }
复制代码
步骤 4:为对话框添加关闭事件,并显示对话框

创建消息处理的匿名函数,当接收到消息后调用。在其中创建对话框,并添加窗口关闭事件,等到窗口关闭时,就将对话框的数据回复给发送者。
  1. public partial class MainWindow : Window
  2. {
  3.     public MainWindow()
  4.     {
  5.         // --- 省略已有代码 ---
  6.         
  7.         WeakReferenceMessenger.Default
  8.             .Register<MainWindow, OpenDialogMessage>(this, (receiver, message) =>
  9.         {
  10.             var dialog = new DialogWindow();
  11.             
  12.             // 添加窗口关闭事件
  13.             dialog.Closed += (s, e) =>
  14.             {
  15.                 var w = s as DialogWindow;
  16.                 message.Reply(w?.InputText.Text);
  17.             };
  18.             // 打开模态对话框
  19.             dialog.ShowDialog();
  20.         });
  21.     }
  22. }
复制代码
步骤 5:创建对话框界面

在这个界面中,需要注意的有三点:

  • TextBox 的输入内容,是要获取的数据,所以为其添加了一个 Name 属性,方便后续调用。
  • 两个 Button 分别设置了 IsDefault 和 IsCancel 属性:

    • IsDefault: 设置 Button 是否为默认按钮。 用户通过按 Enter 键调用默认按钮。
    • IsCancel:设置 Button 是否为取消按钮。 用户可以通过按 ESC 键调用取消按钮。

  • 为两个 Button 分别创建点击事件,来设置 DialogResult 的值。
由于这里开启的是模态对话框,只要设置了对话框的结果值(DialogResult),窗口就会自动关闭。
  1. <Window --- 省略已有代码 ---
  2.         MinHeight="200" MinWidth="300"
  3.         SizeToContent="WidthAndHeight"
  4.         ResizeMode="NoResize"
  5.         WindowStartupLocation="CenterOwner">
  6.    
  7.     <Grid RowDefinitions="Auto, *"
  8.           ColumnDefinitions="Auto, *"
  9.           Margin="20">
  10.         <TextBlock Grid.Row="0" Grid.Column="0"
  11.                    Margin="0 0 5 0"
  12.                    Text="文本:"/>
  13.         <TextBox Grid.Row="0" Grid.Column="1"
  14.                  Name="InputText" Height="100"/>
  15.         <StackPanel Grid.Row="2" Grid.Column="1"
  16.                     Orientation="Horizontal" HorizontalAlignment="Right"
  17.                     VerticalAlignment="Bottom">
  18.             <Button Name="OkBtn" Margin="0 0 5 0"
  19.                     IsDefault="True" Click="OkBtn_OnClick"
  20.                     Content="确认"/>
  21.             <Button Name="CancelBtn" IsCancel="True">取消</Button>
  22.         </StackPanel>
  23.     </Grid>
  24. </Window>
复制代码

  • MinHeight:设置对话框的最小高度。
  • MinWidth:设置对话框的最小宽度。
  • SizeToContent:设置对话框的大小,根据内容自动调整窗口的高宽。
  • ResizeMode:设置对话框的可调整大小模式,这里设置为不可调整大小。
  • WindowStartupLocation:设置对话框的启动位置,这里设置为居中显示。
点击事件:
  1. private void OkBtn_OnClick(object sender, RoutedEventArgs e) =>
  2.     DialogResult = true;
  3. private void CancelBtn_OnClick(object sender, RoutedEventArgs e) =>
  4.     DialogResult = false;
复制代码
总结

在这其中最大的难题,就是在不同的 View 和 ViewModel 之间,如何方便快捷地传递数据,而不是依靠集成对应的实例,或者使用依赖注入的方式,变相的增加耦合。
现在有了消息机制,这个问题就迎刃而解了。消息机制是中介者模式的一种实现,也像事件,当我们订阅事件时,可以传递一些自定义的操作,然后就是等待事件触发。
还有另一个问题,WPF 不像 Avalonia,可以为对话框返回值设定类型,只能返回 bool 类型,要想获取数据,需要花点心思,手动获取对话框的属性值。
参考


  • 对话框概述 - WPF | Microsoft Learn
  • 信使 Messengers | CommunityToolkit - 从入门到精通
  • MusicStore | Avalonia Samples

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册