7.Prism框架之对话框服务

时间:2024-04-26 07:10:43

文章目录

      • 一. 目标
      • 二. 技能介绍
        • ① 什么是Dialog?
        • ② Prism中Dialog的实现方式
        • ③ Dialog使用案例一 (修改器)
        • ④ Dialog使用案例2(异常显示窗口)

一. 目标

  • 1. 什么是Dialog?
  • 2. 传统的Dialog如何实现?
  • 3. Prism中Dialog实现方式
  • 4. 使用Dialog实现一个异常信息弹出框

二. 技能介绍

① 什么是Dialog?

Dialog通常是一种特殊类型的窗口,用于和用户进行交互,一般是用来从用户那里接收数据,显示信息,或者是允许用户执行特定任务.窗口分为两种一种是模态窗口(Modal Window),一种是非模态窗口(Non-Modal WIndow)

  • 模态窗口
    模态窗口就是阻止和其他的控件进行交互的窗口,必须是处理完这个窗口之后,才能和其他的窗口进行交互,这个窗口脚模态窗口.一般用于比较重要的交互,比如保存文件,确认删除等.传统显示的是时候使用ShowDialog(),有返回值.

  • 非模态窗口
    可以在不关闭这个窗口的情况下和其他的控件进行交互,一般适用于不需要立即决策或者响应的情况,如工具箱,提示信息等.传统显示的时候使用Show(),无返回值.

模态窗口的例子

namespace DialogSimple.ViewModels
{
    public class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            ShowDialogCommand = new Command(DoShowDialog);
        }

        private void DoShowDialog(object modalType)
        {
            if (modalType is string modal)
            {
                switch (modal)
                {
                    case "Modal":
                        var modalDialog = new ModalDialog();
                        bool? result = modalDialog.ShowDialog();
                        if (result == true)
                        {
                            System.Diagnostics.Debug.WriteLine("模态对话框返回值为true,执行保存操作!");
                        }
                        else
                        {
                            System.Diagnostics.Debug.WriteLine("模态对话框返回值为false,不执行保存操作!");
                        }
                        break;
                }
            }
        }

        public Command ShowDialogCommand { get; private set; }
    }
}

这里注意一个问题就是这里的result是一个窗口的返回值,为bool?类型,这个返回值是怎么返回的呢?

这个返回值是通过窗口的视图类中的一个DialogResult属性,可以在事件处理的时候设置其返回值.

namespace DialogSimple
{
    /// <summary>
    /// ModalDialog.xaml 的交互逻辑
    /// </summary>
    public partial class ModalDialog : Window
    {
        public ModalDialog()
        {
            InitializeComponent();
        }

        private void BtnOk_Click(object sender, RoutedEventArgs e)
        {
            DialogResult = true;
        }

        private void BtnCancel_Click(object sender, RoutedEventArgs e)
        {
            DialogResult = false;
        }
    }
}

现在我有一个疑问,就是这里我们窗口的按钮实现的方式比较简单,如果比较复杂,比如按钮的处理逻辑放到对应的ViewModel中去处理的时候,这个时候是没有DialogResult属性呢,如果设置其返回值呢?注意事件本质上还是一个委托,其实可以当成是一个回调或者是钩子来使用.

namespace DialogSimple.ViewModels
{
    public class ModalDialogViewModel
    {
        public event Action<bool>? RequestClose;

        public ModalDialogViewModel()
        {
            OkCommand = new Command(DoOkClick);
            CancelCommand = new Command(DoCancelClick);
        }

        private void DoCancelClick(object obj)
        {
            // 1. 处理其他逻辑
            // 2. 调用窗口关闭事件,传递参数为false
            RequestClose?.Invoke(false);
        }

        private void DoOkClick(object obj)
        {
            // 1. 处理其他逻辑
            // 2. 调用窗口关闭事件,传递参数为true
            RequestClose?.Invoke(true);
        }

        public Command OkCommand { get; private set; }
        public Command CancelCommand { get; private set; }
    }
}

然后在UI程序中,去注册这个事件

namespace DialogSimple
{
    /// <summary>
    /// ModalDialog.xaml 的交互逻辑
    /// </summary>
    public partial class ModalDialog : Window
    {
        public ModalDialog()
        {
            InitializeComponent();
            DataContext = new ModalDialogViewModel();
            (DataContext as ModalDialogViewModel)!.RequestClose += ModalDialog_RequestClose;
        }

        private void ModalDialog_RequestClose(bool result)
        {
            DialogResult = result;
        }
    }
}

这里有一个疑问就是为什么我没有关闭这个窗口,当我们点击确定和取消按钮的时候,窗口就关闭了呢,其实这里的原因是因为当在WPF应用中如果设置了DialogResult的属性之后,无论是设置的true还是false,它都会去关闭窗口

注意:

