7.Prism框架之对话框服务

news2025/1/10 3:07:42

文章目录

      • 一. 目标
      • 二. 技能介绍
        • ① 什么是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 = new DelegateCommand(DoOK);
            CancelCommand = new DelegateCommand(DoCancel);
        }

        private void DoCancel()
        {
            RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, new DialogParameters($"resVal={TextInput}")));
        }

        private void DoOK()
        {
            RequestClose?.Invoke(new DialogResult(ButtonResult.OK, new DialogParameters($"resVal={TextInput}")));
        }

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

        private string title;

        public string Title
        {
            get { return title; }
            set
            {
                title = value;
                RaisePropertyChanged();
            }
        }

        private string textShow;

        public string TextShow
        {
            get { return textShow; }
            set
            {
                textShow = value;
                RaisePropertyChanged();
            }
        }

        private string textInput;

        public string TextInput
        {
            get { return textInput; }
            set
            {
                textInput = value;
                RaisePropertyChanged();
            }
        }

        public event Action<IDialogResult> RequestClose;

        public bool CanCloseDialog()
        {
            return true;
        }

        public void OnDialogClosed()
        {

        }

        public void OnDialogOpened(IDialogParameters parameters)
        {
            var parameter = parameters.GetValue<string>("modifyType");
            if (parameter == "Name")
            {
                Title = "名称修改器";
                TextShow = "新名称:";
            }
            else if (parameter == "Phone")
            {
                Title = "电话修改器";
                TextShow = "新电话:";
            }
        }
    }
}

主视图对应的ViewModel

namespace DialogUsedSimple01.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private IDialogService _dialogService;
        private string _title = "弹窗示例1";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
        public MainWindowViewModel(IDialogService dialogService)
        {
            _dialogService = dialogService;

            ModifyNameCommand = new DelegateCommand(DoModifyName);
            ModifyPhoneCommand = new DelegateCommand(DoModifyPhone);
        }

        private void DoModifyPhone()
        {
            _dialogService.ShowDialog(nameof(DialogSimpleDemo), new DialogParameters($"modifyType=Phone"), result =>
            {
                string newVal = result.Parameters.GetValue<string>("resVal");
                if (result.Result == ButtonResult.OK)
                {
                    System.Diagnostics.Debug.WriteLine($"确认成功,修改后的电话为:{newVal}");
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine($"取消修改");
                }
            });
        }

        private void DoModifyName()
        {
            _dialogService.ShowDialog(nameof(DialogSimpleDemo), new DialogParameters($"modifyType=Name"), result =>
            {
                string newVal = result.Parameters.GetValue<string>("resVal");
                if (result.Result == ButtonResult.OK)
                {
                    System.Diagnostics.Debug.WriteLine($"确认成功,修改后名字为: {newVal}");
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine($"取消修改");
                }
            });
        }

        public DelegateCommand ModifyNameCommand { get; private set; }
        public DelegateCommand ModifyPhoneCommand { get; private set; }


    }
}
④ Dialog使用案例2(异常显示窗口)

我们平时看到的异常,比如下面这些,都分别代表什么意思呢?
在这里插入图片描述

  • 1. Data

  • 类型: IDictionary

  • 描述: 获取一个键值对集合,可以在其中添加自定义的异常处理信息.这使得你可以将额外的数据和异常对象关联,便于异常处理时获取更多的上下文信息.

  • 2. HResult

  • 类型: int

  • 描述: 获取或者设置HResult(一个由返回值标识的整数),它是一个从底层COM组件中映射过来的错误代码.通常是一个固定的很大的负数.

  • 3. HelpLink

  • 类型: string

  • 描述: 获取或者设置异常处理相关文档的链接地址或者URI,通常是文件路径或者是网页路径,可以提供更多的异常相关的帮助

  • 4. InnerException

  • 类型: Exception

  • 描述: 内部异常,也就是说异常是可以嵌套的,当我们在处理异常A的过程中,这个异常A是因为异常B导致的,也就是说抛出的异常A在异常B的Catch逻辑里,那么我称最开始出现的异常B为异常A的内部异常,说白了就是我们捕获的时候可能最后只捕获了异常A,但是异常A发生的时候其实先发生了异常B,异常B就是异常A的内部异常.

try
{
	string content= File.ReadAllText(filePath);
}
catch(BException ex)
{
	throw new AException();
}

