自定义Window有多是设计或功能上的要求,能够是非必要的,而自定义RibbonWindow则不同:html
如上图所示,在Windows 10 上运行打开RibbonWindow,能够看到标题栏的内容(包括分隔符)没有居中对齐,缺乏下边框。git
在最大化的时候标题栏内容甚至超出屏幕范围。github
WPF提供的Ribbon是个很古老很古老的控件,附带的RibbonWindow也十分古老。RibbonWindow在之前应该能够运行良好,但多年没有更新,在.NET 4.5(或者说是WIN7平台,我没仔细考究)后就出现了这个问题。做为专业软件这可能无法接受,而这个问题微软好像也没打算修复。之前的作法一般是使用Fluent.Ribbon之类的第三方组件,由于我已经在Kino.Toolkit.Wpf中提供了使用WindowChrome自定义的Window,为了统一外观因而顺手自定义一个ExtendedRibbonWindow。chrome
RibbonWindow是派生自Window,并使用了WindowChrome,它的ControlTemplate大概是这样:shell
<Grid> <Border Name="PART_ClientAreaBorder" Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="{TemplateBinding Control.BorderThickness}" Margin="{Binding Path=(SystemParameters.WindowNonClientFrameThickness)}" /> <Border BorderThickness="{Binding Path=(WindowChrome.WindowChrome).ResizeBorderThickness, RelativeSource={RelativeSource TemplatedParent}}"> <Grid> <Image Name="PART_Icon" WindowChrome.IsHitTestVisibleInChrome="True" HorizontalAlignment="Left" VerticalAlignment="Top" Width="{Binding Path=(SystemParameters.SmallIconWidth)}" Height="{Binding Path=(SystemParameters.SmallIconHeight)}" /> <AdornerDecorator> <ContentPresenter Name="PART_RootContentPresenter" /> </AdornerDecorator> <ResizeGrip Name="WindowResizeGrip" WindowChrome.ResizeGripDirection="BottomRight" HorizontalAlignment="Right" VerticalAlignment="Bottom" Visibility="Collapsed" IsTabStop="False" /> </Grid> </Border> </Grid>
Ribbon的Chrome部分彻底依赖于WindowChrome,PART_ClientAreaBorder负责为ClientArea提供背景颜色。在PART_ClientAreaBorder后面的另外一个Border才是真正的ClientArea部分,它用于放置Ribbon。由于Ribbon的一些按钮位于标题栏,因此Ribbon必须占用标题栏的位置,而且由Ribbon显示本来应该由Window显示的标题。WindowChrome的标题栏高度是SystemParameters.WindowNonClientFrameThickness.Top,在Windows 10,100% DPI的状况下为27像素。而Ribbon标题栏部分使用了SystemParameters.WindowCaptionHeight做为高度,这个属性的值为23,因此才会出现对不齐的问题。c#
<DockPanel Grid.Column="0" Grid.ColumnSpan="3" LastChildFill="True" Height="{Binding Path=(SystemParameters.WindowCaptionHeight)}">
而最大化的时候彻底没有调整Ribbon的Margin,而且WindowChrome自己在最大化就会有问题。因此不能直接使用WindowChrome,而应该使用自定义的UI覆盖WindowChrome的内容。windows
我在Kino.Toolkit.Wpf提供了一个自定义RibbonWindow,基本上代码和ControlTempalte与自定义Window同样,运行效果如上图所示。在自定义RibbonWindow里我添加了RibbonStyle属性,默认值是一个解决Ribbon标题栏问题的Ribbon样式,里面使用SystemParameters.WindowNonClientFrameThickness做为标题的高度。api
<DockPanel Grid.Column="0" Grid.ColumnSpan="3" Margin="0,-1,0,0" Height="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}" LastChildFill="True">
RibbonWindow还添加了一个StyleTypedProperty:ide
[StyleTypedProperty(Property = nameof(RibbonStyle), StyleTargetType = typeof(Ribbon))]
StyleTypedProperty 应用于类定义并肯定类型为 TargetType 的属性的 Style。使用了这个属性的控件能够在Blend中使用 "右键"->"编辑其余模板"->"编辑RibbonSytle" 建立Ribbon的Style。设计
不过虽然我这么贴心地加上这个Attribute,但个人Blend复制Ribbon模板老是报错。
我也见过一些很专业的软件没处理RibbonWindow,反正外观上的问题忍一忍就过去了,实在受不了能够买一个有现代化风格的控件库,只是为了标题栏对不齐这种小事比较难说服上面赞成引入一个新的组件。除了使用我提供的解决方案,stackoverflow也由很多关于这个问题的讨论及解决方案可供参考,例如这个:
c# - WPF RibbonWindow + Ribbon = Title outside screen - Stack Overflow
顺便一提,ExtendedRibbonWindow须要继承RibbonWindow,因此无法直接集成ExtendedWindow。由于ExtendedWindow不少功能都试用附加属性和控件代码分离,因此ExtendedRibbonWindow须要重复的代码不会太多。
RibbonWindow Class (System.Windows.Controls.Ribbon) Microsoft Docs
Ribbon Class (System.Windows.Controls.Ribbon) Microsoft Docs
WindowChrome Class (System.Windows.Shell) Microsoft Docs
SystemParameters Class (System.Windows) Microsoft Docs
StyleTypedPropertyAttribute Class (System.Windows) Microsoft Docs