[WPF 自定义控件]使用WindowChrome自定义RibbonWindow
1. 为什么要自定义RibbonWindow
自定义Window有可能是设计或功能上的要求,可以是非必要的,而自定义RibbonWindow则不一样:
- 如果程序使用了自定义样式的Window,为了统一外观需要把RibbonWindow一起修改样式。
- 为了解决RibbonWindow的BUG。

如上图所示,在Windows 10 上运行打开RibbonWindow,可以看到标题栏的内容(包括分隔符)没有居中对齐,缺少下边框。

在最大化的时候标题栏内容甚至超出屏幕范围。
WPF提供的Ribbon是个很古老很古老的控件,附带的RibbonWindow也十分古老。RibbonWindow在以前应该可以运行良好,但多年没有更新,在.NET 4.5(或者说是WIN7平台,我没仔细考究)后就出现了这个问题。作为专业软件这可能没法接受,而这个问题微软好像也没打算修复。以前的做法通常是使用Fluent.Ribbon之类的第三方组件,因为我已经在Kino.Toolkit.Wpf中提供了使用WindowChrome自定义的Window,为了统一外观于是顺手自定义一个ExtendedRibbonWindow。
2. 问题产生的原因
RibbonWindow是派生自Window,并使用了WindowChrome,它的ControlTemplate大概是这样:
<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,所以才会出现对不齐的问题。
<DockPanel Grid.Column="0" Grid.ColumnSpan="3" LastChildFill="True" Height="{Binding Path=(SystemParameters.WindowCaptionHeight)}">
而最大化的时候完全没有调整Ribbon的Margin,并且WindowChrome本身在最大化就会有问题。所以不能直接使用WindowChrome,而应该使用自定义的UI覆盖WindowChrome的内容。
3. 自定义RibbonWindow

我在Kino.Toolkit.Wpf提供了一个自定义RibbonWindow,基本上代码和ControlTempalte与自定义Window一样,运行效果如上图所示。在自定义RibbonWindow里我添加了RibbonStyle属性,默认值是一个解决Ribbon标题栏问题的Ribbon样式,里面使用SystemParameters.WindowNonClientFrameThickness作为标题的高度。
<DockPanel Grid.Column="0"
Grid.ColumnSpan="3"
Margin="0,-1,0,0"
Height="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
LastChildFill="True">
RibbonWindow还添加了一个StyleTypedProperty:
[StyleTypedProperty(Property = nameof(RibbonStyle), StyleTargetType = typeof(Ribbon))]
StyleTypedProperty 应用于类定义并确定类型为 TargetType 的属性的 Style。使用了这个属性的控件可以在Blend中使用 "右键"->"编辑其他模板"->"编辑RibbonSytle" 创建Ribbon的Style。

