基于WPF手寫(xiě)一個(gè)簡(jiǎn)單的消息對(duì)話(huà)框
消息對(duì)話(huà)框是UI界面中不可或缺的組成部分,用于給用戶(hù)一些提示,警告或者詢(xún)問(wèn)的窗口。在WPF中,消息對(duì)話(huà)框是系統(tǒng)原生(user32.dll)的MessageBox,無(wú)法通過(guò)Style或者Template來(lái)修改消息對(duì)話(huà)框的外觀(guān)。因此,當(dāng)需要一個(gè)與應(yīng)用程序主題風(fēng)格一致的消息對(duì)話(huà)框時(shí),只能自己動(dòng)手造輪子了。
確定“輪子”的功能
消息對(duì)話(huà)框的核心功能是向用戶(hù)顯示信息,并在用戶(hù)對(duì)消息進(jìn)行處理前中斷用戶(hù)的操作。根據(jù)常見(jiàn)的應(yīng)用場(chǎng)景,可以梳理出以下幾點(diǎn)功能:
支持的消息類(lèi)型:提示信息、警告信息、錯(cuò)誤信息、詢(xún)問(wèn)信息
支持的對(duì)話(huà)框類(lèi)型:迷你模式(顯示簡(jiǎn)要信息并自動(dòng)關(guān)閉)、普通模式、完整模式(適用于消息內(nèi)容分層級(jí)顯示)
設(shè)置消息對(duì)話(huà)框是否將觸發(fā)源作為父窗體并顯示遮罩層
主要功能如下圖所示:
開(kāi)始造“輪子”
消息對(duì)話(huà)框本質(zhì)也是一個(gè)窗體,因此首先要做的是自定義一個(gè)彈窗的樣式,然后根據(jù)消息類(lèi)型以及對(duì)話(huà)框類(lèi)型定義相應(yīng)的模板。
自定義窗口外觀(guān)
標(biāo)準(zhǔn)的窗口由兩個(gè)重疊的矩形組成。外部矩形是非工作區(qū),其中包括標(biāo)題欄按鈕(最小化、最大化和關(guān)閉) 、窗口邊框、調(diào)整大小和移動(dòng)行為、應(yīng)用程序圖標(biāo)和標(biāo)題以及系統(tǒng)菜單。它由操作系統(tǒng)的窗口管理器繪制和管理。其尺寸由標(biāo)準(zhǔn)操作系統(tǒng)設(shè)置決定。內(nèi)部矩形是工作區(qū),也就是應(yīng)用程序的內(nèi)容。
自定義窗口外觀(guān)主要是針對(duì)非工作區(qū),可以通過(guò)設(shè)置屬性WindowStyle
為None
,或者使用 WindowChrome
類(lèi)來(lái)自定義。這里我們使用前一種方法。
<!-- 彈出提示窗體模板 --> <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="關(guān)閉" 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}" />
上述代碼中,通過(guò)把WindowStyle
屬性設(shè)置為None
來(lái)隱藏默認(rèn)的非工作區(qū)(控制區(qū)),然后再窗口的Template
中定義一個(gè)兩行的Grid
,第一行模擬窗口非工作區(qū)的標(biāo)題欄,本例中僅放一個(gè)關(guān)閉按鈕。第二行則是工作區(qū)。
分享一個(gè)小小的經(jīng)驗(yàn):在定義AlterDialogWindow
樣式的時(shí)候,最后一行代碼僅僅是定義了一個(gè)TargetType
為view:AlterDialogWindow
的樣式,并且通過(guò)BasedOn
繼承自 x:Key="AlterDailogBaseStyle"
的樣式。這樣做并非多此一舉,而是為了方便局部需要個(gè)性化樣式時(shí)最大限度地復(fù)用默認(rèn)的全局樣式。
自定義消息對(duì)話(huà)框模板
消息對(duì)話(huà)框整體可以劃分為信息區(qū)域和交互區(qū)域兩部分。信息區(qū)域呈現(xiàn)消息類(lèi)型和消息內(nèi)容,交互區(qū)域用于呈現(xiàn)確定和取消按鈕。信息區(qū)域的布局及大小與對(duì)話(huà)框類(lèi)型相關(guān)。交互區(qū)域則與消息類(lèi)型以及對(duì)話(huà)框類(lèi)型都有關(guān)。提示、警告、錯(cuò)誤這三類(lèi)消息是通知警示的作用,不需要用戶(hù)做出YES or NO的處理,僅需要顯示確定按鈕即可,詢(xún)問(wèn)類(lèi)信息則需要顯示確定和取消兩個(gè)按鈕。迷你模式的對(duì)話(huà)框則不需顯示確定和取消按鈕,因此整個(gè)交互區(qū)都不顯示。
根據(jù)三種類(lèi)型的對(duì)話(huà)框定義三個(gè)信息區(qū)域的模板:
<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>
交互區(qū)域可定義兩個(gè)模板:僅顯示確定按鈕,顯示確定和取消按鈕。
<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>
定義好了信息區(qū)域和交互區(qū)域的幾種模板后,AlterDialogWindow
聲明兩個(gè)ContentPresenter
表示信息區(qū)域和交互區(qū)域,通過(guò)模板選擇器選擇相應(yīng)模板。其中交互區(qū)域通過(guò)綁定對(duì)話(huà)框類(lèi)型來(lái)判斷是否顯示該區(qū)域。
<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>
至此,一個(gè)消息對(duì)話(huà)框就基本完成了。前邊確定功能時(shí)提到調(diào)用消息對(duì)話(huà)框的窗口顯示遮罩層。針對(duì)這個(gè)功能,我們可以在AlterDialogWindow
中定義一個(gè)ShowDialog
方法,參數(shù)是調(diào)用消息對(duì)話(huà)框的窗口對(duì)象,然后在該窗口中加上一個(gè)半透明的Grid
作為遮罩層,并在AlterDialogWindow
的OnClosed
事件處理邏輯中刪除遮罩層。
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(); }
小結(jié)
本文介紹了自定義消息對(duì)話(huà)框的主要思路和代碼,通過(guò)造輪子,重新溫習(xí)了樣式、主題、控件模板、數(shù)據(jù)模板、模板選擇器、觸發(fā)器、值轉(zhuǎn)換器等技術(shù)。這也是MaterialDesign、HandyControl等控件珠玉在前,還要自己造輪子的原因之一。
代碼示例
https://github.com/czwy/AlertDialogWindow
以上就是基于WPF手寫(xiě)一個(gè)簡(jiǎn)單的消息對(duì)話(huà)框的詳細(xì)內(nèi)容,更多關(guān)于WPF消息對(duì)話(huà)框的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#中HttpClient使用注意(預(yù)熱與長(zhǎng)連接)
本文主要介紹了C#中HttpClient使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02C?sharp?(#)?數(shù)據(jù)類(lèi)型獲取方式
這篇文章主要介紹了C?sharp?(#)?數(shù)據(jù)類(lèi)型獲取方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11C#實(shí)現(xiàn)隨鼠標(biāo)移動(dòng)窗體實(shí)例
這篇文章主要介紹了C#實(shí)現(xiàn)隨鼠標(biāo)移動(dòng)窗體實(shí)例,主要通過(guò)簡(jiǎn)單的窗體事件代碼即可實(shí)現(xiàn)鼠標(biāo)隨窗體移動(dòng)的功能,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2014-10-10