我们可能还记得MVVM框架的设计原则,UI的逻辑一般都是在UI的模块代码中去完成,但是上面的弹窗部分是在ViewModel中实现的,并且在ViewModel中操作了UI视图的创建,这在实践上不是一个很好的实践,那么如何将这个创建弹窗的逻辑放到UI代码中去的也就是放到View中去呢,其中一种方式和上面的类似,就是通过事件.每次请求窗口的时候,调用事件,然后UI代码注册这个事件,完成对应的操作.

namespace DialogSimple.ViewModels
{
    public class MainWindowViewModel
    {
        public event Action<object>? RequestShowDialog;
        public MainWindowViewModel()
        {
            ShowDialogCommand = new Command(DoShowDialog);
        }

        private void DoShowDialog(object modalType)
        {
            if (modalType is string modal)
            {
                switch (modal)
                {
                    case "Modal":
                        RequestShowDialog?.Invoke("Modal");
                        break;
                }
            }
        }
        public Command ShowDialogCommand { get; private set; }
    }
}

然后在View中去做具体的窗口弹出处理

 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
            (DataContext as MainWindowViewModel)!.RequestShowDialog += MainWindow_RequestShowDialog;
        }

        private void MainWindow_RequestShowDialog(object obj)
        {
            if (obj is string windowType)
            {
                switch (windowType)
                {
                    case "Modal":
                        var result = new ModalDialog().ShowDialog();
                        if (result == true)
                        {
                            System.Diagnostics.Debug.WriteLine("返回true,进行保存操作");
                        }
                        else
                        {
                            System.Diagnostics.Debug.WriteLine("返回false,取消保存操作");
                        }
                        break;
                }
            }
        }
    }

非模态窗口的例子

非模态窗口就是把ShowDialog换成Show()就OK了,并且非模态窗口是没有返回值的.

② Prism中Dialog的实现方式

Prism对话框Dialog的实现主要分为三步

  • 1. 创建对话框(UserControl)
  • 2. 注册对话框(并绑定其关联的ViewModel)
  • 3. 使用IDialogService来显示对话框,如果有参数并传递参数

第一步: 创建对话框用户控件,注意这里只能是UserControl,不能是Window

<UserControl x:Class="PrismDialogSimple.Views.ModalDialog"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:PrismDialogSimple.Views"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="50" />
                <RowDefinition />
                <RowDefinition Height="100" />
            </Grid.RowDefinitions>
            <TextBlock Text="模态对话框演示"
                       FontSize="16"
                       Margin="10"
                       HorizontalAlignment="Left" />
            <TextBlock Grid.Row="1"
                       Text="确认要进行保存吗"
                       FontSize="18"
                       HorizontalAlignment="Center" VerticalAlignment="Center" />
            <StackPanel Grid.Row="2" HorizontalAlignment="Right" Orientation="Horizontal">
                <Button Width="100" Height="30"
                        Content="确定"
                        Command="{Binding OkCommand}"
                        CommandParameter="" />
                <Button Width="100" Height="30"
                        Content="取消"
                        Command="{Binding CancelCommand}"
                        CommandParameter=""
                        Margin="10,0" />
            </StackPanel>
        </Grid>
    </Grid>
</UserControl>

第二步:注册对话框

注意种类关联的ViewModel,就是和对话框关联的ViewModel必须是实现了IDialogAware接口才可以.

namespace PrismDialogSimple.ViewModels
{
    public class ModalDialogViewModel : BindableBase, IDialogAware
    {
        public string Title => throw new NotImplementedException();

        public event Action<IDialogResult> RequestClose;

        public bool CanCloseDialog()
        {
            throw new NotImplementedException();
        }

        public void OnDialogClosed()
        {
            throw new NotImplementedException();
        }

        public void OnDialogOpened(IDialogParameters parameters)
        {
            throw new NotImplementedException();
        }
    }
}

注册对话框的代码

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    // 1. 注册导航视图和视图模型
    containerRegistry.RegisterForNavigation<MainWindow, MainWindowViewModel>();

    // 2. 注册对话框和对话框模型
    containerRegistry.RegisterDialog<ModalDialog, ModalDialogViewModel>();
}

第三步: 使用IDialogService显示对话框

在显示对话框之前,我们要把对话框的ViewModel中实现的那些接口来解释一下,并进行一些简单的实现,不然等下在弹出窗口的时候回报错.下面具体看看这个IDialogAware都实现了什么东西?

 public class ModalDialogViewModel : BindableBase, IDialogAware
 {
     public string Title => throw new NotImplementedException();

     public event Action<IDialogResult> RequestClose;

     public bool CanCloseDialog()
     {
         throw new NotImplementedException();
     }

     public void OnDialogClosed()
     {
         throw new NotImplementedException();
     }

     public void OnDialogOpened(IDialogParameters parameters)
     {
         throw new NotImplementedException();
     }
 }
  • Title