上面的BException就是AException的内部异常.

  • 5. Message

  • 类型: string

  • 描述: 获取描述当前异常的消息.这是一个人类可以读取的错误描述,通常用于提示异常原因.

  • 6. Source

  • 类型: string

  • 描述: 获取或者设置引发当前异常的应用程序或者对象的名称.默认情况下,这通常是引发异常的程序集的名称.

  • 7. StackTrace

  • 类型: string

  • 描述: 获取调用堆栈上的当前发生异常的时候的方法调用序列包括异常发生的地方,对于调试和定位问题非常重要.

  • 8. TargetSite

  • 类型: MethodBase

  • 描述: 获取引发当前异常的方法.这个返回一个MethodBase对象,包含有关异常源头的方法的信息,如方法名,拥有类等.

个人想法

我现在是这样想的,当出现异常的时候,我首先是弹出一个框,这个框先弹出来的异常信息,上面有几个按钮,一个是查看详情,一个是打开打开异常日志目录,一个是关闭,点击查看详情的时候弹出一个框显示这个异常的详细信息,详细信息就是上面介绍的这些内容,这里要注意一点就是如果有内部异常,内部异常也要现实出来.然后这个详情页就只有两个按钮,一个是复制,一个是关闭

第一步: 创建异常显示页面,只显示异常的Message消息

<UserControl x:Class="ExceptionShowDialogDemo.Dialogs.ErrorShowDialog"
             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:ExceptionShowDialogDemo.Dialogs"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:prism="http://prismlibrary.com/"
             mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
    <!--
        设置Prism对话框所在窗口的全局属性
        1) 窗口显示的区域,父窗口的中心
        2) 窗口是否可以被用户调整大小,NoResize不可以
        3) 窗口是否在任务栏中显示, False,不显示,一般模态窗口和一些简单的操作不显示在任务栏
        4) 设置窗口的大小自动调整以适应其内容宽高
    -->
    <prism:Dialog.WindowStyle>
        <Style TargetType="Window">
            <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterOwner" />
            <Setter Property="ResizeMode" Value="NoResize" />
            <Setter Property="ShowInTaskbar" Value="False" />
            <Setter Property="SizeToContent" Value="WidthAndHeight" />
        </Style>
    </prism:Dialog.WindowStyle>
    <UserControl.Resources>
        <Style TargetType="Button">
            <Setter Property="Height" Value="35" />
            <Setter Property="VerticalContentAlignment" Value="Center" />
        </Style>
    </UserControl.Resources>
    <DockPanel MinWidth="700" MinHeight="300"
               Background="#EEE"
               LastChildFill="False">

        <StackPanel Orientation="Horizontal">
            <Label>
                <TextBlock Text="{Binding Message}"
                           Foreground="#333"
                           FontSize="16"
                           TextWrapping="Wrap" />
            </Label>
        </StackPanel>

        <StackPanel DockPanel.Dock="Bottom"
                    Margin="20"
                    HorizontalAlignment="Right"
                    Orientation="Horizontal">
            <Button Width="100" Content="查看详情" Command="{Binding ViewDetailCommand}" />
            <Button Width="100" Content="关闭" Command="{Binding CloseDialogCommand}" />
        </StackPanel>
    </DockPanel>
</UserControl>

其对应的ViewModel

namespace ExceptionShowDialogDemo.ViewModels
{
    public class ErrorShowDialogViewModel : BindableBase, IDialogAware
    {
        private readonly IDialogService _dialogService;
        public ErrorShowDialogViewModel(IDialogService dialogService)
        {
            _dialogService = dialogService;

            ViewDetailCommand = new DelegateCommand(DoViewDetail);
            OpenErrorDirCommand = new DelegateCommand(DoOpenErrorDir);
            CloseDialogCommand = new DelegateCommand(DoCloseDialog);
        }

        private void DoCloseDialog()
        {
            RequestClose?.Invoke(new DialogResult());
        }

        private void DoOpenErrorDir()
        {

        }

        private void DoViewDetail()
        {
            DialogParameters parameters = new DialogParameters()
            {
                {"Title","异常详情页" },
                {"Exception",ExceptionDetail }
            };
            _dialogService.ShowDialog(nameof(ExceptionDetailDialog), parameters, 
            result => { });
        }

