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的更多相关文章

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

    原文:[WPF自定义控件]?使用WindowChrome自定义Window Style 1. 为什么要自定义Window 对稍微有点规模的桌面软件来说自定义的Window几乎是标配了,一来设计师总是克 ...

  2. [WPF自定义控件库]使用WindowChrome自定义RibbonWindow

    原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自 ...

  3. 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 ...

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

    1. 为什么要自定义Window 对稍微有点规模的桌面软件来说自定义的Window几乎是标配了,一来设计师总是克制不住自己想想软件更个性化,为了UI的和谐修改Window也是必要的:二来多一行的空间可 ...

  5. WPF中使用WindowChrome自定义窗口中遇到的最大化问题

    FrameWork 4.5 之后,内置了WindowChrome类,官方文档: https://msdn.microsoft.com/en-us/library/system.windows.shel ...

  6. WPF自定义控件与样式-自定义按钮(Button)

    一.前言 程序界面上的按钮多种多样,常用的就这几种:普通按钮.图标按钮.文字按钮.图片文字混合按钮.本文章记录了不同样式类型的按钮实现方法. 二.固定样式的按钮 固定样式的按钮一般在临时使用时或程序的 ...

  7. [WPF]使用WindowChrome自定义Window Style

    1. 前言 做了WPF开发多年,一直未曾自己实现一个自定义Window Style,无论是<WPF编程宝典>或是各种博客都建议使用WindowStyle="None" ...

  8. [WPF 自定义控件]自定义控件库系列文章

    Kino.Toolkit.Wpf Kino.Toolkit.Wpf是一组简单实用的WPF控件与工具,用于介绍自定义控件的入门.相关博客地址如下: 开始一个自定义控件库项目 介绍开始一个自定义控件库项目 ...

  9. WPF自定义控件与样式(13)-自定义窗体Window & 自适应内容大小消息框MessageBox

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 自定义 ...

随机推荐

  1. 剑指Offer-46.孩子们的游戏(圆圈中最后剩下的数)(C++/Java)

    题目: 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为牛客的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的:首先,让小朋友们围成一个大圈.然后,他随机指定 ...

  2. ASP.NET CORE 使用Consul实现服务治理与健康检查(2)——源码篇

    题外话 笔者有个习惯,就是在接触新的东西时,一定要先搞清楚新事物的基本概念和背景,对之有个相对全面的了解之后再开始进入实际的编码,这样做最主要的原因是尽量避免由于对新事物的认知误区导致更大的缺陷,Bu ...

  3. JS 操作符、控制流程、循环、字符串/数组方法

    操作符 算术运算符:+ .- . * . / . %.++.-- 赋值运算符:= .+=.-=. *=./=.%= 比较运算符:>.>=.<.<=.!=.==.===(全等,数 ...

  4. adb 获取平台号

    获取 Android 的 PLATFORM_VERSION :adb shell getprop ro.build.version.release获取 Android 的 APP_PACKAGE 和 ...

  5. bayaim——听课笔记_01.Docker基础应用 10课.txt

    ===========2019年8月5日18:39:06====================10.20.100.21rootbayaim ==========01-Docker介绍======== ...

  6. 28.web8

    file_get_contents()文件包含漏洞,根据题目提示txt?尝试flag.txt payload:  ?ac=flags&fn=flag.txt

  7. .NET 收徒,带你走向架构师。

    最近感悟天命,偶有所得,故而打算收徒若干,以继吾之传承. 有缘者,可破瓶颈,走向架构师之峰,指日可待. 入门基本要求: 1.工作经验:1年或以上. 2.入门费用:10000元(RMB). 联系方式(联 ...

  8. TI的32位定点DSP库IQmath在H7和F4上的移植和使用

    说明: 1.最近在制作第2版DSP教程,除了ARM家的,这次重点了解下载TI的DSP库,特此移植了一个TI的IQmath. 2.初次使用这个定点库,感觉在各种Q格式的互转,Q格式数值和浮点数的互转处理 ...

  9. Apollo 分布式配置中心(补充)

    1.   Namespace 1.1.  什么是Namespace Namespace是配置项的集合,类似于一个配置文件的概念. Apollo在创建项目的时候,都会默认创建一个“application ...

  10. Dynamics CRM通过定制应用程序功能区为符合条件的实体表单增加按钮

    关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复167或者20151029可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me! 前面的博文都是为一个实体添加按钮 ...