翼度科技»论坛 编程开发 .net 查看内容

[WPF]动手写一个简单的消息对话框

8

主题

8

帖子

24

积分

新手上路

Rank: 1

积分
24
消息对话框是UI界面中不可或缺的组成部分,用于给用户一些提示,警告或者询问的窗口。在WPF中,消息对话框是系统原生(user32.dll)的MessageBox,无法通过Style或者Template来修改消息对话框的外观。因此,当需要一个与应用程序主题风格一致的消息对话框时,只能自己动手造轮子了。
确定“轮子”的功能

消息对话框的核心功能是向用户显示信息,并在用户对消息进行处理前中断用户的操作。根据常见的应用场景,可以梳理出以下几点功能:

  • 支持的消息类型:提示信息、警告信息、错误信息、询问信息
  • 支持的对话框类型:迷你模式(显示简要信息并自动关闭)、普通模式、完整模式(适用于消息内容分层级显示)
  • 设置消息对话框是否将触发源作为父窗体并显示遮罩层
    主要功能如下图所示:

开始造“轮子”

消息对话框本质也是一个窗体,因此首先要做的是自定义一个弹窗的样式,然后根据消息类型以及对话框类型定义相应的模板。
自定义窗口外观

标准的窗口由两个重叠的矩形组成。外部矩形是非工作区,其中包括标题栏按钮(最小化、最大化和关闭) 、窗口边框、调整大小和移动行为、应用程序图标和标题以及系统菜单。它由操作系统的窗口管理器绘制和管理。其尺寸由标准操作系统设置决定。内部矩形是工作区,也就是应用程序的内容。
自定义窗口外观主要是针对非工作区,可以通过设置属性WindowStyle为None,或者使用 WindowChrome类来自定义。这里我们使用前一种方法。
  1. <ControlTemplate x:Key="AlertDialogBaseTemplate" TargetType="{x:Type Window}">
  2.     <Border x:Name="border" Margin="0"
  3.             Background="White" CornerRadius="3"
  4.             RenderTransformOrigin="0.5,0.5">
  5.         <i:Interaction.Triggers>
  6.             <i:EventTrigger EventName="Loaded">
  7.                 <helper:EventToCommand Command="{Binding LoadedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" />
  8.             </i:EventTrigger>
  9.         </i:Interaction.Triggers>
  10.         <Border.RenderTransform>
  11.             <TransformGroup>
  12.                 <ScaleTransform />
  13.             </TransformGroup>
  14.         </Border.RenderTransform>
  15.         <Grid>
  16.             <Grid.RowDefinitions>
  17.                 <RowDefinition Height="Auto" />
  18.                 <RowDefinition Height="*" />
  19.             </Grid.RowDefinitions>
  20.             <toolkit:ImageButton Grid.Row="0" Width="16" Height="16"
  21.                                  Margin="0,16,16,0"
  22.                                  HorizontalAlignment="Right"
  23.                                  VerticalAlignment="Bottom"
  24.                                  Command="{Binding CloseWinCommand}"
  25.                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
  26.                                  DownImage="Images/AlterDialog/btnclose_hover.png"
  27.                                  HoverImage="Images/AlterDialog/btnclose_hover.png"
  28.                                  NormalImage="Images/AlterDialog/btnclose.png"
  29.                                  ToolTip="关闭"
  30.                                  Visibility="{Binding DialogMode, Converter={helper:EnumExcludeConverter}, ConverterParameter='Mini'}" />
  31.             <ContentPresenter Grid.Row="1" />
  32.         </Grid>
  33.     </Border>
  34. </ControlTemplate>
  35.             
  36.         </StackPanel.Resources>
  37.         <Image Width="32" Height="34"
  38.                HorizontalAlignment="Right"
  39.                RenderOptions.BitmapScalingMode="LowQuality"
  40.                RenderOptions.CachingHint="Cache"
  41.                SnapsToDevicePixels="False"
  42.                Source="{Binding DialogType, Converter={StaticResource AlterDialogWindow_IconConverter}}"
  43.                Stretch="UniformToFill" />
  44.         <ScrollViewer MaxWidth="300" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
  45.             <toolkit:SelectableTextBlock Margin="0,0,0,0"
  46.                                          HorizontalAlignment="Left" FontSize="18"
  47.                                          Foreground="#333333"
  48.                                          Text="{Binding Content}"
  49.                                          TextWrapping="Wrap" />
  50.         </ScrollViewer>
  51.     </StackPanel>
  52. </DataTemplate>
  53. <DataTemplate x:Key="TemplateNormal">
  54.     <StackPanel Margin="40,18,40,0" HorizontalAlignment="Center" VerticalAlignment="Top" Orientation="Horizontal">
  55.         <StackPanel.Resources>
  56.             
  57.             
  58.         </StackPanel.Resources>
  59.         <Image Width="40" Height="42"
  60.                HorizontalAlignment="Right"
  61.                RenderOptions.BitmapScalingMode="LowQuality"
  62.                RenderOptions.CachingHint="Cache"
  63.                SnapsToDevicePixels="False"
  64.                Source="{Binding DialogType, Converter={StaticResource AlterDialogWindow_IconConverter}}"
  65.                Stretch="UniformToFill" />
  66.         <ScrollViewer MaxWidth="280" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
  67.             <toolkit:SelectableTextBlock Margin="0,0,0,0"
  68.                                          HorizontalAlignment="Left" FontSize="18"
  69.                                          Foreground="#333333"
  70.                                          Text="{Binding Content}"
  71.                                          TextWrapping="Wrap" />
  72.         </ScrollViewer>
  73.     </StackPanel>
  74. </DataTemplate>
  75. <DataTemplate x:Key="TemplateFull">
  76.     <Grid Margin="40,10,40,0" HorizontalAlignment="Center" VerticalAlignment="Top">
  77.         <Grid.RowDefinitions>
  78.             <RowDefinition />
  79.             <RowDefinition />
  80.         </Grid.RowDefinitions>
  81.         <Image Width="54" Height="56"
  82.                HorizontalAlignment="Center"
  83.                RenderOptions.BitmapScalingMode="LowQuality"
  84.                RenderOptions.CachingHint="Cache"
  85.                SnapsToDevicePixels="False"
  86.                Source="{Binding DialogType, Converter={StaticResource AlterDialogWindow_IconConverter}}"
  87.                Stretch="UniformToFill" />
  88.         <ScrollViewer Grid.Row="1" MaxWidth="300"
  89.                       Margin="0,12,0,0"
  90.                       HorizontalScrollBarVisibility="Disabled"
  91.                       VerticalScrollBarVisibility="Auto">
  92.             <StackPanel>
  93.                 <toolkit:SelectableTextBlock Margin="0,0,0,0"
  94.                                              HorizontalAlignment="Center"
  95.                                              FontSize="18" Foreground="#333333"
  96.                                              Text="{Binding Content}"
  97.                                              TextWrapping="Wrap" />
  98.                 <toolkit:SelectableTextBlock HorizontalAlignment="Center" FontSize="14" Foreground="#999999" Text="{Binding SubContent}" />
  99.             </StackPanel>
  100.         </ScrollViewer>
  101.     </Grid>
  102. </DataTemplate>