        public DelegateCommand ViewDetailCommand { get; private set; }
        public DelegateCommand OpenErrorDirCommand { get; private set; }
        public DelegateCommand CloseDialogCommand { get; private set; }

        private string title;

        public string Title
        {
            get { return title; }
            set
            {
                title = value;
                RaisePropertyChanged();
            }
        }

        private string message;

        public string Message
        {
            get { return message; }
            set
            {
                message = value;
                RaisePropertyChanged();
            }
        }

        public Exception ExceptionDetail { get; set; }


        public event Action<IDialogResult> RequestClose;

        public bool CanCloseDialog()
        {
            return true;
        }

        public void OnDialogClosed()
        {

        }

        public void OnDialogOpened(IDialogParameters parameters)
        {
            var title = parameters.GetValue<string>("Title");
            var exception = parameters.GetValue<Exception>("Exception");
            if (!string.IsNullOrEmpty(title) && exception != null)
            {
                Title = title;
                ExceptionDetail = exception; // 异常赋值
                Message = exception.Message;
            }
        }
    }
}

注册和模拟异常代码app.cs中

namespace ExceptionShowDialogDemo
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App
    {
        public App()
        {
            Startup += App_Startup;
            DispatcherUnhandledException += App_DispatcherUnhandledException;
        }

        private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            var dialogService = Container.Resolve<IDialogService>();
            DialogParameters parameters = new DialogParameters
            {
                { "Title", "未捕获异常提示框" },
                { "Exception", e.Exception }
            };
            dialogService.ShowDialog(nameof(ErrorShowDialog), parameters, result => { });
        }

        private void App_Startup(object sender, StartupEventArgs e)
        {

        }

        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            try
            {
                throw new ArgumentException("数据库操作异常");
            }
            catch (Exception ex)
            {
                ex.Data.Add("DataBaseName", "TableA");
                ex.Data.Add("HappenTime", DateAndTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                ex.HelpLink = "Https//www.baidu.com";
                throw new Exception("外部异常", ex);
            }
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<MainWindow, MainWindowViewModel>();

            containerRegistry.RegisterDialog<ErrorShowDialog, ErrorShowDialogViewModel>();
            containerRegistry.RegisterDialog<ExceptionDetailDialog, ExceptionDetailDialogViewModel>();
        }
    }
}

点击详情页弹出的另外一个详情页窗口

