WPF三种自定义窗体的实现
目前我所知道的,有三种方式可以实现自定义窗体:WindowStyle="None"、WindowChrome、第三方库ControlzEx;但它们都有各自的优缺点,下面一一展示如何使用。
一、WindowStyle="None"
<Window x:Class="CustomWindows.TransparentWindow" 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:pui="clr-namespace:PumbaaUI.Controls;assembly=PumbaaUI" xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:ctrlz="clr-namespace:ControlzEx.Behaviors;assembly=ControlzEx" mc:Ignorable="d" Title="TransparentWindow" Height="450" Width="800" WindowStyle="None" AllowsTransparency="True" ResizeMode="CanResizeWithGrip" Background="#CCFFFFFF" BorderThickness="1" BorderBrush="{DynamicResource PuiBrushes.Theme}"> <Window.Template> <ControlTemplate TargetType="{x:Type Window}"> <Grid> <Border x:Name="LayoutRoot" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"> <DockPanel> <Border x:Name="border_title" DockPanel.Dock="Top" Height="28" Background="{DynamicResource PuiBrushes.Window.Title.Background}"> <DockPanel> <StackPanel DockPanel.Dock="Right"></StackPanel> <TextBlock Margin="12 0 0 0" VerticalAlignment="Center" Foreground="{DynamicResource PuiBrushes.Window.Title.Foregound}" TextTrimming="WordEllipsis" Text="{TemplateBinding Title}" /> </DockPanel> </Border> <Border Background="{TemplateBinding Background}"> <AdornerDecorator> <ContentPresenter /> </AdornerDecorator> </Border> </DockPanel> </Border> <ResizeGrip x:Name="WindowResizeGrip" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsTabStop="False" UseLayoutRounding="True" Visibility="Collapsed" /> </Grid> <ControlTemplate.Triggers> <Trigger Property="WindowState" Value="Maximized"> <Setter TargetName="LayoutRoot" Property="Margin" Value="{x:Static pui:WindowHelper.ChromeThickness}" /> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="ResizeMode" Value="CanResizeWithGrip" /> <Condition Property="WindowState" Value="Normal" /> </MultiTrigger.Conditions> <Setter TargetName="WindowResizeGrip" Property="Visibility" Value="Visible" /> </MultiTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Window.Template> <i:Interaction.Behaviors> <ctrlz:GlowWindowBehavior GlowBrush="{DynamicResource PuiBrushes.Theme80}" NonActiveGlowBrush="{DynamicResource PuiBrushes.Theme80}" ResizeBorderThickness="6" /> </i:Interaction.Behaviors> </Window>
要注意的几个点:
1、窗口大小无法拖拽;设置ResizeMode="CanResizeWithGrip",并且ControlTemplate的根元素是Panel,ResizeGrip其实可有可无;
2、窗口最大化时,溢出屏幕;对内容元素设置Margin,不要对根元素设置Margin,不会有效果,Margin的大小可能屏幕有关,计算方式如下
public static class WindowHelper { private static Nullable<Thickness> chromeThickness; public static Thickness ChromeThickness { get { if (chromeThickness.HasValue) return chromeThickness.Value; var w = (SystemParameters.MaximizedPrimaryScreenWidth - SystemParameters.WorkArea.Width) / 2; var h = (SystemParameters.MaximizedPrimaryScreenHeight - SystemParameters.WorkArea.Height) / 2; chromeThickness = new Thickness(w, h, w, h); return chromeThickness.Value; } } }
未必正确,我这里计算的值是8,实际上7才是完全全屏,第三方UI框架ModernUI就是写死的7,不过它使用的是WindowChrome,而我使用WindowChrome的时候8才是正确的,7相当于去掉1单位的边框,而我只需要最大化时将边框设为0即可;
3、允许窗口透明时,窗口没有发光效果;使用ControlzEx的GlowWindowBehavior可以设置窗口在Active和NoActive下发什么光;
还有什么缺点:
1、窗口最大化时覆盖了任务栏,相当于全屏;
二、WindowChrome
<Window x:Class="CustomWindows.ChormeWindow" 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:pui="clr-namespace:PumbaaUI.Controls;assembly=PumbaaUI" mc:Ignorable="d" Title="ChormeWindow" Height="450" Width="800" BorderThickness="1" BorderBrush="{DynamicResource PuiBrushes.Theme}"> <WindowChrome.WindowChrome> <WindowChrome CornerRadius="0" GlassFrameThickness="1" /> </WindowChrome.WindowChrome> <Window.Template> <ControlTemplate TargetType="{x:Type Window}"> <Border> <Border x:Name="LayoutRoot" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"> <DockPanel> <Border x:Name="border_title" DockPanel.Dock="Top" Height="28" Background="{DynamicResource PuiBrushes.Window.Title.Background}"> <DockPanel> <StackPanel DockPanel.Dock="Right"></StackPanel> <TextBlock Margin="12 0 0 0" VerticalAlignment="Center" Foreground="{DynamicResource PuiBrushes.Window.Title.Foregound}" TextTrimming="WordEllipsis" Text="{TemplateBinding Title}" /> </DockPanel> </Border> <Border Background="{TemplateBinding Background}"> <AdornerDecorator> <ContentPresenter /> </AdornerDecorator> </Border> </DockPanel> </Border> </Border> <ControlTemplate.Triggers> <Trigger Property="WindowState" Value="Maximized"> <Setter TargetName="LayoutRoot" Property="Margin" Value="{x:Static pui:WindowHelper.ChromeThickness}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Window.Template> </Window>
了解WindowChrome,可查看微软官方文档:https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.shell.windowchrome
要注意的几个点:
1、GlassFrameThickness设置成负数才会出现默认窗口按钮(不推荐,建议自己添加按钮),设置成0则没有发光效果(默认是黑色的光,如果想用不同颜色的光,推荐使用ControlzEx的GlowWindowBehavior)
2、需要注意的是CaptionHeight,指的是标题栏的高度,默认不等于0,右键标题栏会有一些窗口操作功能,并且在标题栏区域的按钮将无法响应鼠标事件,除非设置IsHitTestVisibleInChrome="True",不需要标题栏的可以将CaptionHeight设为0;
3、和WindowStyle="None"一样,最大化时会溢出屏幕,解决方式也一样,不过Margin的计算是正确的;
还有什么缺点:
1、如果不设置WindowStyle="None"的话,窗口不能实现透明,设置了WindowStyle="None",如果GlassFrameThickness="0",则修复了最大化全屏的问题,但是窗口内容底部溢出(在本身的溢出上,又溢出了一个任务栏高度),并且在此时如果窗口的任务栏大小发生或设置成自动隐藏,窗口的会认为此时不是最大化状态,且无法恢复到原来的大小;如果GlassFrameThickness!="0",则跟WindowStyle="None"效果一样,就跟没设置WindowChrome一样;
2、相比ControlzEx的WindowChromeBehavior,不能全屏显示,除非隐藏掉任务栏;
补充:
在设置ResizeMode=CanMinimize或NoResize时,最大化将显示为全屏;
三、ControlzEx
<Window x:Class="CustomWindows.ControlExWindow" 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:ctrlz="clr-namespace:ControlzEx.Behaviors;assembly=ControlzEx" mc:Ignorable="d" Title="ControlExWindow" Height="450" Width="800" BorderThickness="1" BorderBrush="{DynamicResource PuiBrushes.Theme}"> <Window.Template> <ControlTemplate TargetType="{x:Type Window}"> <Border x:Name="LayoutRoot" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"> <DockPanel> <Border x:Name="border_title" DockPanel.Dock="Top" Height="28" Background="{DynamicResource PuiBrushes.Window.Title.Background}"> <DockPanel> <StackPanel DockPanel.Dock="Right"></StackPanel> <TextBlock Margin="12 0 0 0" VerticalAlignment="Center" Foreground="{DynamicResource PuiBrushes.Window.Title.Foregound}" TextTrimming="WordEllipsis" Text="{TemplateBinding Title}" /> </DockPanel> </Border> <Border Background="{TemplateBinding Background}"> <AdornerDecorator> <ContentPresenter /> </AdornerDecorator> </Border> </DockPanel> </Border> </ControlTemplate> </Window.Template> <i:Interaction.Behaviors> <ctrlz:WindowChromeBehavior IgnoreTaskbarOnMaximize="True" /> <ctrlz:GlowWindowBehavior GlowBrush="{DynamicResource PuiBrushes.Theme80}" NonActiveGlowBrush="{DynamicResource PuiBrushes.Theme80}" ResizeBorderThickness="6" /> </i:Interaction.Behaviors> </Window>
了解ControlzEx,请查看https://github.com/ControlzEx/ControlzEx,内容很少,但每一点都可能是困扰你很久的问题;
要注意的几个点:
1、关于WindowChromeBehavior,设置IgnoreTaskbarOnMaximize="True",则最大化时覆盖任务栏,可以实现最大化和全屏的切换;
2、关于GlowWindowBehavior,不设置Brush和ResizeBorderThickness是不会发光的,默认值是Null和0,发光效果实际上是由四条带BlurEffect的Border组成,所以性能上不敢苟同;
还有什么缺点:
1、无论是否设置WindowStyle="None",窗口都无法透明;
总结:
如果不需要透明窗体,使用ControlzEx是最完美的,最大化的时候也不用处理溢出,可自由地切换最大化和全屏;如果需要使用透明窗体,只能使用WindowStyle="None"的方式了,唯一的缺点就是最大化就是全屏,而一般的应用都不会超过任务栏的。
另外第三方UI框架MahApps.Metro v2.1.1的窗口是比较完美的,它使用的是ControlzEx,但是效果又与我的demo不太一致,在窗口透明的情况下,窗口溢出且显示诡异,效果如下: