MVVM架构下如何在ViewModel中关闭自定义按钮的ContentDialog(不传入实例)
在MVVM架构中无耦合关闭自定义ContentDialog
问题背景
开发自定义ContentDialog(未使用默认按钮),需要在ViewModel中触发关闭操作,但不想直接将ContentDialog实例传入ViewModel以保持MVVM的解耦原则。
原ContentDialog XAML代码:
<Grid HorizontalAlignment="Center"> <Grid.RowDefinitions> <RowDefinition Height="auto" /> <RowDefinition Height="1*" /> <RowDefinition Height="auto" /> </Grid.RowDefinitions> <Grid Grid.Row="0" Height="32"> <Button Width="32px" Height="32px" HorizontalAlignment="Right" Command="{x:Bind ViewModel.CancelCommand, Mode=OneWay}" CommandParameter="{Binding ElementName=addDeviceDialog}" Style="{ThemeResource CommandBarFlyoutEllipsisButtonStyle}"> <FontIcon Width="16px" Height="16px" FontSize="10px" Glyph="" /> </Button> </Grid> <Grid Grid.Row="1"> </Grid> <Grid Grid.Row="2" Margin="0 20 0 0"> <Button Width="219" HorizontalAlignment="Right" Command="{x:Bind ViewModel.AddCommand}" Content="Add" Style="{ThemeResource AccentButtonStyle}" /> </Grid> </Grid>
原ViewModel代码:
public partial class AddDeviceDialogViewModel : ObservableRecipient { public IRelayCommand AddCommand { get; } public IRelayCommand CancelCommand { get; } public AddDeviceDialogViewModel() { AddCommand = new RelayCommand(AddDevice); CancelCommand = new RelayCommand(Cancel); } public void AddDevice() { // 业务逻辑 if (AddedSuccessfully) { // 需要关闭对话框 } else { // 显示验证错误 } } public async void Cancel() { // 需要关闭对话框 } }
方案1:使用事件通知(最符合MVVM解耦)
通过ViewModel定义关闭请求事件,View层订阅该事件并执行关闭操作,ViewModel无需知晓View的具体实现。
步骤1:修改ViewModel添加关闭事件
public partial class AddDeviceDialogViewModel : ObservableRecipient { // 定义关闭请求事件 public event EventHandler? CloseRequested; public IRelayCommand AddCommand { get; } public IRelayCommand CancelCommand { get; } public AddDeviceDialogViewModel() { AddCommand = new RelayCommand(AddDevice); CancelCommand = new RelayCommand(Cancel); } public void AddDevice() { // 业务逻辑 if (AddedSuccessfully) { // 触发关闭事件 CloseRequested?.Invoke(this, EventArgs.Empty); } else { // 显示验证错误 } } public void Cancel() { // 触发关闭事件 CloseRequested?.Invoke(this, EventArgs.Empty); } }
步骤2:在ContentDialog后台代码订阅事件
假设你的ContentDialog名为AddDeviceDialog,在其代码后台(AddDeviceDialog.xaml.cs)中:
public sealed partial class AddDeviceDialog : ContentDialog { public AddDeviceDialogViewModel ViewModel { get; } = new AddDeviceDialogViewModel(); public AddDeviceDialog() { this.InitializeComponent(); // 订阅ViewModel的关闭事件 ViewModel.CloseRequested += ViewModel_CloseRequested; } private void ViewModel_CloseRequested(object? sender, EventArgs e) { // 关闭对话框 this.Hide(); } // 记得在对话框关闭时取消订阅,避免内存泄漏 protected override void OnClosed(ContentDialogClosedEventArgs args) { base.OnClosed(args); ViewModel.CloseRequested -= ViewModel_CloseRequested; } }
方案2:使用带参数的RelayCommand(轻量解耦)
让命令接收ContentDialog实例作为参数,但ViewModel仅调用Hide()方法,不持有实例引用,避免强耦合。
步骤1:修改ViewModel的命令为带参数
public partial class AddDeviceDialogViewModel : ObservableRecipient { // 改为带参数的RelayCommand public IRelayCommand<ContentDialog> AddCommand { get; } public IRelayCommand<ContentDialog> CancelCommand { get; } public AddDeviceDialogViewModel() { AddCommand = new RelayCommand<ContentDialog>(AddDevice); CancelCommand = new RelayCommand<ContentDialog>(Cancel); } public void AddDevice(ContentDialog? dialog) { // 业务逻辑 if (AddedSuccessfully && dialog != null) { dialog.Hide(); } else { // 显示验证错误 } } public void Cancel(ContentDialog? dialog) { dialog?.Hide(); } }
步骤2:调整XAML中的CommandParameter绑定
<!-- 右上角关闭按钮 --> <Button Width="32px" Height="32px" HorizontalAlignment="Right" Command="{x:Bind ViewModel.CancelCommand, Mode=OneWay}" CommandParameter="{Binding ElementName=addDeviceDialog}" Style="{ThemeResource CommandBarFlyoutEllipsisButtonStyle}"> <FontIcon Width="16px" Height="16px" FontSize="10px" Glyph="" /> </Button> <!-- Add按钮 --> <Button Width="219" HorizontalAlignment="Right" Command="{x:Bind ViewModel.AddCommand, Mode=OneWay}" CommandParameter="{Binding ElementName=addDeviceDialog}" Content="Add" Style="{ThemeResource AccentButtonStyle}" />
方案3:使用Messenger消息传递(适合复杂场景)
利用MVVM Toolkit的IMessenger进行跨组件通信,ViewModel发送关闭消息,View接收后执行关闭操作。
步骤1:定义关闭消息类
public class CloseDialogMessage { // 可添加标识区分不同对话框(如果有多个) public string? DialogId { get; set; } }
步骤2:ViewModel中发送消息
public partial class AddDeviceDialogViewModel : ObservableRecipient { private readonly IMessenger _messenger; public IRelayCommand AddCommand { get; } public IRelayCommand CancelCommand { get; } // 注入IMessenger(需要依赖注入支持,或直接使用WeakReferenceMessenger.Default) public AddDeviceDialogViewModel(IMessenger messenger) { _messenger = messenger; AddCommand = new RelayCommand(AddDevice); CancelCommand = new RelayCommand(Cancel); } public void AddDevice() { if (AddedSuccessfully) { // 发送关闭消息 _messenger.Send(new CloseDialogMessage { DialogId = "AddDeviceDialog" }); } else { // 显示验证错误 } } public void Cancel() { _messenger.Send(new CloseDialogMessage { DialogId = "AddDeviceDialog" }); } }
步骤3:ContentDialog后台注册消息处理
public sealed partial class AddDeviceDialog : ContentDialog { private readonly IMessenger _messenger; public AddDeviceDialogViewModel ViewModel { get; } public AddDeviceDialog(IMessenger messenger) { _messenger = messenger; InitializeComponent(); ViewModel = new AddDeviceDialogViewModel(messenger); // 注册消息处理 _messenger.Register<CloseDialogMessage>(this, (r, m) => { if (m.DialogId == "AddDeviceDialog") { this.Hide(); } }); } protected override void OnClosed(ContentDialogClosedEventArgs args) { base.OnClosed(args); // 取消注册 _messenger.Unregister<CloseDialogMessage>(this); } }
内容的提问来源于stack exchange,提问作者RezaNoei
相关产品推荐
相关产品推荐

