笔记: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->>Messenger: 发送打开对话框消息
Messenger->>View: 触发消息处理
View->>Dialog: 创建并显示对话框
Note over Dialog: 用户操作并关闭
Dialog-->>View: 返回数据
View-->>Messenger: 回复消息
Messenger-->>VM: 返回结果
VM->>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<string> { }
</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<OpenDialogMessage>();
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<MainWindow, OpenDialogMessage>(this, (receiver, message) => { });
}
}
</code></pre>
<h3 id="步骤-4为对话框添加关闭事件并显示对话框">步骤 4:为对话框添加关闭事件,并显示对话框</h3>
<p>创建消息处理的匿名函数,当接收到消息后调用。在其中创建对话框,并添加窗口关闭事件,等到窗口关闭时,就将对话框的数据回复给发送者。</p>
<pre><code class="language-csharp">public partial class MainWindow : Window
{
public MainWindow()
{
// --- 省略已有代码 ---
WeakReferenceMessenger.Default
.Register<MainWindow, OpenDialogMessage>(this, (receiver, message) =>
{
var dialog = new DialogWindow();
// 添加窗口关闭事件
dialog.Closed += (s, e) =>
{
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"><Window --- 省略已有代码 ---
MinHeight="200" MinWidth="300"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="Auto, *"
ColumnDefinitions="Auto, *"
Margin="20">
<TextBlock Grid.Row="0" Grid.Column="0"
Margin="0 0 5 0"
Text="文本:"/>
<TextBox Grid.Row="0" Grid.Column="1"
Name="InputText" Height="100"/>
<StackPanel Grid.Row="2" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Right"
VerticalAlignment="Bottom">
<Button Name="OkBtn" Margin="0 0 5 0"
IsDefault="True" Click="OkBtn_OnClick"
Content="确认"/>
<Button Name="CancelBtn" IsCancel="True">取消</Button>
</StackPanel>
</Grid>
</Window>
</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) =>
DialogResult = true;
private void CancelBtn_OnClick(object sender, RoutedEventArgs e) =>
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]