<UserControl x:Class="ExceptionShowDialogDemo.Dialogs.ExceptionDetailDialog"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:converter="clr-namespace:ExceptionShowDialogDemo.ValueConverters"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:ExceptionShowDialogDemo.Dialogs"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:prism="http://prismlibrary.com/"
             mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
    <prism:Dialog.WindowStyle>
        <Style TargetType="Window">
            <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterOwner" />
            <Setter Property="ResizeMode" Value="NoResize" />
            <Setter Property="ShowInTaskbar" Value="False" />
            <Setter Property="SizeToContent" Value="WidthAndHeight" />
        </Style>
    </prism:Dialog.WindowStyle>
    <UserControl.Resources>
        <converter:GetTypeConverter x:Key="GetTypeConverter" />
        <converter:NotNullConverter x:Key="NotNullConverter" />
        <Style TargetType="Button">
            <Setter Property="Height" Value="35" />
            <Setter Property="Width" Value="100" />
            <Setter Property="VerticalContentAlignment" Value="Center" />
        </Style>
    </UserControl.Resources>
    <DockPanel MinWidth="700" MinHeight="300"
               Background="#EEE"
               LastChildFill="False">
        <StackPanel DockPanel.Dock="Top" Margin="10" Orientation="Horizontal">
            <Label VerticalContentAlignment="Center">
                <TextBlock Text="{Binding ExceptionShow.Message}" Foreground="#333" TextWrapping="Wrap" />
            </Label>
        </StackPanel>
        <!--
            Width = "*"  和 Width 什么都不写有什么区别?
            1) Width = * 宽度占用剩余的部分,使用Width="*"
            2) 如果什么都不写表示这个列的宽度会根据内容自动适应
        -->
        <Grid Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="250" />
                <ColumnDefinition Width="auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <ListView Background="#EEE"
                      Padding="5,0"
                      ItemsSource="{Binding Exceptions}"
                      SelectedValue="{Binding ExceptionSelected, Mode=TwoWay}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <!--
                            绑定当前上下文,其中的Bingding Path=. 可以省略,直接写成Binding Convereter={StaticResource GetTypeConverter}
                        -->
                        <TextBlock Text="{Binding Path=., Mode=OneWay, Converter={StaticResource GetTypeConverter}}" />
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            <!--  Grid之间的分隔线  -->
            <GridSplitter Grid.Column="1"
                          Width="2"
                          Background="#888"
                          Margin="10,0" />

            <ScrollViewer Grid.Column="2"
                          HorizontalScrollBarVisibility="Auto" TextBlock.FontSize="13"
                          VerticalScrollBarVisibility="Hidden">
                <StackPanel>
                    <Label>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding ExceptionSelected, Converter={StaticResource GetTypeConverter}}" FontSize="18" FontWeight="Bold" />
                            <TextBlock Text="(异常类型)" FontSize="18" FontWeight="Bold" />
                        </StackPanel>
                    </Label>

                    <Label Content="Message(异常信息)"
                           FontWeight="Bold"
                           Margin="0,5,0,0"
                           Visibility="{Binding ExceptionSelected.Message, Converter={StaticResource NotNullConverter}}" />
                    <Label Content="{Binding ExceptionSelected.Message}" FontSize="12" Visibility="{Binding ExceptionSelected.Message, Converter={StaticResource NotNullConverter}}" />
                    <Label Content="Stack Trace(方法调用序列)"
                           FontWeight="Bold"
                           Margin="0,5,0,0"
                           Visibility="{Binding ExceptionSelected.StackTrace, Converter={StaticResource NotNullConverter}}" />
                    <Label Content="{Binding ExceptionSelected.StackTrace}" Margin="0,5,0,0" />
                    <Label Content="Target Site(异常发生方法)"
                           FontWeight="Bold"
                           Margin="0,5,0,0"
                           Visibility="{Binding ExceptionSelected.TargetSite, Converter={StaticResource NotNullConverter}}" />
                    <Label Content="{Binding ExceptionSelected.TargetSite}" Visibility="{Binding ExceptionSelected.TargetSite, Converter={StaticResource NotNullConverter}}" />

                    <!--
                        每个Exception对象都有一个Data属性,这是一个IDictionary接口的实例,用于存储定义的键值数据.
                        如果你有一个数据库连接异常,你想知道是哪个数据库连接异常,你可以把这个信息添加到Data中去.
                    -->
                    <Label Content="Data(异常附加信息)"
                           FontWeight="Bold"
                           Margin="0,5,0,0"
                           Visibility="{Binding ExceptionSelected.Data, Converter={StaticResource NotNullConverter}}" />
                    <ItemsControl ItemsSource="{Binding ExceptionSelected.Data}" Visibility="{Binding ExceptionSelected.Data, Converter={StaticResource NotNullConverter}}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Label>
                                    <TextBlock FontSize="12" FontStyle="Italic">
                                        <Run Text="{Binding Key, Mode=OneWay}" />
                                        <Run Text=" = " />
                                        <Run Text="{Binding Value, Mode=OneWay}" />
                                    </TextBlock>
                                </Label>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>

                    <!--
                        获取或者设置导致错误的应用程序的名称,通常是引发异常的应用程序的名称
                    -->
                    <Label Content="Source(异常程序集)"
                           FontWeight="Bold"
                           Margin="0,5,0,0"
                           Visibility="{Binding ExceptionSelected.Source, Converter={StaticResource NotNullConverter}}" />
                    <Label Content="{Binding ExceptionSelected.Source}" Visibility="{Binding ExceptionSelected.Source, Converter={StaticResource NotNullConverter}}" />

                    <Label Content="Help Link(异常帮助链接)"
                           FontWeight="Bold"
                           Margin="0,5,0,0"
                           Visibility="{Binding ExceptionSelected.HelpLink, Converter={StaticResource NotNullConverter}}" />
                    <Label Content="{Binding ExceptionSelected.HelpLink}" Visibility="{Binding ExceptionSelected.HelpLink, Converter={StaticResource NotNullConverter}}" />

                    <Label Content="HResult(异常错误代码)"
                           FontWeight="Bold"
                           Margin="0,10,0,0"
                           Visibility="{Binding ExceptionSelected.HResult, Converter={StaticResource NotNullConverter}}" />
                    <Label Content="{Binding ExceptionSelected.HResult}" Visibility="{Binding ExceptionSelected.HResult, Converter={StaticResource NotNullConverter}}" />

                </StackPanel>
            </ScrollViewer>

        </Grid>

        <StackPanel DockPanel.Dock="Bottom"
                    Margin="10"
                    HorizontalAlignment="Right"
                    Orientation="Horizontal">
            <Button Content="关闭" Command="{Binding CloseDialogCommand}" />
        </StackPanel>

    </DockPanel>
