























DialogService(对话框服务)是一种使用抽象层来显示对话框的方法。
ViewModel将显示对话框的责任委托给DialogService,只需向服务提供显示所需的数据即可。
DialogService拥有显示对话框的职责,因此我们可以将ViewModel与 System.Windows解耦,避免从View到ViewModel之间的引用。
在单元测试中,我们可以注入一个虚假的对话框服务实例,而不是显示实际的对话框,并使用我们的虚假对象进行预留和模拟。
在前面介绍MVVM进阶内容时,介绍过为什么需要使用对话框服务
可以参考下面的链接
https://www.cnblogs.com/zhaotianff/p/18816257
在Prism中,框架提供了DialogService功能,所以我们只需要关注View和ViewModel的功能就可以了,不用再关心对话框的创建、返回值如何获取及生命周期等细节问题。
在Prism中,提供了一个IDialogService接口,可以控制对话框的显示
IDialogService.cs
1 public interface IDialogService 2 { 3 /// <summary> 4 /// 显示对话框 5 /// </summary> 6 /// <param name="name">注入到容器中的View名称(要求唯一).</param> 7 /// <param name="parameters">传递到对话框的参数</param> 8 /// <param name="callback">对话框显示完成后的回调</param> 9 /// <example> 10 /// 示例(传递2个参数) 11 /// <code> 12 /// var parameters = new DialogParameters 13 /// { 14 /// { "title", "Connection Lost!" }, 15 /// { "message", "We seem to have lost network connectivity" } 16 /// }; 17 /// _dialogService.ShowDialog("DemoDialog", parameters, <paramref name="callback"/>: null); 18 /// </code> 19 /// </example> 20 void ShowDialog(string name, IDialogParameters parameters, DialogCallback callback); 21 }
除了IDialogService中定义的ShowDialog函数外,Prism框架还在一个IDialogServiceCompatExtensions扩展类中定义了一些扩展函数
如下所示:
IDialogServiceCompatExtensions.cs
1 /// <summary> 2 /// IDialogService 的扩展方法 3 /// </summary> 4 public static class IDialogServiceCompatExtensions 5 { 6 #if !UNO_WINUI 7 /// <summary> 8 /// 显示一个非模态对话框。 9 /// </summary> 10 /// <param name="dialogService">对话框服务实例</param> 11 /// <param name="name">要显示的对话框名称。</param> 12 /// <param name="parameters">要传递给对话框的参数。</param> 13 /// <param name="callback">对话框关闭时要执行的操作。</param> 14 public static void Show(this IDialogService dialogService, string name, IDialogParameters parameters, Action<IDialogResult> callback) 15 { 16 parameters = EnsureShowNonModalParameter(parameters); 17 dialogService.ShowDialog(name, parameters, new DialogCallback().OnClose(callback)); 18 } 19 20 /// <summary> 21 /// 显示一个非模态对话框。 22 /// </summary> 23 /// <param name="dialogService">对话框服务实例</param> 24 /// <param name="name">要显示的对话框名称。</param> 25 /// <param name="parameters">要传递给对话框的参数。</param> 26 /// <param name="callback">对话框关闭时要执行的操作。</param> 27 /// <param name="windowName">在 IContainerRegistry 中注册的宿主窗口名称。</param> 28 public static void Show(this IDialogService dialogService, string name, IDialogParameters parameters, Action<IDialogResult> callback, string windowName) 29 { 30 parameters = EnsureShowNonModalParameter(parameters); 31 32 if(!string.IsNullOrEmpty(windowName)) 33 parameters.Add(KnownDialogParameters.WindowName, windowName); 34 35 dialogService.ShowDialog(name, parameters, new DialogCallback().OnClose(callback)); 36 } 37 38 /// <summary> 39 /// 显示一个非模态对话框。 40 /// </summary> 41 /// <param name="dialogService">对话框服务实例</param> 42 /// <param name="name">要显示的对话框名称。</param> 43 public static void Show(this IDialogService dialogService, string name) 44 { 45 var parameters = EnsureShowNonModalParameter(null); 46 dialogService.Show(name, parameters, null); 47 } 48 49 /// <summary> 50 /// 显示一个非模态对话框。 51 /// </summary> 52 /// <param name="dialogService">对话框服务实例</param> 53 /// <param name="name">要显示的对话框名称。</param> 54 /// <param name="callback">对话框关闭时要执行的操作。</param> 55 public static void Show(this IDialogService dialogService, string name, Action<IDialogResult> callback) 56 { 57 var parameters = EnsureShowNonModalParameter(null); 58 dialogService.Show(name, parameters, callback); 59 } 60 61 private static IDialogParameters EnsureShowNonModalParameter(IDialogParameters parameters) 62 { 63 parameters ??= new DialogParameters(); 64 65 if (!parameters.ContainsKey(KnownDialogParameters.ShowNonModal)) 66 parameters.Add(KnownDialogParameters.ShowNonModal, true); 67 68 return parameters; 69 } 70 #endif 71 }
这一步我们创建一个用户控件,根据自己想要显示的对话框效果进行布局即可
DialogView.xaml
1 <Grid x:Name="LayoutRoot" Margin="5"> 2 <Grid.RowDefinitions> 3 <RowDefinition /> 4 <RowDefinition Height="Auto" /> 5 </Grid.RowDefinitions> 6 7 <TextBlock Text="{Binding Message}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="0" TextWrapping="Wrap" /> 8 <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0" Grid.Row="1" > 9 <Button Command="{Binding CloseDialogCommand}" CommandParameter="true" Content="确认" Width="75" Height="25" IsDefault="True" /> 10 <Button Command="{Binding CloseDialogCommand}" CommandParameter="false" Content="取消" Width="75" Height="25" Margin="10,0,0,0" IsCancel="True" /> 11 </StackPanel> 12 </Grid>
布局效果如下所示

接下来我们创建对话框View绑定的ViewModel
在此之前我们先了解一下IDialogAware接口
IDialogAware接口是弹窗视图模型(ViewModel)必须实现的接口,用于处理弹窗的参数接收、结果返回、关闭确认等逻辑。
定义如下
IDialogAware.cs
1 // 2 // 摘要: 3 // 为视图模型(ViewModels)提供对话框功能和事件的接口。 4 public interface IDialogAware 5 { 6 // 7 // 摘要: 8 // 将显示在窗口标题栏中的对话框标题。 9 string Title { get; } 10 11 // 12 // 摘要: 13 // 指示 Prism.Services.Dialogs.IDialogWindow 关闭该对话框。 14 event Action<IDialogResult> RequestClose; 15 16 // 17 // 摘要: 18 // 确定对话框是否可以关闭。 19 // 20 // 返回结果: 21 // 如果为 true,则对话框可以关闭。如果为 false,则对话框将不会关闭。 22 bool CanCloseDialog(); 23 24 // 25 // 摘要: 26 // 当对话框关闭时调用。 27 void OnDialogClosed(); 28 29 // 30 // 摘要: 31 // 当对话框打开时调用。 32 // 33 // 参数: 34 // parameters: 35 // 传递给对话框的参数。 36 void OnDialogOpened(IDialogParameters parameters); 37 }
其中RequestClose事件包含了一个IDialogResult类型,它用于返回对话框的结果。
该类型定义如下:
IDialogResult.cs
1 /// <summary> 2 /// 一个 <see cref="IDialogResult"/> 类型,包含来自对话框的 <see cref="IDialogParameters"/> 参数 3 /// 以及对话框的 <see cref="ButtonResult"/> 结果。 4 /// </summary> 5 public class DialogResult : IDialogResult 6 { 7 /// <summary> 8 /// 来自对话框的参数。 9 /// </summary> 10 public IDialogParameters Parameters { get; private set; } = new DialogParameters(); 11 12 /// <summary> 13 /// 对话框的结果。() 14 /// </summary> 15 public ButtonResult Result { get; private set; } = ButtonResult.None; 16 17 /// <summary> 18 /// 初始化 <see cref="DialogResult"/> 类的新实例。 19 /// </summary> 20 public DialogResult() { } 21 22 /// <summary> 23 /// 初始化 <see cref="DialogResult"/> 类的新实例。 24 /// </summary> 25 /// <param name="result">对话框的结果。</param> 26 public DialogResult(ButtonResult result) 27 { 28 Result = result; 29 } 30 31 /// <summary> 32 /// 初始化 <see cref="DialogResult"/> 类的新实例。 33 /// </summary> 34 /// <param name="result">对话框的结果。</param> 35 /// <param name="parameters">来自对话框的参数。</param> 36 public DialogResult(ButtonResult result, IDialogParameters parameters) 37 { 38 Result = result; 39 Parameters = parameters; 40 } 41 }
现在我们可以创建ViewModel
DialogViewModel.cs
1 public class DialogViewModel : BindableBase, IDialogAware 2 { 3 /// <summary> 4 /// 确认/取消 绑定的命令,通过参数来判断true/false 5 /// </summary> 6 public DelegateCommand<string> CloseDialogCommand { get; private set; } 7 8 public DialogViewModel() 9 { 10 CloseDialogCommand = new DelegateCommand<string>(CloseDialog); 11 } 12 13 private string _message; 14 15 public string Message 16 { 17 get { return _message; } 18 set { SetProperty(ref _message, value); } 19 } 20 21 private string _title = "通知消息"; 22 23 public string Title 24 { 25 get { return _title; } 26 set { SetProperty(ref _title, value); } 27 } 28 29 public event Action<IDialogResult> RequestClose; 30 31 protected virtual void CloseDialog(string parameter) 32 { 33 ButtonResult result = ButtonResult.None; 34 35 //判断选择了“确认”还是“取消” 36 if (parameter?.ToLower() == "true") 37 result = ButtonResult.OK; 38 else if (parameter?.ToLower() == "false") 39 result = ButtonResult.Cancel; 40 41 RaiseRequestClose(new DialogResult(result)); 42 } 43 44 public virtual void RaiseRequestClose(IDialogResult dialogResult) 45 { 46 RequestClose.Invoke(dialogResult); 47 } 48 49 public virtual bool CanCloseDialog() 50 { 51 return true; 52 } 53 54 public virtual void OnDialogClosed() 55 { 56 //对话框关闭时调用 57 } 58 59 public virtual void OnDialogOpened(IDialogParameters parameters) 60 { 61 //从参数中获取消息 62 Message = parameters.GetValue<string>("message"); 63 } 64 }
现在我们可以在Shell中使用DialogService来显示对话框了。
在此之前,还需要了解DialogParameters类型,它用于传递参数到对话框。本质上来说,它是一个键值对列表。使用方法和Dictionary<string,object>基本类似 ,这里不做详细介绍。
ShellViewModel.cs
1 public class ShellViewModel : BindableBase 2 { 3 private string dialogResultContent; 4 5 public string DialogResultContent 6 { 7 get => dialogResultContent; 8 set => SetProperty(ref dialogResultContent, value); 9 } 10 11 public DelegateCommand ShowDialogCommand { get; private set; } 12 13 private IDialogService dialogService; 14 15 public ShellViewModel(IDialogService dialogService) 16 { 17 this.dialogService = dialogService; 18 19 ShowDialogCommand = new DelegateCommand(ShowDialog); 20 } 21 22 /// <summary> 23 /// 显示对话框 24 /// </summary> 25 private void ShowDialog() 26 { 27 DialogParameters keyValuePairs = new DialogParameters(); 28 keyValuePairs.Add("message", "这是用于显示在对话框上的消息"); 29 this.dialogService.Show(nameof(DialogView), keyValuePairs, HandleDialogResult); 30 } 31 32 /// <summary> 33 /// 处理对话框返回结果 34 /// </summary> 35 /// <param name="dialogResult"></param> 36 private void HandleDialogResult(IDialogResult dialogResult) 37 { 38 //显示对话框结果 39 DialogResultContent = dialogResult.Result.ToString(); 40 } 41 }
万事具备,只欠东风。
此时我们只需要注册View类型,就可以正常显示这个对话框
1 public partial class App : PrismApplication 2 { 3 protected override Window CreateShell() 4 { 5 return Container.Resolve<Shell>(); 6 } 7 8 protected override void RegisterTypes(IContainerRegistry containerRegistry) 9 { 10 //注册对话框View类型 11 containerRegistry.RegisterDialog<DialogView>(); 12 } 13 }

通过前面的示例我们可以发现,以前在WPF中创建对话框都是直接创建一个Window类型,但是现在创建的是一个UserControl类型。
我们都知道UserControl是无法直接被显示的,这里实际上是Prism帮我们创建了一个宿主窗口。
在前面介绍Bootstrapper在初始化时,会初始化Prism提供的服务
其中就包含了这样一行代码
1 containerRegistry.Register<IDialogWindow, DialogWindow>(); //default dialog host
这个DialogWindow就是Prism用于显示对话框的宿主窗口,它的定义如下
src\Wpf\Prism.Wpf\Services\Dialogs\DialogWindow.xaml
1 <Window x:Class="Prism.Services.Dialogs.DialogWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 Title="{Binding Title}" 5 WindowStartupLocation="CenterOwner"> 6 <Window.Style> 7 <Style TargetType="{x:Type Window}" > 8 <Setter Property="SizeToContent" Value="WidthAndHeight" /> 9 </Style> 10 </Window.Style> 11 12 </Window>
如果我们需要自定义宿主窗口的样式,可以通过下面两种方式
1 <UserControl ...> 2 <!--定义宿主窗口样式--> 3 <prism:Dialog.WindowStyle> 4 <Style TargetType="Window"> 5 <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" /> 6 <Setter Property="ResizeMode" Value="NoResize"/> 7 <Setter Property="ShowInTaskbar" Value="False"/> 8 <Setter Property="SizeToContent" Value="WidthAndHeight"/> 9 </Style> 10 </prism:Dialog.WindowStyle> 11 <Grid x:Name="LayoutRoot" Margin="5"> 12 ... 13 </Grid> 14 </UserControl>
运行效果

这种方式是借助IDialogService的扩展函数来实现的,也就是下面这种形式
1 public static void Show(this IDialogService dialogService, string name, IDialogParameters parameters, Action<IDialogResult> callback, string windowName)
实现步骤如下
1、创建自定义宿主窗口
CustomDialog.xaml
1 <tianxia:BlurWindow x:Class="CustomDialogWindow.Views.CustomDialog" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:CustomDialogWindow.Views" 7 xmlns:tianxia="clr-namespace:TianXiaTech;assembly=BlurWindow" 8 mc:Ignorable="d" 9 Title="CustomDialog" Height="450" Width="800"> 10 <tianxia:BlurWindow.Background> 11 <SolidColorBrush Color="White" Opacity=".4"></SolidColorBrush> 12 </tianxia:BlurWindow.Background> 13 <Grid> 14 15 </Grid> 16 </tianxia:BlurWindow>
说明:这里我使用了blurwindow包,用于窗口美化
2、在窗口后台代码中实现IDialogWindow接口
在此之前我们了解一下IDialogWindow接口,在Prism 框架中,IDialogWindow 是用于定义对话框窗口的核心接口,它是 Prism 对话框服务(IDialogService)的基础组件,目的是标准化对话框的行为。
它可以灵活自定义对话框窗口的样式和逻辑,同时兼容 Prism 的对话框生命周期管理。
IDialogWindow定义如下:
IDialogWindow.cs
1 public interface IDialogWindow 2 { 3 // 4 // 摘要: 5 // 对话框内容。 6 object Content { get; set; } 7 8 // 9 // 摘要: 10 // 窗口的所有者。 11 Window Owner { get; set; } 12 13 // 14 // 摘要: 15 // 窗口的数据上下文。 16 // 17 // 备注: 18 // 数据上下文必须实现 Prism.Services.Dialogs.IDialogAware 接口。 19 object DataContext { get; set; } 20 21 // 22 // 摘要: 23 // 对话框的结果。 24 IDialogResult Result { get; set; } 25 26 // 27 // 摘要: 28 // 窗口样式。 29 Style Style { get; set; } 30 31 // 32 // 摘要: 33 // 窗口加载完成时触发。 34 event RoutedEventHandler Loaded; 35 36 // 37 // 摘要: 38 // 窗口关闭后触发。 39 event EventHandler Closed; 40 41 // 42 // 摘要: 43 // 窗口正在关闭时触发。 44 event CancelEventHandler Closing; 45 46 // 47 // 摘要: 48 // 关闭窗口。 49 void Close(); 50 51 // 52 // 摘要: 53 // 显示非模态对话框。 54 void Show(); 55 56 // 57 // 摘要: 58 // 显示模态对话框。 59 bool? ShowDialog(); 60 }
CustomDialog.xaml.cs
1 public partial class CustomDialog : TianXiaTech.BlurWindow, IDialogWindow 2 { 3 public CustomDialog() 4 { 5 InitializeComponent(); 6 } 7 8 public IDialogResult Result { get; set; } 9 }
3、注册自定义宿主窗口
1 protected override void RegisterTypes(IContainerRegistry containerRegistry) 2 { 3 //注册自定义宿主窗口 4 containerRegistry.RegisterDialogWindow<CustomDialog>(); 5 6 //注册对话框View类型 7 containerRegistry.RegisterDialog<DialogView>(); 8 }
说明:注册宿主窗口之后,显示对话框的过程参考文章前面的内容即可
运行结果

https://github.com/zhaotianff/WPF-MVVM-Beginner/tree/main/16_Prism_DialogService
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。