首页 > 其他分享 >[WPF]动手写一个简单的消息对话框

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

时间:2023-12-20 09:45:16浏览次数:32  
标签:对话框 动手 区域 grid 消息 按钮 WPF 模板

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

确定“轮子”的功能#

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

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

开始造“轮子”#

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

自定义窗口外观#

标准的窗口由两个重叠的矩形组成。外部矩形是非工作区,其中包括标题栏按钮(最小化、最大化和关闭) 、窗口边框、调整大小和移动行为、应用程序图标和标题以及系统菜单。它由操作系统的窗口管理器绘制和管理。其尺寸由标准操作系统设置决定。内部矩形是工作区,也就是应用程序的内容。
自定义窗口外观主要是针对非工作区,可以通过设置属性WindowStyleNone,或者使用 WindowChrome类来自定义。这里我们使用前一种方法。

<!-- 弹出提示窗体模板 -->
<ControlTemplate x:Key="AlertDialogBaseTemplate" TargetType="{x:Type Window}">
    <Border x:Name="border" Margin="0"
            Background="White" CornerRadius="3"
            RenderTransformOrigin="0.5,0.5">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Loaded">
                <helper:EventToCommand Command="{Binding LoadedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
        <Border.RenderTransform>
            <TransformGroup>
                <ScaleTransform />
            </TransformGroup>
        </Border.RenderTransform>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <toolkit:ImageButton Grid.Row="0" Width="16" Height="16"
                                 Margin="0,16,16,0"
                                 HorizontalAlignment="Right"
                                 VerticalAlignment="Bottom"
                                 Command="{Binding CloseWinCommand}"
                                 CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
                                 DownImage="Images/AlterDialog/btnclose_hover.png"
                                 HoverImage="Images/AlterDialog/btnclose_hover.png"
                                 NormalImage="Images/AlterDialog/btnclose.png"
                                 ToolTip="关闭"
                                 Visibility="{Binding DialogMode, Converter={helper:EnumExcludeConverter}, ConverterParameter='Mini'}" />
            <ContentPresenter Grid.Row="1" />
        </Grid>
    </Border>
</ControlTemplate>

<!-- 弹出提示窗体样式 -->
<Style x:Key="AlterDailogBaseStyle" TargetType="{x:Type view:AlterDialogWindow}" BasedOn="{StaticResource BaseWindowStyle}">
    <Setter Property="AllowsTransparency" Value="True" />
    <Setter Property="Height" Value="180" />
    <Setter Property="MaxHeight" Value="240" />
    <Setter Property="MaxWidth" Value="400" />
    <Setter Property="OverridesDefaultStyle" Value="True" />
    <Setter Property="Template" Value="{StaticResource AlertDialogBaseTemplate}" />
    <Setter Property="Topmost" Value="False" />
    <Setter Property="Width" Value="400" />
    <Setter Property="WindowState" Value="Normal" />
    <Setter Property="WindowStyle" Value="None" />
</Style>

<Style TargetType="{x:Type view:AlterDialogWindow}" BasedOn="{StaticResource AlterDailogBaseStyle}" />

上述代码中,通过把WindowStyle属性设置为None来隐藏默认的非工作区(控制区),然后再窗口的Template中定义一个两行的Grid,第一行模拟窗口非工作区的标题栏,本例中仅放一个关闭按钮。第二行则是工作区。

分享一个小小的经验:在定义AlterDialogWindow样式的时候,最后一行代码仅仅是定义了一个TargetTypeview:AlterDialogWindow的样式,并且通过BasedOn继承自 x:Key="AlterDailogBaseStyle"的样式。这样做并非多此一举,而是为了方便局部需要个性化样式时最大限度地复用默认的全局样式。

自定义消息对话框模板#

消息对话框整体可以划分为信息区域和交互区域两部分。信息区域呈现消息类型和消息内容,交互区域用于呈现确定和取消按钮。信息区域的布局及大小与对话框类型相关。交互区域则与消息类型以及对话框类型都有关。提示、警告、错误这三类消息是通知警示的作用,不需要用户做出YES or NO的处理,仅需要显示确定按钮即可,询问类信息则需要显示确定和取消两个按钮。迷你模式的对话框则不需显示确定和取消按钮,因此整个交互区都不显示。
根据三种类型的对话框定义三个信息区域的模板:

