WPF中自定义标题栏时窗体最大化处理之WindowChrome
注意:
本文方法基础是WindowChrome,而WindowChrome在.NET Framework 4.5之后才集成发布的。见:WindowChrome Class
在.NET Framework 4.0中使用WindowChrome,需要安装Ribbon 来支持WindowChrome
目前官方文档的内容较为陈旧(但仍有参考价值),其中提到了SystemParameters2,这个应该是Ribbon里的东西,4.5想用可以安装Xceed.Wpf.AvalonDock库,这里面有现成的Microsoft.Windows.Shell.SystemParameters2实现——当然,自己不怕麻烦,可以自己实现来获取要用的系统变量。(一般用不着,4.5中SystemParameters添加了许多系统变量的实现)
实现步骤
第一步:基本实现【保留系统基础操作按钮】
添加Window的Style定义,并设置WindowChrome.WindowChrome属性;
设置WindowChrome标题栏:
CaptionHeight——主要用于拖动有效区;
GlassFrameThickness——影响标题栏系统按钮显示,0表示不使用系统按钮【后面介绍】,-1表示用的系统默认值,如下示例则表示标题栏高度30;
自定义窗体Title
注意:控件模板中的定义:
1、最外层Border背景无颜色,否则会覆盖标题栏,看不到系统按钮。
2、内部布局定义,使用Grid隔出30的标题栏高度,也可以直接对ContentPresenter设置Margin【主要是为了让顶部显示出标题栏】。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <Style x:Key ="WindowStyle1" TargetType ="{x:Type Window}" > <Setter Property ="WindowChrome.WindowChrome" > <Setter.Value > <WindowChrome CaptionHeight ="30" GlassFrameThickness ="0,30,0,0" /> </Setter.Value > </Setter > <Setter Property ="Template" > <Setter.Value > <ControlTemplate TargetType ="{x:Type Window}" > <Border BorderBrush ="{TemplateBinding BorderBrush}" BorderThickness ="{TemplateBinding BorderThickness}" > <AdornerDecorator > <Grid > <Grid.RowDefinitions > <RowDefinition Height ="30" /> <RowDefinition Height ="*" /> </Grid.RowDefinitions > <ContentPresenter Grid.Row ="1" /> <TextBlock Text ="{TemplateBinding Title}" HorizontalAlignment ="Left" VerticalAlignment ="Center" /> </Grid > </AdornerDecorator > </Border > </ControlTemplate > </Setter.Value > </Setter > </Style >
存在的问题: 最大化边框问题 —— 会有部分溢出。
根据观察,这个溢出宽度应该是8,此值的来源:(SystemParameters.MaximizedPrimaryScreenWidth - SystemParameters.WorkArea.Width)/2 ,高度也可以这样计算。
第二步:优化边界处理
方法1:模板添加最大化触发器,设置最大化时,内部布局Margin设为8
方法2:模板添加最大化触发器,设置最大化时,限制布局最大化的宽高最大值
不管使用哪种方法,最大化时,系统的标题栏高度都发生了变化,故,需要重新设置模板中定义的标题栏高度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <ControlTemplate TargetType ="{x:Type Window}" > <Border BorderBrush ="{TemplateBinding BorderBrush}" BorderThickness ="{TemplateBinding BorderThickness}" > <AdornerDecorator > <Grid Name ="win_content" > <Grid.RowDefinitions > <RowDefinition Height ="30" x:Name ="row_title" /> <RowDefinition Height ="*" /> </Grid.RowDefinitions > <ContentPresenter Grid.Row ="1" /> <TextBlock Text ="{TemplateBinding Title}" HorizontalAlignment ="Left" VerticalAlignment ="Center" /> </Grid > </AdornerDecorator > </Border > <ControlTemplate.Triggers > <Trigger Property ="WindowState" Value ="Maximized" > <Setter Property ="Margin" TargetName ="win_content" Value ="8" /> <Setter Property ="MaxWidth" TargetName ="win_content" Value ="{Binding Source={x:Static SystemParameters.WorkArea},Path=Width}" /> <Setter Property ="MaxHeight" TargetName ="win_content" Value ="{Binding Source={x:Static SystemParameters.WorkArea},Path=Height}" /> <Setter Property ="Height" TargetName ="row_title" Value ="22" /> </Trigger > </ControlTemplate.Triggers > </ControlTemplate >
至此,都是系统标题栏和我们自定义内容组合使用,但这样总有些边边角角要修正,下面就完全自定义标题栏
第三步:完全自定义标题栏【即,不使用系统的操作按钮】
初步操作类似第一步,其中将GlassFrameThickness设置为0
在内容定义部分添加自定义的标题栏,添加操作按钮,并设置按钮属性WindowChrome.IsHitTestVisibleInChrome="True"
如果不设置WindowChrome.IsHitTestVisibleInChrome,则由于我们之前设置CaptionHeight,则这个区域内,按钮将失效。
但是,也不能将整个标题栏布局设置这个属性,那样会完全覆盖系统标题栏的操作,如拖动效果,即CaptionHeight设置的那个区域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <Style x:Key ="WindowStyle2" TargetType ="{x:Type Window}" > <Setter Property ="WindowChrome.WindowChrome" > <Setter.Value > <WindowChrome UseAeroCaptionButtons ="False" GlassFrameThickness ="0" CaptionHeight ="30" /> </Setter.Value > </Setter > <Setter Property ="Template" > <Setter.Value > <ControlTemplate TargetType ="{x:Type Window}" > <Border BorderBrush ="{TemplateBinding BorderBrush}" BorderThickness ="{TemplateBinding BorderThickness}" Background ="{TemplateBinding Background}" > <AdornerDecorator > <ContentPresenter x:Name ="win_content" /> </AdornerDecorator > </Border > <ControlTemplate.Triggers > <Trigger Property ="WindowState" Value ="Maximized" > <Setter Property ="Margin" TargetName ="win_content" Value ="8" /> </Trigger > </ControlTemplate.Triggers > </ControlTemplate > </Setter.Value > </Setter > </Style >
1 2 3 4 5 6 7 8 9 10 <Grid Background ="Red" > <Grid Height ="30" HorizontalAlignment ="Stretch" VerticalAlignment ="Top" Background ="Blue" > <StackPanel Orientation ="Horizontal" WindowChrome.IsHitTestVisibleInChrome ="True" HorizontalAlignment ="Right" > <Button Name ="btn_Min" Content ="—" > </Button > <Button Name ="btn_Max" Content ="☐" > </Button > <Button Name ="btn_Close" Content ="✕" > </Button > </StackPanel > </Grid > </Grid >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public partial class MainWindow : Window { public MainWindow () { InitializeComponent(); this .btn_Min.Click += Btn_Min_Click; this .btn_Max.Click += Btn_Max_Click; this .btn_Close.Click += Btn_Close_Click; } private void Btn_Close_Click (object sender, RoutedEventArgs e ) { this .Close(); } private void Btn_Max_Click (object sender, RoutedEventArgs e ) { this .WindowState = WindowState.Maximized == this .WindowState ? WindowState.Normal : WindowState.Maximized; } private void Btn_Min_Click (object sender, RoutedEventArgs e ) { this .WindowState = WindowState.Minimized; } }
关联了解:
如果你平时也使用Visual Studio Code【VSCode】,文中的第一步和第三步,在vscode的设置中有对应效果:
第一步 —— 将vscode用户设置中Title Bar Style值设为native;
第三步 —— 将vscode用户设置中Title Bar Style值设为custom。
相关下载
点击查看完整源代码
解:奇葩史
落后要挨打,个人以前一直局限于老旧方式实现窗体处理,没有跟进WPF技术改进,在此,非常感谢@vbfool 的提醒。【之前说周末搞出来,自己这几周浪了,没研究。今天趁着别人双11买买买,咱就撸撸代码】