</UserControl>

异常详情页对应的ViewModel

namespace ExceptionShowDialogDemo.ViewModels
{
    public class ExceptionDetailDialogViewModel : BindableBase, IDialogAware
    {
        public ExceptionDetailDialogViewModel()
        {
            CloseDialogCommand = new DelegateCommand(DoCloseDialog);
        }

        private void DoCloseDialog()
        {
            RequestClose?.Invoke(new DialogResult());
        }

        public DelegateCommand CloseDialogCommand { get; private set; }

        private string title;

        public string Title
        {
            get { return title; }
            set
            {
                title = value;
                RaisePropertyChanged();
            }
        }

        private Exception exceptionSelected;

        public Exception ExceptionSelected
        {
            get { return exceptionSelected; }
            set
            {
                exceptionSelected = value;
                RaisePropertyChanged();
            }
        }


        /// <summary>
        /// 异常列表,包括发生异常的内部异常.
        /// </summary>
        private ObservableCollection<Exception> exceptions = new ObservableCollection<Exception>();

        public ObservableCollection<Exception> Exceptions
        {
            get { return exceptions; }
            set
            {
                exceptions = value;
                RaisePropertyChanged();
            }
        }





        public event Action<IDialogResult> RequestClose;

        public bool CanCloseDialog()
        {
            return true;
        }

        public void OnDialogClosed()
        {

        }

        public void OnDialogOpened(IDialogParameters parameters)
        {
            Exceptions.Clear();
            var title = parameters.GetValue<string>("Title");
            var exception = parameters.GetValue<Exception>("Exception");
            if (!string.IsNullOrEmpty(title))
            {
                Title = title;
            }
            while (exception != null)
            {
                Exceptions.Add(exception);
                // 当没有内部异常的时候exception.InnerException的返回值为null
                exception = exception.InnerException;
            }
            ExceptionSelected = Exceptions.FirstOrDefault();
        }
    }
}

使用到的两个类型转换器

namespace ExceptionShowDialogDemo.ValueConverters
{
    public class GetTypeConverter : IValueConverter
    {
        /// <summary>
        /// 当数据从绑定源(后台的一个数据模型)传递给绑定目标(例如一个控件)时,Converter方法会被调用.
        /// </summary>
        /// <param name="value">要转换的值(绑定的数据源)</param>
        /// <param name="targetType">转换的目标类型,UI控件属性承载的类型,比如Text和Content的目标类型就是string</param>
        /// <param name="parameter">一个可选参数,可以在XAML中提供,你可以使用这个参数来影响转换的行为</param>
        /// <param name="culture">用于转换的文化信息,用来进行一些区域特性的转换,例如日期和字符串</param>
        /// <returns></returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var res = value?.GetType() ?? targetType;

            // 如果value为null,就返回targetType,如果是textBox或者是textBlock就返回string
            return res;
        }

        // 当数据从绑定目标传递给绑定源的时候,就是从UI控件的值到后台的值的时候.ConvertBack会被调用.
        // 其实就是相当于控件上的值发生了变化,会调用这个方法,而我们使用了Bingding.DoNothing,表示不作转换
        // 什么都不做.
        /// <summary>
        /// 
        /// </summary>
        /// <param name="value">从绑定目标(UI控件)传递过来的值</param>
        /// <param name="targetType">绑定源的数据类型</param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns></returns>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }
}
namespace ExceptionShowDialogDemo.ValueConverters
{
    public class NotNullConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool res = value != null;
            switch (value)
            {
                case ICollection collection:
                    res = collection.Count > 0;
                    break;
                case string str:
                    res = !string.IsNullOrWhiteSpace(str);
                    break;
                case int i:
                    res = i != 0;
                    break;
                case double d:
                    res = Math.Abs(d) > 0.00001;
                    break;
            }

