[WPF自定义控件]使用WindowChrome自定义Window Style

1.为什么要自定义Window对稍微有点规模的桌面软件来说自定义的Window几乎是标配了,一来设计师总是克制不住自己想想软件更个性化,为了UI的和谐修改Window也是必要的;二来多一行的空间可以添加很多功能,尤其是上边缘,因为被屏幕限制住鼠标的移动所以上边缘的按钮很容易选中。做桌面开发总有一天会遇到自定义Window的需求,所以我在控件库中也提供了一个简单的自定义Window。2.我想要的功能...

[WPF自定义控件]使用WindowChrome自定义Window Style

1. 为什么要自定义Window

对稍微有点规模的桌面软件来说自定义的Window几乎是标配了,一来设计师总是克制不住自己想想软件更个性化,为了UI的和谐修改Window也是必要的;二来多一行的空间可以添加很多功能,尤其是上边缘,因为被屏幕限制住鼠标的移动所以上边缘的按钮很容易选中。做桌面开发总有一天会遇到自定义Window的需求,所以我在控件库中也提供了一个简单的自定义Window。

2. 我想要的功能

我在上一篇文章介绍了标准Window的功能,我想实现一个包含这些基本功能的,窄边框、扁平化的Window,基本上模仿Windows 10 的Window,但要可以方便地自定义样式;阴影、动画效果保留系统默认的就可以了,基本上会很耐看。最后再放置一个FunctionBar方便添加更多功能。

最后成果如下:

这是一个名为ExtendedWindow的自定义Window,源码地址可见文章最后。

3. WindowChrome

3.1 为什么要使用WindowChrome自定义Window

WPF有两种主流的自定义Window的方案,《WPF编程宝典》介绍了使用Window“None“AllowsTransparency=“True“创建无边框的Window然后在里面仿造一个Window,以前也有很多博客详细介绍了这种方式,这里就不再赘述。这种方法的原理是从Window中删除non-client area(即chrome),再由用户自定义Window的所有外观和部分行为。这种方式的自由度很高,但也有不少问题:

  • Window没有阴影导致很难看,但添加自定义的DropShadowEffect又十分影响性能;
  • 没有弹出、关闭、最大化、最小化动画,尤其当启动了大量任务将任务栏堆满的情况下没有最小化动画很容易找不到自己的程序;
  • 没有动画很麻烦,自定义的动画做得不好也十分影响使用;
  • 需要写大量代码实现Window本来的拖动、改变大小、最大化等行为;
  • 各种其它细节的缺失;

大部分自定义Window或多或少都有上面所说的问题,幸好WPF提供了WindowChrome这个类用于创建自定义的Window,这个类本身处理了上面部分问题。

3.2 WindowChrome的基本概念

WindowChrome定义了Window non-client area(即chrome)的外观和行为, 在Window上应用WindowChrome的WindowChrome附加属性即可将Window的non-client area替换为WindowChrome(绕口):

<WindowChrome.WindowChrome> <WindowChrome /></WindowChrome.WindowChrome>

然后用Blend生成这个Window的Style,将最外层Border的背景移除并做了些简化后大概是这样:

<Window.Style> <Style TargetType=“{x:Type Window}“>  <Setter Property=“Template“><Setter.Value> <ControlTemplate TargetType=“{x:Type Window}“>  <Border><Grid> <AdornerDecorator>  <ContentPresenter /> </AdornerDecorator> <ResizeGrip x:Name=“WindowResizeGrip“ HorizontalAlignment=“Right“ IsTabStop=“false“ Visibility=“Collapsed“ VerticalAlignment=“Bottom“ /></Grid>  </Border>  <ControlTemplate.Triggers><MultiTrigger> <MultiTrigger.Conditions>  <Condition Property=“ResizeMode“ Value=“CanResizeWithGrip“ />  <Condition Property=“WindowState“ Value=“Normal“ /> </MultiTrigger.Conditions> <Setter Property=“Visibility“ TargetName=“WindowResizeGrip“ Value=“Visible“ /></MultiTrigger>  </ControlTemplate.Triggers> </ControlTemplate></Setter.Value>  </Setter> </Style></Window.Style>

这样一个没有Content的Window运行效果如下:

可以看到WindowChrome已经定义好noe-client area的边框、阴影、标题栏、右上角的三个按钮,ControleTemplate里也在右下角放置了一个ResizeGrip,而且拖动、改变大小、最大化最小化、动画等功能都已经做好了。除了Icon和标题外WindowChrome已经把一个标准的Window实现得差不多了。要实现自定义Window,只需要将我们想要的边框、Icon、标题、自定义样式的按钮等放在上面遮挡WindowChrome的各种元素就可以了。原理十分简单,接下来再看看WindowChrome的各个属性。

