成都威哥 發表於 2026-3-28 09:50:00

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

<p>本篇笔记主要记录如何在 MVVM 模式下,通过消息机制获取自定义对话框的数据。</p>
<p>这是在实现一个增删查改的小页面时出现的需求,将原本添加的功能单独放在对话框中,进而精简主界面。</p>
<p>运行环境:</p>
<ul>
<li>.NET 10</li>
<li>CommunityToolkit.Mvvm 8.4.0</li>
</ul>
<h2 id="实现步骤">实现步骤</h2>
<p>先定义一个带回复功能的消息类,用作媒介,传递不同的 View 和 ViewModel 之间的数据,然后分别在 View、ViewModel 中订阅、发送消息。</p>
<p>这个消息类的作用是通知接收者该打开对话框,并在对话框关闭后将数据返回。</p>
<div class="mermaid">sequenceDiagram
    participant VM as ViewModel
    participant Messenger as 消息中介
    participant View as View
    participant Dialog as 对话框

    VM-&gt;&gt;Messenger: 发送打开对话框消息
    Messenger-&gt;&gt;View: 触发消息处理
    View-&gt;&gt;Dialog: 创建并显示对话框
    Note over Dialog: 用户操作并关闭
    Dialog--&gt;&gt;View: 返回数据
    View--&gt;&gt;Messenger: 回复消息
    Messenger--&gt;&gt;VM: 返回结果
    VM-&gt;&gt;VM: 处理返回数据
</div><h3 id="步骤-1定义带回复能力的消息类">步骤 1:定义带回复能力的消息类</h3>
<p>定义一个普通的类,继承 CommunityToolkit.mvvm 的 <code>RequestMessage</code>,使 <code>Send</code> 方法拥有一个返回值,而这个返回值就是消息订阅者回复的数据。</p>
<pre><code class="language-csharp">public class OpenDialogMessage : RequestMessage&lt;string&gt; { }
</code></pre>
<p>此时,消息的接收者也能够通过消息上的 <code>Reply</code> 方法回复消息的发送者。</p>
<blockquote>
<p>如果需要了解更多的消息类型,可以到 此网站 查阅。</p>
</blockquote>
<h3 id="步骤-2主界面-viewmodel-发送-打开对话框-消息">步骤 2:主界面 ViewModel 发送 "打开对话框" 消息</h3>
<p>创建一个命令,绑定到对应的按钮或其他触发器上,当触发后,会发送"打开对话框"的消息,并接收订阅者的回复,然后打印在控制台中。</p>
<pre><code class="language-csharp">public partial class MainWindowViewModel : ObservableObject
{
   
    private void OpenDialog()
    {
      var result = WeakReferenceMessenger
            .Default.Send&lt;OpenDialogMessage&gt;();
      
      if (result != null)
      {
            Console.Out.WriteLine("result = {0}", result.Response);
      }
    }
}
</code></pre>
<h3 id="步骤-3主界面后置代码订阅消息">步骤 3:主界面后置代码订阅消息</h3>
<pre><code class="language-csharp">public partial class MainWindow : Window
{
    public MainWindow()
    {
      InitializeComponent();

      DataContext = new MainWindowViewModel();

      WeakReferenceMessenger.Default
            .Register&lt;MainWindow, OpenDialogMessage&gt;(this, (receiver, message) =&gt; { });
    }
}
</code></pre>
<h3 id="步骤-4为对话框添加关闭事件并显示对话框">步骤 4:为对话框添加关闭事件,并显示对话框</h3>
<p>创建消息处理的匿名函数,当接收到消息后调用。在其中创建对话框,并添加窗口关闭事件,等到窗口关闭时,就将对话框的数据回复给发送者。</p>
<pre><code class="language-csharp">public partial class MainWindow : Window
{
    public MainWindow()
    {
      // --- 省略已有代码 ---
      
      WeakReferenceMessenger.Default
            .Register&lt;MainWindow, OpenDialogMessage&gt;(this, (receiver, message) =&gt;
      {
            var dialog = new DialogWindow();
            
            // 添加窗口关闭事件
            dialog.Closed += (s, e) =&gt;
            {
                var w = s as DialogWindow;
                message.Reply(w?.InputText.Text);
            };

            // 打开模态对话框
            dialog.ShowDialog();
      });
    }
}
</code></pre>
<h3 id="步骤-5创建对话框界面">步骤 5:创建对话框界面</h3>
<p>在这个界面中,需要注意的有三点:</p>
<ul>
<li>TextBox 的输入内容,是要获取的数据,所以为其添加了一个 <code>Name</code> 属性,方便后续调用。</li>
<li>两个 <code>Button</code> 分别设置了 <code>IsDefault</code> 和 <code>IsCancel</code> 属性:
<ul>
<li><code>IsDefault</code>: 设置 Button 是否为<strong>默认</strong>按钮。 用户通过按 <code>Enter</code> 键调用默认按钮。</li>
<li><code>IsCancel</code>:设置 Button 是否为<strong>取消</strong>按钮。 用户可以通过按 <code>ESC</code> 键调用取消按钮。</li>
</ul>
</li>
<li>为两个 <code>Button</code> 分别创建点击事件,来设置 <code>DialogResult</code> 的值。</li>
</ul>
<blockquote>
<p>由于这里开启的是模态对话框,只要设置了对话框的结果值(<code>DialogResult</code>),窗口就会自动关闭。</p>
</blockquote>
<pre><code class="language-xml">&lt;Window --- 省略已有代码 ---
      MinHeight="200" MinWidth="300"
      SizeToContent="WidthAndHeight"
      ResizeMode="NoResize"
      WindowStartupLocation="CenterOwner"&gt;
   
    &lt;Grid RowDefinitions="Auto, *"
          ColumnDefinitions="Auto, *"
          Margin="20"&gt;
      &lt;TextBlock Grid.Row="0" Grid.Column="0"
                   Margin="0 0 5 0"
                   Text="文本:"/&gt;
      &lt;TextBox Grid.Row="0" Grid.Column="1"
               Name="InputText" Height="100"/&gt;

      &lt;StackPanel Grid.Row="2" Grid.Column="1"
                  Orientation="Horizontal" HorizontalAlignment="Right"
                  VerticalAlignment="Bottom"&gt;
            &lt;Button Name="OkBtn" Margin="0 0 5 0"
                  IsDefault="True" Click="OkBtn_OnClick"
                  Content="确认"/&gt;
            &lt;Button Name="CancelBtn" IsCancel="True"&gt;取消&lt;/Button&gt;
      &lt;/StackPanel&gt;
    &lt;/Grid&gt;