<DataTemplate x:Key="TemplateMini">
    <StackPanel Margin="40,15,40,15" HorizontalAlignment="Center" Orientation="Horizontal">
        <StackPanel.Resources>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="VerticalAlignment" Value="Center" />
            </Style>
            <Style TargetType="{x:Type toolkit:SelectableTextBlock}">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="VerticalAlignment" Value="Center" />
            </Style>
        </StackPanel.Resources>
        <Image Width="32" Height="34"
               HorizontalAlignment="Right"
               RenderOptions.BitmapScalingMode="LowQuality"
               RenderOptions.CachingHint="Cache"
               SnapsToDevicePixels="False"
               Source="{Binding DialogType, Converter={StaticResource AlterDialogWindow_IconConverter}}"
               Stretch="UniformToFill" />
        <ScrollViewer MaxWidth="300" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
            <toolkit:SelectableTextBlock Margin="0,0,0,0"
                                         HorizontalAlignment="Left" FontSize="18"
                                         Foreground="#333333"
                                         Text="{Binding Content}"
                                         TextWrapping="Wrap" />
        </ScrollViewer>
    </StackPanel>
</DataTemplate>

<DataTemplate x:Key="TemplateNormal">
    <StackPanel Margin="40,18,40,0" HorizontalAlignment="Center" VerticalAlignment="Top" Orientation="Horizontal">
        <StackPanel.Resources>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="VerticalAlignment" Value="Center" />
            </Style>
            <Style TargetType="{x:Type toolkit:SelectableTextBlock}">
                <Setter Property="FontSize" Value="18" />
                <Setter Property="VerticalAlignment" Value="Center" />
            </Style>
        </StackPanel.Resources>
        <Image Width="40" Height="42"
               HorizontalAlignment="Right"
               RenderOptions.BitmapScalingMode="LowQuality"
               RenderOptions.CachingHint="Cache"
               SnapsToDevicePixels="False"
               Source="{Binding DialogType, Converter={StaticResource AlterDialogWindow_IconConverter}}"
               Stretch="UniformToFill" />
        <ScrollViewer MaxWidth="280" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
            <toolkit:SelectableTextBlock Margin="0,0,0,0"
                                         HorizontalAlignment="Left" FontSize="18"
                                         Foreground="#333333"
                                         Text="{Binding Content}"
                                         TextWrapping="Wrap" />
        </ScrollViewer>
    </StackPanel>
</DataTemplate>

<DataTemplate x:Key="TemplateFull">
    <Grid Margin="40,10,40,0" HorizontalAlignment="Center" VerticalAlignment="Top">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Image Width="54" Height="56"
               HorizontalAlignment="Center"
               RenderOptions.BitmapScalingMode="LowQuality"
               RenderOptions.CachingHint="Cache"
               SnapsToDevicePixels="False"
               Source="{Binding DialogType, Converter={StaticResource AlterDialogWindow_IconConverter}}"
               Stretch="UniformToFill" />
        <ScrollViewer Grid.Row="1" MaxWidth="300"
                      Margin="0,12,0,0"
                      HorizontalScrollBarVisibility="Disabled"
                      VerticalScrollBarVisibility="Auto">
            <StackPanel>
                <toolkit:SelectableTextBlock Margin="0,0,0,0"
                                             HorizontalAlignment="Center"
                                             FontSize="18" Foreground="#333333"
                                             Text="{Binding Content}"
                                             TextWrapping="Wrap" />
                <toolkit:SelectableTextBlock HorizontalAlignment="Center" FontSize="14" Foreground="#999999" Text="{Binding SubContent}" />
            </StackPanel>
        </ScrollViewer>
    </Grid>
</DataTemplate>

交互区域可定义两个模板:仅显示确定按钮,显示确定和取消按钮。