3.3 UseAeroCaptionButtons

UseAeroCaptionButtons表示标题栏上的那三个默认按钮是否可以命中,因为我们想要自己管理这三个按钮的样式、显示或隐藏,所以设置为False。

3.4 GlassFrameThickness和ResizeBorderThickness

GlassFrameThickness和ResizeBorderThickness,这两个属性用于控制边框,及用户可以单击并拖动以调整窗口大小的区域的宽度。如果两个都设置为50效果如下:

可以看到因为边框和ResizeBorder变大了,标题栏也下移了相应的距离(通过可拖动区域和SystemMenu的位置判断)。当然因为外观是我们自己定义的,ResizeBorderThickness也不需要这么宽,所以两个值都保留默认值就可以了。

3.5 CaptionHeight

CaptionHeight指定WindowChrome的标题栏高度。它不影响外观,因为WindowChrome的标题栏范围实际是不可见的,它包括可以拖动窗体、双击最大化窗体、右键打开SystemMenu等行为。

CaptionHeight、GlassFrameThickness和ResizeBorderThickness的默认值都和SystemParameters的对应的值一致。

3.6 IsHitTestVisibleInChrome附加属性

GlassFrameThickness和CaptionHeight定义了Chrome的范围,默认情况下任何在Chrome的范围内的元素都不可以交互,如果需要在标题栏放自己的按钮(或其它交互元素)需要将这个按钮的WindowsChrome.IsHitTestVisibleInChrome附加属性设置为True。

3.7 使用WindowChrome

综上所述,使用WindowChrome只需要设置UseAeroCaptionButtons为False,并且设置CaptionHeight,比较标准的做法是使用SystemParameter的WindowNonClientFrameThickness的Top,在100% DPI下是 27 像素(其它三个边都为4像素,因为我的目标是窄边框的Window,所以不会用这个值)。

<Setter Property=“WindowChrome.WindowChrome“> <Setter.Value>  <WindowChrome UseAeroCaptionButtons=“False“ CaptionHeight=“{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}“ /> </Setter.Value></Setter>

WindowChrome的文档有些旧了,文档中介绍的SystemParameters2在.NET 4.5已经找不到,在Github上还能找到不少它的实现,但没必要勉强用一个旧的API。

4. 自定义Window基本布局

<ControlTemplate TargetType=“{x:Type Window}“> <Border BorderBrush=“{TemplateBinding BorderBrush}“BorderThickness=“{TemplateBinding BorderThickness}“x:Name=“WindowBorder“>  <Grid x:Name=“LayoutRoot“  Background=“{TemplateBinding Background}“><Grid.RowDefinitions> <RowDefinition Height=“Auto“ /> <RowDefinition Height=“*“ /></Grid.RowDefinitions><Grid x:Name=“WindowTitlePanel“Height=“{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}“Background=“{TemplateBinding BorderBrush}“Margin=“0,-1,0,0“> <Grid.ColumnDefinitions>  <ColumnDefinition Width=“*“ />  <ColumnDefinition Width=“Auto“ /> </Grid.ColumnDefinitions> <StackPanel Orientation=“Horizontal“>  <Image Source=“{TemplateBinding Icon}“Height=“{x:Static SystemParameters.SmallIconHeight}“Width=“{x:Static SystemParameters.SmallIconWidth}“WindowChrome.IsHitTestVisibleInChrome=“True“ />  <ContentControl FontSize=“{DynamicResource {x:Static SystemFonts.CaptionFontSize}}“Content=“{TemplateBinding Title}“ /> </StackPanel> <StackPanel x:Name=“WindowCommandButtonsPanel“ Grid.Column=“1“ HorizontalAlignment=“Right“ Orientation=“Horizontal“ WindowChrome.IsHitTestVisibleInChrome=“True“ Margin=“0,0,-1,0“>  <ContentPresenter Content=“{Binding FunctionBar, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}“  Focusable=“False“ />  <Button x:Name=“MinimizeButton“ />  <Grid Margin=“1,0,1,0“><Button x:Name=“RestoreButton“  Visibility=“Collapsed“ /><Button x:Name=“MaximizeButton“ />  </Grid> 
源文地址:https://www.guoxiongfei.cn/cntech/19150.html