复制代码
交互区域可定义两个模板:仅显示确定按钮,显示确定和取消按钮。
  1. <DataTemplate x:Key="Template0">
  2.     <StackPanel Orientation="Horizontal">
  3.         <toolkit:ImageButton Width="108" Height="56"
  4.                              Command="{Binding YesCommand}"
  5.                              DownImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|2'}"
  6.                              Foreground="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|3'}"
  7.                              HoverImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|1'}"
  8.                              NormalImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|0'}">
  9.             <Grid>
  10.                 <TextBlock FontSize="16" Foreground="White" Text="{Binding YesButtonText}" Visibility="{Binding IsCountdown, Converter={StaticResource VisibilityConverter}, ConverterParameter='!'}" />
  11.                 <StackPanel Orientation="Horizontal" TextBlock.Foreground="White" Visibility="{Binding IsCountdown, Converter={StaticResource VisibilityConverter}}">
  12.                     <TextBlock FontSize="16" Text="{Binding YesButtonText}" />
  13.                     <TextBlock FontSize="14" Text="{Binding Countdown, StringFormat={}({0}s)}" />
  14.                 </StackPanel>
  15.             </Grid>
  16.         </toolkit:ImageButton>
  17.         <toolkit:ImageButton Width="108" Height="32"
  18.                              Margin="29,0,0,0"
  19.                              Command="{Binding NoCommand}"
  20.                              DownImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='1|2'}"
  21.                              Foreground="#366d85"
  22.                              HoverImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='1|1'}"
  23.                              IsDefault="True"
  24.                              NormalImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='1|0'}">
  25.             <TextBlock FontSize="16" Foreground="#0099ff" Text="{Binding NoButtonText}" />
  26.         </toolkit:ImageButton>
  27.     </StackPanel>
  28. </DataTemplate>
  29. <DataTemplate x:Key="Template1">
  30.     <StackPanel Orientation="Horizontal">
  31.         <toolkit:ImageButton Width="108" Height="56"
  32.                              Command="{Binding YesCommand}"
  33.                              DownImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|2'}"
  34.                              FontSize="18"
  35.                              Foreground="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|3'}"
  36.                              HoverImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|1'}"
  37.                              IsDefault="True"
  38.                              NormalImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|0'}">
  39.             <Grid>
  40.                 <TextBlock FontSize="16" Foreground="White" Text="{Binding YesButtonText}" Visibility="{Binding IsCountdown, Converter={StaticResource VisibilityConverter}, ConverterParameter='!'}" />
  41.                 <StackPanel Orientation="Horizontal" TextBlock.Foreground="White" Visibility="{Binding IsCountdown, Converter={StaticResource VisibilityConverter}}">
  42.                     <TextBlock FontSize="16" Text="{Binding YesButtonText}" />
  43.                     <TextBlock FontSize="14" Text="{Binding Countdown, StringFormat={}({0}s)}" />
  44.                 </StackPanel>
  45.             </Grid>
  46.         </toolkit:ImageButton>
  47.     </StackPanel>
  48. </DataTemplate>