<DataTemplate x:Key="Template0">
    <StackPanel Orientation="Horizontal">
        <toolkit:ImageButton Width="108" Height="56"
                             Command="{Binding YesCommand}"
                             DownImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|2'}"
                             Foreground="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|3'}"
                             HoverImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|1'}"
                             NormalImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|0'}">
            <Grid>
                <TextBlock FontSize="16" Foreground="White" Text="{Binding YesButtonText}" Visibility="{Binding IsCountdown, Converter={StaticResource VisibilityConverter}, ConverterParameter='!'}" />
                <StackPanel Orientation="Horizontal" TextBlock.Foreground="White" Visibility="{Binding IsCountdown, Converter={StaticResource VisibilityConverter}}">
                    <TextBlock FontSize="16" Text="{Binding YesButtonText}" />
                    <TextBlock FontSize="14" Text="{Binding Countdown, StringFormat={}({0}s)}" />
                </StackPanel>
            </Grid>
        </toolkit:ImageButton>
        <toolkit:ImageButton Width="108" Height="32"
                             Margin="29,0,0,0"
                             Command="{Binding NoCommand}"
                             DownImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='1|2'}"
                             Foreground="#366d85"
                             HoverImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='1|1'}"
                             IsDefault="True"
                             NormalImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='1|0'}">
            <TextBlock FontSize="16" Foreground="#0099ff" Text="{Binding NoButtonText}" />
        </toolkit:ImageButton>

    </StackPanel>
</DataTemplate>

<DataTemplate x:Key="Template1">
    <StackPanel Orientation="Horizontal">
        <toolkit:ImageButton Width="108" Height="56"
                             Command="{Binding YesCommand}"
                             DownImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|2'}"
                             FontSize="18"
                             Foreground="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|3'}"
                             HoverImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|1'}"
                             IsDefault="True"
                             NormalImage="{Binding DialogType, Converter={StaticResource AlterDialogWindow_ButtonConverter}, ConverterParameter='0|0'}">
            <Grid>
                <TextBlock FontSize="16" Foreground="White" Text="{Binding YesButtonText}" Visibility="{Binding IsCountdown, Converter={StaticResource VisibilityConverter}, ConverterParameter='!'}" />
                <StackPanel Orientation="Horizontal" TextBlock.Foreground="White" Visibility="{Binding IsCountdown, Converter={StaticResource VisibilityConverter}}">
                    <TextBlock FontSize="16" Text="{Binding YesButtonText}" />
                    <TextBlock FontSize="14" Text="{Binding Countdown, StringFormat={}({0}s)}" />
                </StackPanel>
            </Grid>
        </toolkit:ImageButton>
    </StackPanel>
</DataTemplate>

定义好了信息区域和交互区域的几种模板后,AlterDialogWindow声明两个ContentPresenter表示信息区域和交互区域,通过模板选择器选择相应模板。其中交互区域通过绑定对话框类型来判断是否显示该区域。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="auto" />
    </Grid.RowDefinitions>
    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Top" Content="{Binding}">
        <ContentPresenter.ContentTemplateSelector>
            <local:AlterDialogWindowContentTemplateSelector Template0="{StaticResource TemplateMini}" Template1="{StaticResource TemplateNormal}" Template2="{StaticResource TemplateFull}" />
        </ContentPresenter.ContentTemplateSelector>
    </ContentPresenter>
    <ContentPresenter Grid.Row="1" Margin="0,0,0,16"
                      HorizontalAlignment="center"
                      VerticalAlignment="Top"
                      Content="{Binding}"
                      Visibility="{Binding DialogMode, Converter={helper:EnumExcludeConverter}, ConverterParameter='Mini'}">
        <ContentPresenter.ContentTemplateSelector>
            <local:AlterDialogWindowButtonDataTemplateSelector Template0="{StaticResource Template0}" Template1="{StaticResource Template1}" />
        </ContentPresenter.ContentTemplateSelector>
    </ContentPresenter>
</Grid>

至此,一个消息对话框就基本完成了。前边确定功能时提到调用消息对话框的窗口显示遮罩层。针对这个功能,我们可以在AlterDialogWindow中定义一个ShowDialog方法,参数是调用消息对话框的窗口对象,然后在该窗口中加上一个半透明的Grid作为遮罩层,并在AlterDialogWindowOnClosed事件处理逻辑中删除遮罩层。