我们首先要明白一点就是我们弹出的窗口是一个UserContorl控件,它其实是在一个Window窗口中的,这个窗口呢是Prism框架来进行创建和管理的,这个窗口呢,绑定了一个标题,就是这个Title,所以如果你只是在初始化的时候就一个标题,那么就这样简单的赋值就行了,如果你想要在运行过程中更改这个标题,你还要实现它的通知属性才行.

 private string title;

 public string Title
 {
     get { return title; }
     set
     {
         title = value;
         RaisePropertyChanged();
     }
 }
  • RequestClose()事件

用于从ViewModel触发对话框的关闭.是的ViewModel能够控制视图的关闭,而不必依赖视图层的代码.
这样做的好处就是保持了视图和业务逻辑的分离,符合MVVM设计原则.

  • CanCloseDialog()方法

CanCloseDialog()方法用于确定对话框是否可以关闭.这可以基于某些判断,比如表单是否填写完毕或者是否满足特定的业务逻辑来控制对话框是否可以关闭.返回true表示可以关闭对话框,返回false表示不允许关闭对话框.

  • OnDialogClosed()方法

关闭时调用,可以用于执行一些清理操作或者其他逻辑的地方,如资源释放或者状态重置.

  • OnDialogOpened()方法

在对话框打开时调用,可以在这里处理对话框打开的初始化工作,如基于传入的参数设置初始状态或者加载数据.

关于对话框的返回值怎么传递的问题?当你需要从对话框返回数据到调用它的地方的时候,你可以在RequestClose事件时,传递一个
DialogResult实例,并通过DialogResult的构造函数或者属性来设置返回值.

private void DoCancel()
{
    RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, null));
}

private void DoOk()
{
    RequestClose?.Invoke(new DialogResult(ButtonResult.OK, null));
}

然后在弹出窗口的那里对结果进行获取,并进行判断

private void DoShowDialog(string modelType)
{
    switch (modelType)
    {
        case "Modal":
            _dialogService.ShowDialog(nameof(ModalDialog), result =>
            {
                if (result.Result == ButtonResult.OK)
                {
                    System.Diagnostics.Debug.WriteLine("点击了OK,进行保存操作.");
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("点击了取消,不进行保存操作.");
                }
            });
            break;
    }
}
③ Dialog使用案例一 (修改器)

我们做一个案例,界面上有两个按钮,弹出来的是同一个弹窗,但是弹窗的功能稍微有点不同,一个是名称修改,一个是电话修改.这个案例不重要,主要是运用之前的知识. 修改之后,如果点击了确定按钮,要把这个新的数据返回给调用者,调用者那里显示这个新的值.

主界面操作按钮

<Window x:Class="DialogUsedSimple01.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        Title="{Binding Title}"
        Width="1000" Height="800"
        prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <StackPanel Margin="50">
            <Button Width="100" Height="35"
                    Content="修改名称"
                    Command="{Binding ModifyNameCommand}" />
            <Button Width="100" Height="35"
                    Content="修改电话"
                    Command="{Binding ModifyPhoneCommand}"
                    Margin="0,10" />
        </StackPanel>
    </Grid>
</Window>

注册view和viewModel

namespace DialogUsedSimple01
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            // 1. 注册导航视图
            containerRegistry.RegisterForNavigation<MainWindow, MainWindowViewModel>();

            // 2.注册弹框
            containerRegistry.RegisterDialog<DialogSimpleDemo, DialogSimpleViewModel>();
        }
    }
}

弹框视图

<UserControl x:Class="DialogUsedSimple01.Views.DialogSimpleDemo"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:DialogUsedSimple01.Views"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"
             Width="500" Height="300">
    <Grid>
        <StackPanel Margin="10" Orientation="Horizontal">
            <Label Width="60" Height="30"
                   Content="{Binding TextShow}"
                   FontSize="16"
                   VerticalAlignment="Center" />
            <TextBox Width="200" Height="30"
                     Text="{Binding TextInput}"
                     Margin="10,0"
                     VerticalContentAlignment="Center" />
        </StackPanel>

        <StackPanel Margin="10"
                    HorizontalAlignment="Right" VerticalAlignment="Bottom"
                    Orientation="Horizontal">
            <Button Width="100" Content="确认" Command="{Binding OkCommand}" />
            <Button Width="100"
                    Content="取消"
                    Command="{Binding CancelCommand}"
                    Margin="10,0" />
        </StackPanel>
    </Grid>
</UserControl>

弹框视图对应的ViewModel

namespace DialogUsedSimple01.ViewModels
{
    public class DialogSimpleViewModel : BindableBase, IDialogAware
    {
        public DialogSimpleViewModel()
        {
            OkCommand = ne