            // 看看是否需要 反转结果,根据paraeter的值来判读昂
            if ((parameter is bool inverse || parameter is string s && bool.TryParse(s, out inverse)) && inverse)
                res = !res;

            if (targetType == typeof(Visibility))
                return res ? Visibility.Visible : Visibility.Collapsed;
            return res;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }
}

最后实现的效果,出现异常的第一个弹窗
在这里插入图片描述
点击查看详情的弹窗
在这里插入图片描述
总结:

这个小案例还是用到了不少的知识点,但是还是有可以优化的地方,因为这里又在ViewModel中操作了UI,最好的实践方式是将弹窗这里封装成为一个服务,然后在服务里操作窗口,后续再介绍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1614638.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python编写一个简单的课时记录系统

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用Python创建一个简单的课时记录系统 在学习过程中&#xff0c;跟踪课时的进度是非常重要…

LeetCode in Python 69. Sqrt(x) (x的平方根)

求x的平方根&#xff0c;第一想法可能是遍历0&#xff5e;x&#xff0c;求其平方&#xff0c;找到或且但其时间复杂度为O(n)&#xff0c;或是想到遍历0&#xff5e;M即可&#xff0c;其中M x // 2&#xff0c;将时间复杂度降至O()。本文利用二分思想&#xff0c;给出一种时间复…

python--pyQt5 进度条:QProgressBar

https://www.cnblogs.com/itwangqiang/articles/14959401.html https://blog.csdn.net/weixin_43990846/article/details/123880081 进度条用于向用户指示操作的进度&#xff0c;并向他们保证应用程序仍在运行 例 1 import sys from PyQt5.QtWidgets import QApplication, QWi…

(十六)C++自制植物大战僵尸游戏的宏定义讲解

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/uzrnw 宏定义 在游戏代码中为了方便,定义了许多宏定义。使用宏定义简化代码并提高可读性。下面将讲解游戏中用到的宏定义。 代码位置 代码所在位置是Class\Scenes\GameScene文件夹中。具体如下图所示。 Define.h …

尝试给笔记本超频

超频&#xff08;英语&#xff1a;overclocking&#xff09;是把一个电子配件的时脉速度提升至高于厂方所定的速度运作&#xff0c;从而提升性能的方法&#xff0c;但此举有可能导致该配件稳定性以及配件寿命下降。 笔记本配置为&#xff1a; 处理器 AMD Ryzen 7 7730U wit…

学习部分排序,插入排序,冒泡排序以及希尔排序

1.插入排序 <1>.首先我们举个例子 我们要把6进行前面的插入&#xff0c;那我们要进行比较&#xff0c;首先确定一个end的指针&#xff0c;然后他指向的数字就是我们需要比较的&#xff0c;如果end指向的数比我们end1 的大的话&#xff0c;那我们就往前挪一个&#xff0c…

四六级英语听力考试音频无线发射系统在安顺学院的成功应用分析

四六级英语听力考试音频无线发射系统在安顺学院的成功应用分析 由北京海特伟业科技任洪卓发布于2024年4月22日 安顺学院为了提高学生的外语听力水平&#xff0c;并确保英语四六级听力考试的稳定可靠进行&#xff0c;决定对传统的英语听力音频传输系统进行改造&#xff0c;以提供…

【YOLOv9】实战二:手把手教你使用TensorRT实现YOLOv9实时目标检测(含源码)

‍‍&#x1f3e1;博客主页&#xff1a; virobotics(仪酷智能)&#xff1a;LabVIEW深度学习、人工智能博主 &#x1f384;所属专栏&#xff1a;『LabVIEW深度学习实战』 &#x1f4d1;上期文章&#xff1a;『【YOLOv9】实战一&#xff1a;在 Windows 上使用LabVIEW OpenVINO工具…

gitlab 16.x - ERR unknown command ‘HELLO‘

现象 gitlab部分操作报错500。通过Rails日志发现以下报错&#xff1a; 报错&#xff1a; RedisClient::CommandError ERR unknown command HELLO {"severity": "ERROR","time": "2024-04-22T02:50:16.906Z","correlation_id&quo…

3667B芯茂微SOP7封装5V1A 5W适配器/充电器芯片

