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

  1. [WPF自定义控件库]使用WindowChrome的问题

    1. 前言 上一篇文章介绍了使用WindowChrome自定义Window,实际使用下来总有各种各样的问题,这些问题大部分都不影响使用,可能正是因为不影响使用所以一直没得到修复(也有可能别人根本不觉得 ...

  2. [WPF自定义控件库]了解如何自定义ItemsControl

    1. 前言 对WPF来说ContentControl和ItemsControl是最重要的两个控件. 顾名思义,ItemsControl表示可用于呈现一组Item的控件.大部分时候我们并不需要自定义It ...

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

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

  4. WPF自定义控件与样式(2)-自定义按钮FButton

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

  5. WPF 如何创建自己的WPF自定义控件库

    在我们平时的项目中,我们经常需要一套自己的自定义控件库,这个特别是在Prism这种框架下面进行开发的时候,每个人都使用一套统一的控件,这样才不会每个人由于界面不统一而造成的整个软件系统千差万别,所以我 ...

  6. [WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack)

    原文:[WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack) 1. 什么是滚动轮劫持# 这篇文章介绍一个很简单的继承自ScrollViewer的控件 ...

  7. [WPF自定义控件库] 让Form在加载后自动获得焦点

    原文:[WPF自定义控件库] 让Form在加载后自动获得焦点 1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录" ...

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

    1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自定义RibbonWindow则不一样: 如果程序使用了自定义样式的Window,为了统一 ...

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

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等. 本文主要内容: 自定义Window窗体样式: 基于自定义窗体实现自定义MessageB ...

随机推荐

  1. 【网络】IP地址,子网掩码,网段表示法,默认网关,DNS服务器详解

    楔子: 以Windows系统中IP地址设置界面为参考(如图1), IP地址, 子网掩码, 默认网关 和 DNS服务器, 这些都是什么意思呢? 学习IP地址的相关知识时还会遇到网络地址,广播地址,子网等 ...

  2. Delphi中任务栏状态区的编程

    在Windows桌面的任务栏上有一个凹陷的区域,其中显示着系统时钟以及一些图标,这个长方形的区域便是Windows的任务栏状态区(taskbar status area).本文将介绍使用Borland ...

  3. NX二次开发-对话框加锁UF_UI_lock_ug_access

    VC/MFC调用UG Dialog要进入加锁状态 加锁 UF_UI_lock_ug_access ( UF_UI_FROM_CUSTOM ); 此处为UF_UI_select的函数 解锁 UF_UI_ ...

  4. (转)简述负载均衡&CDN技术

    转:http://www.cnblogs.com/mokafamily/p/4402366.html#commentform 曾经见到知乎上有人问“为什么像facebook这类的网站需要上千个工程师维 ...

  5. JVM内核-原理、诊断与优化学习笔记(三):常用JVM配置参数

    文章目录 Trace跟踪参数 -verbose:gc (打开gc的跟踪情况) -XX:+printGC(打开gc的log开关,如果在运行的过程中出现了gc,就会打印出相关的信息.) -XX:+Prin ...

  6. 2014 0416 word清楚项目黑点 输入矩阵 普通继承和虚继承 函数指针实现多态 强弱类型语言

    1.word 如何清除项目黑点 选中文字区域,选择开始->样式->全部清除 2.公式编辑器输入矩阵 先输入方括号,接着选择格式->中间对齐,然后点下面红色框里的东西,组后输入数据   ...

  7. postgresql+java数据类型对照

    网上搜了很多都不理想,这里总结的一部分是官网的文档,一部分是网上的,大体没问题 PostgreSQL™                 Java SE 8 date            LocalD ...

  8. js对象属性值初始化封装函数

    在平常做项目的过程中,总是会遇到需要对一个已经定义过的对象的属性值进行初始化,且对象的属性值的类型有多种(string.number.array.object.boolean),为了方便自己就简单封装 ...

  9. 《转》python(7)列表

    转自 http://www.cnblogs.com/BeginMan/p/3153842.html 一.序列类型操作符 1.切片[]和[:] 2.成员关系操作符(in ,not in ) 1: s1 ...

  10. springboot整合thymeleaf手动渲染

    Thymeleaf手动渲染 为提高页面访问速度,可缓存html页面,客户端请求从缓存获取,获取不到再手动渲染 在spring4下 @Autowired ThymeleafViewResolver th ...