public bool? ShowDialog(DependencyObject parent)
{
    if (this.Parent == null && parent != null)
    {
        Grid layer = new Grid() { Name = "maskLayer", Background = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0)) };
        _grid = Window.GetWindow(parent).FindFirstVisualChild<Grid>();
        if (_grid.FindAllVisualChilds<Grid>().FirstOrDefault(r => r.Name == "maskLayer") == null)
            _grid.Children.Add(layer);
        if (_grid.RowDefinitions.Count > 0)
            Grid.SetRowSpan(layer, _grid.RowDefinitions.Count);
        if (_grid.ColumnDefinitions.Count > 0)
            Grid.SetColumnSpan(layer, _grid.ColumnDefinitions.Count);
        this.Owner = Window.GetWindow(parent);
        this.WindowStartupLocation = WindowStartupLocation.CenterOwner;
    }
    return ShowDialog();
}

小结#

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

代码示例

https://github.com/czwy/AlertDialogWindow

 

【出处】:https://www.cnblogs.com/czwy/p/17850306.html

=======================================================================================

标签:对话框,动手,区域,grid,消息,按钮,WPF,模板
From: https://www.cnblogs.com/mq0036/p/17915519.html

相关文章

  • WPF使用SVG
    使用svg创建path<Window.Resources><Geometryx:Key="Icon-Close">M512.72533385.12c235.3920426.538667191.146667426.538667426.581333s-191.146667426.581333-426.538667426.581334c-235.4346670-426.581333-191.146667-426.581......
  • 界面控件DevExpress WPF Dock组件,轻松创建类Visual Studio窗口界面!
    本文主要为大家介绍DevExpressWPF控件中的Dock组件,它能帮助用户轻松创还能受MicrosoftVisualStudio启发的Dock窗口界面。P.S:DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用......
  • Wpf应用Path路径绘制圆弧
    [原]Wpf应用Path路径绘制圆弧 1. 移动指令:MoveCommand(M):M起始点 或者:m起始点比如:M100,240或m100,240使用大写M时,表示绝对值;使用小写m时;表示相对于前一点的值,如果前一点没有指定,则使用(0,0)。2. 绘制指令(DrawCommand):我们可以绘制以下形状:(1)直线:Line(L)(2)......
  • C++ Qt开发:自定义Dialog对话框组件
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍自定义Dialog组件的常用方法及灵活运用。在之前的文章中笔者已经为大家展示了默认Dialog组件的使......
  • C++ Qt开发:标准Dialog对话框组件
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍标准对话框QInputDialog、QFileDialog这两种对话框组件的常用方法及灵活运用。在Qt中,标准对话......
  • WPF实现边缘依靠效果
    最近做的某个功能需要用到边缘依靠,WPF实现了下,效果如下  主要实现原理如下:1、增加一块热点区域,鼠标进入时,触发显示动画,并隐藏热点区域2、鼠标拖动或离开窗体,判断窗体离屏幕边缘的距离,符合条件的,触发隐藏动画,并显示热点区域3、使用Window.Left属性进行窗体动画 需要注意......
  • WPF透明框设置Demo
    <Windowx:Class="GuiDB.EBMultiEditTextWin"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http......
  • [转][C#][WPF]原生使用 INotifyPropertyChanged
    在上一篇基础上,我们添加对字段的绑定效果:界面添加:<TextBoxText="{BindingName}"Width="120"Height="31"/>在 MainWindowModel里添加:publicclassMainWindowModel:INotifyPropertyChanged{publiceventPropertyChangedEventHandlerProper......
  • 【Avalonia】Avalonia的学习笔记以及与WPF的不同点
    1.axaml中引用命名空间xmlns:model="using:IDataTemplateSample.Models"2.Grid支持行列的简化写法<GridRowDefinitions="Auto,Auto,*"ColumnDefinitions="Auto,*"/>3.DataTemplate根据DataType自动选择对应类型的样式,使用Window.DataTemplates加载多个DataTem......
  • [转][C#][WPF]原生使用 ICommand
    传参这里参考:https://www.cnblogs.com/zhlziliaoku/p/5867556.html界面上添加一个按钮 <ButtonContent="点击"Width="120"Height="31"Command="{BindingshowCommand}"CommandParameter="{BindingRelativeSource={RelativeSourcese......