&lt;/Window&gt;
</code></pre>
<ul>
<li><code>MinHeight</code>:设置对话框的最小高度。</li>
<li><code>MinWidth</code>:设置对话框的最小宽度。</li>
<li><code>SizeToContent</code>:设置对话框的大小,根据内容自动调整窗口的高宽。</li>
<li><code>ResizeMode</code>:设置对话框的可调整大小模式,这里设置为不可调整大小。</li>
<li><code>WindowStartupLocation</code>:设置对话框的启动位置,这里设置为居中显示。</li>
</ul>
<p>点击事件:</p>
<pre><code class="language-csharp">private void OkBtn_OnClick(object sender, RoutedEventArgs e) =&gt;
    DialogResult = true;

private void CancelBtn_OnClick(object sender, RoutedEventArgs e) =&gt;
    DialogResult = false;
</code></pre>
<h2 id="总结">总结</h2>
<p>在这其中最大的难题,就是在不同的 View 和 ViewModel 之间,如何方便快捷地传递数据,而不是依靠集成对应的实例,或者使用依赖注入的方式,变相的增加耦合。</p>
<p>现在有了消息机制,这个问题就迎刃而解了。消息机制是中介者模式的一种实现,也像事件,当我们订阅事件时,可以传递一些自定义的操作,然后就是等待事件触发。</p>
<p>还有另一个问题,WPF 不像 Avalonia,可以为对话框返回值设定类型,只能返回 bool 类型,要想获取数据,需要花点心思,手动获取对话框的属性值。</p>
<h2 id="参考">参考</h2>
<ul>
<li>对话框概述 - WPF | Microsoft Learn</li>
<li>信使 Messengers | CommunityToolkit - 从入门到精通</li>
<li>MusicStore | Avalonia Samples</li>
</ul><br><br>
来源:https://www.cnblogs.com/qisork/p/19785837
頁: [1]
查看完整版本: 笔记:WPF MVVM 模式下通过消息机制获取自定义对话框数据