3667B是一款高度集成的隔离型适配器和充电器的自供电PSR控制芯片&#xff0c;外部设计极其简单。LP3667 固定原边峰值电流&#xff0c;通过变压器原副边匝比来设置输出恒流点&#xff1b;通过设定一个FB 电阻来设置输出恒压点。为了实现系统成本的简化&#xff0c;LP3667 内置启…

CSS基础常用属性之字体属性(如果想知道CSS的字体属性知识点,那么只看这一篇就足够了!)

前言&#xff1a;在我们学习CSS的时候&#xff0c;主要学习选择器和常用的属性&#xff0c;而这篇文章讲解的就是最基础的属性之一——文字属性。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客 废话不多说&#xff0c;让我们直…

删除二叉树的子树:假设二叉树中的结点均不相等,采用二叉链存储,设计递归算法删除根结点值为x的子树。(C语言)

目录 实验内容&#xff1a; 实验过程&#xff1a; 1.算法设计 2.程序清单 3.复杂度分析 4.运行结果 实验内容&#xff1a; 删除二叉树的子树:假设二叉树中的结点均不相等&#xff0c;采用二叉链存储&#xff0c;设计递归算法删除根结点值为x的子树。 实验过程&#xff1…

web前端(简洁版)

0. 开发环境 && 安装插件 这里我使用的是vscode开发环境 Auto Rename Tag是语法自动补齐view-in-browser是快速在浏览器中打开live server实时网页刷新 1. HTML 文件基本结构 <html><head><title>第一个页面</title></head><body&g…

PCIe系统阻抗控制85还是100的验证

高速先生成员--周伟 还记得上次的文章&#xff0c;PCIe阻抗控制&#xff0c;85ohm和100ohm哪个好&#xff0c;文章里面只讲到目前的主要问题&#xff0c;但没有给出具体怎么解决这个问题&#xff0c;今天我们就通过无源仿真的方式来聊聊上次那个问题的最终解决方案。 目前我们看…

lvgl图形化设计工具GUI Guider结合使用

前言 上篇博客整合了lvgl到项目中&#xff0c;采用的是自己编写源码的方式&#xff0c;实现了个简单的界面。实际过程中一般情况开发界面都借助设计工具&#xff0c;这里使用的是gui guider来进行示例记录 项目结构&#xff08;生成代码路径依然放到项目路径下&#xff09; C…

Hack The Box-Runner

总体思路 子域名扫描->CVE-2023-42793利用->获取敏感信息->user->端口转发->CVE-2024-21626利用->root 信息收集&端口利用 nmap -sSVC 10.10.11.13目标开放22、80、8000端口&#xff0c;这里先将runner.htb加入到hosts文件后&#xff0c;访问之 查看源…

腾讯后端一面:当 TCP 建立连接之后,TCP 和 UDP 的实时性是不是就差不多了?

更多大厂面试内容可见 -> http://11come.cn 腾讯后端一面&#xff1a;当 TCP 建立连接之后&#xff0c;TCP 和 UDP 的实时性是不是就差不多了&#xff1f; 项目相关 面试官可能是 Go 方向的&#xff0c;我面试的是 Java 方向的&#xff0c;所以面试官也没有问我简历上的项…

信号继电器HBDXH-200/1辅助电源110VDC 启动电压110VDC JOSEF约瑟

用途 适用于直流操作的继电保护和自动控制线路中&#xff0c;作为信号指示用&#xff0c;有多组动合保持触点。满足现场指示和遥信要求。 技术参数 启动信号额定值:直流电流型:10mA~4A. 直流电压型:220VDC、110VDC、48VDC、24VDC 辅助电源电压:220VDC、110VDC、220VAC、110…

华为ensp中MSTP多网段传输协议(原理及配置命令)

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月22日15点29分 在华为ENSP中&#xff0c;MSTP&#xff08;多段传输协议&#xff09;是重要的生成树协议&#xff0c;它扩展了STP&#xff08;生成树协议&#xff09…

舒适护眼模式:苹果手机字体大小怎么设置?

在现代社会&#xff0c;人们对于手机的使用已经不再局限于通讯和娱乐&#xff0c;也逐渐成为了我们生活和工作中不可或缺的一部分。然而&#xff0c;长时间使用手机可能会对我们的视力造成一定程度的影响。 为了更好地保护视力健康&#xff0c;苹果手机提供了舒适护眼的模式&a…