不过虽然我这么贴心地加上这个Attribute,但我的Blend复制Ribbon模板总是报错。
4. 结语
我也见过一些很专业的软件没处理RibbonWindow,反正外观上的问题忍一忍就过去了,实在受不了可以买一个有现代化风格的控件库,只是为了标题栏对不齐这种小事比较难说服上面同意引入一个新的组件。除了使用我提供的解决方案,stackoverflow也由不少关于这个问题的讨论及解决方案可供参考,例如这个:
c# - WPF RibbonWindow + Ribbon = Title outside screen - Stack Overflow
顺便一提,ExtendedRibbonWindow需要继承RibbonWindow,所以没法直接集成ExtendedWindow。因为ExtendedWindow很多功能都试用附加属性和控件代码分离,所以ExtendedRibbonWindow需要重复的代码不会太多。
5. 参考
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
6. 源码
Kino.Toolkit.Wpf_Window at master
[WPF 自定义控件]使用WindowChrome自定义RibbonWindow的更多相关文章
- [WPF自定义控件]?使用WindowChrome自定义Window Style
原文:[WPF自定义控件]?使用WindowChrome自定义Window Style 1. 为什么要自定义Window 对稍微有点规模的桌面软件来说自定义的Window几乎是标配了,一来设计师总是克 ...
- [WPF自定义控件库]使用WindowChrome自定义RibbonWindow
原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自 ...
- wpf自定义控件中使用自定义事件
wpf自定义控件中使用自定义事件 1 创建自定义控件及自定义事件 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 2 ...
- [WPF自定义控件]使用WindowChrome自定义Window Style
1. 为什么要自定义Window 对稍微有点规模的桌面软件来说自定义的Window几乎是标配了,一来设计师总是克制不住自己想想软件更个性化,为了UI的和谐修改Window也是必要的:二来多一行的空间可 ...
- WPF中使用WindowChrome自定义窗口中遇到的最大化问题
FrameWork 4.5 之后,内置了WindowChrome类,官方文档: https://msdn.microsoft.com/en-us/library/system.windows.shel ...
- WPF自定义控件与样式-自定义按钮(Button)
一.前言 程序界面上的按钮多种多样,常用的就这几种:普通按钮.图标按钮.文字按钮.图片文字混合按钮.本文章记录了不同样式类型的按钮实现方法. 二.固定样式的按钮 固定样式的按钮一般在临时使用时或程序的 ...
- [WPF]使用WindowChrome自定义Window Style
1. 前言 做了WPF开发多年,一直未曾自己实现一个自定义Window Style,无论是<WPF编程宝典>或是各种博客都建议使用WindowStyle="None" ...
- [WPF 自定义控件]自定义控件库系列文章
Kino.Toolkit.Wpf Kino.Toolkit.Wpf是一组简单实用的WPF控件与工具,用于介绍自定义控件的入门.相关博客地址如下: 开始一个自定义控件库项目 介绍开始一个自定义控件库项目 ...
- WPF自定义控件与样式(13)-自定义窗体Window & 自适应内容大小消息框MessageBox
一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 自定义 ...
随机推荐
- contiue和break的用法
contiue和break的作用分别是什么: 1.continue的意思是终止本次循环,直接进入下一次循环. 注意: continue不能加在循环体的最后一步执行的代码,因为代码加上去毫无意义 2.b ...
- 《Java基础知识》序列化与反序列化详解
序列化的作用:为了不同jvm之间共享实例对象的一种解决方案.由java提供此机制. 序列化应用场景: 1. 分布式传递对象. 2. 网络传递对象. 3. tomcat关闭以后会把session对象序列 ...
- var和let部分浅析
ES6中新增了let命令,用于声明变量,但所声明的变量只在let命令的代码块内有效. 举个例子: var a = []; for(var i=0;i<10;i++){ a[i] = functi ...
- kuangbin专题 数论基础 part1?
线段树专题太难了,那我来做数学吧! 但数学太难了,我......(扯 这两天想了做了查了整理了几道数学. 除了一些进阶的知识,像莫比乌斯反演,杜教筛,min25学不会我跳了,一些基础的思维还是可以记录 ...
- 人工智能技术导论——使用PROLOG逻辑语言解决爱因斯坦斑马文件
一.背景 在上一篇博客https://www.cnblogs.com/wkfvawl/p/12056951.html上,我简单介绍了一下Prolog的一些基本语法,这篇博客主要通过老师课上讲过的一个问 ...
- k8s~跨namespace的service相互访问
在k8s里,你可以通过服务名去访问相同namespace里的服务,然后服务可以解析到对应的pod,从而再由pod转到对应的容器里,我们可以认为这个过程有两个port的概念,service port 就 ...
- 前端开发必备:nvm与npm与nrm
nvm Node.js version manager ,推荐使用它来安装 node.js . Mac 版项目地址 Windows 版项目地址 windows 版安装 进入 下载页 找到安装包: 跟着 ...
- GeoServer 2.15.0版本跨域问题解决方法
geoserver默认不开启跨域设置,开启步骤如下: 1.修改配置文件web.xml,该配置文件的路径如下 \webapps\geoserver\WEB-INF\web.xml 2.搜索:cross- ...
- IT兄弟连 HTML5教程 CSS3属性特效 倒影
在Web制作中,有些时候需要实现一些倒影的效果.在传统网页中,我们只能使用photoshop事先将倒影设计好,然后导入到网页中,这样不但耗费资源,也阻碍了开发效率.而CSS新增了Reflections ...
- Fortify---Detail--Sql注入
Abstract: 在 PaperDao.java 的第 40 行,checkQuesUsed() 方法调用通过不可信来源的输入构建的 SQL 查询.通过这种调用,攻击者能够修改指令的含义或执行任 ...