复制代码
定义好了信息区域和交互区域的几种模板后,AlterDialogWindow声明两个ContentPresenter表示信息区域和交互区域,通过模板选择器选择相应模板。其中交互区域通过绑定对话框类型来判断是否显示该区域。
  1. <Grid>
  2.     <Grid.RowDefinitions>
  3.         <RowDefinition Height="*" />
  4.         <RowDefinition Height="auto" />
  5.     </Grid.RowDefinitions>
  6.     <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Top" Content="{Binding}">
  7.         <ContentPresenter.ContentTemplateSelector>
  8.             <local:AlterDialogWindowContentTemplateSelector Template0="{StaticResource TemplateMini}" Template1="{StaticResource TemplateNormal}" Template2="{StaticResource TemplateFull}" />
  9.         </ContentPresenter.ContentTemplateSelector>
  10.     </ContentPresenter>
  11.     <ContentPresenter Grid.Row="1" Margin="0,0,0,16"
  12.                       HorizontalAlignment="center"
  13.                       VerticalAlignment="Top"
  14.                       Content="{Binding}"
  15.                       Visibility="{Binding DialogMode, Converter={helper:EnumExcludeConverter}, ConverterParameter='Mini'}">
  16.         <ContentPresenter.ContentTemplateSelector>
  17.             <local:AlterDialogWindowButtonDataTemplateSelector Template0="{StaticResource Template0}" Template1="{StaticResource Template1}" />
  18.         </ContentPresenter.ContentTemplateSelector>
  19.     </ContentPresenter>
  20. </Grid>
复制代码
至此,一个消息对话框就基本完成了。前边确定功能时提到调用消息对话框的窗口显示遮罩层。针对这个功能,我们可以在AlterDialogWindow中定义一个ShowDialog方法,参数是调用消息对话框的窗口对象,然后在该窗口中加上一个半透明的Grid作为遮罩层,并在AlterDialogWindow的OnClosed事件处理逻辑中删除遮罩层。
  1. public bool? ShowDialog(DependencyObject parent)
  2. {
  3.     if (this.Parent == null && parent != null)
  4.     {
  5.         Grid layer = new Grid() { Name = "maskLayer", Background = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0)) };
  6.         _grid = Window.GetWindow(parent).FindFirstVisualChild<Grid>();
  7.         if (_grid.FindAllVisualChilds<Grid>().FirstOrDefault(r => r.Name == "maskLayer") == null)
  8.             _grid.Children.Add(layer);
  9.         if (_grid.RowDefinitions.Count > 0)
  10.             Grid.SetRowSpan(layer, _grid.RowDefinitions.Count);
  11.         if (_grid.ColumnDefinitions.Count > 0)
  12.             Grid.SetColumnSpan(layer, _grid.ColumnDefinitions.Count);
  13.         this.Owner = Window.GetWindow(parent);
  14.         this.WindowStartupLocation = WindowStartupLocation.CenterOwner;
  15.     }
  16.     return ShowDialog();
  17. }
复制代码
小结

本文介绍了自定义消息对话框的主要思路和代码,通过造轮子,重新温习了样式、主题、控件模板、数据模板、模板选择器、触发器、值转换器等技术。这也是MaterialDesign、HandyControl等控件珠玉在前,还要自己造轮子的原因之一。
代码示例

https://github.com/czwy/AlertDialogWindow.git

来源:https://www.cnblogs.com/czwy/archive/2023/11/22/17850306.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具