[WPF自定义控件库]使用WindowChrome自定义RibbonWindow
原文:[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的问题
1. 前言 上一篇文章介绍了使用WindowChrome自定义Window,实际使用下来总有各种各样的问题,这些问题大部分都不影响使用,可能正是因为不影响使用所以一直没得到修复(也有可能别人根本不觉得 ...
- [WPF自定义控件库]了解如何自定义ItemsControl
1. 前言 对WPF来说ContentControl和ItemsControl是最重要的两个控件. 顾名思义,ItemsControl表示可用于呈现一组Item的控件.大部分时候我们并不需要自定义It ...
- WPF自定义控件与样式(13)-自定义窗体Window & 自适应内容大小消息框MessageBox
一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 自定义 ...
- WPF自定义控件与样式(2)-自定义按钮FButton
一.前言.效果图 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 还是先看看效果 ...
- WPF 如何创建自己的WPF自定义控件库
在我们平时的项目中,我们经常需要一套自己的自定义控件库,这个特别是在Prism这种框架下面进行开发的时候,每个人都使用一套统一的控件,这样才不会每个人由于界面不统一而造成的整个软件系统千差万别,所以我 ...
- [WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack)
原文:[WPF自定义控件库] 关于ScrollViewer和滚动轮劫持(scroll-wheel-hijack) 1. 什么是滚动轮劫持# 这篇文章介绍一个很简单的继承自ScrollViewer的控件 ...
- [WPF自定义控件库] 让Form在加载后自动获得焦点
原文:[WPF自定义控件库] 让Form在加载后自动获得焦点 1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录" ...
- [WPF 自定义控件]使用WindowChrome自定义RibbonWindow
1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自定义RibbonWindow则不一样: 如果程序使用了自定义样式的Window,为了统一 ...
- 【转】WPF自定义控件与样式(13)-自定义窗体Window & 自适应内容大小消息框MessageBox
一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等. 本文主要内容: 自定义Window窗体样式: 基于自定义窗体实现自定义MessageB ...
随机推荐
- curl 命令帮助及使用
目录 一.简介 二.curl 帮助文档 三.curl 的使用 前言 刚接触 curl 就发现它的非常强大.奈何帮助文档全是英文,看起来贼费劲.无奈只能硬着头皮用自己蹩脚的英语和翻译软件硬生生的翻译了一 ...
- 《DNS攻击防范科普系列2》 -DNS服务器怎么防DDoS攻击
在上个系列<你的DNS服务真的安全么?>里我们介绍了DNS服务器常见的攻击场景,看完后,你是否对ddos攻击忧心重重?本节我们来告诉你,怎么破局!! 首先回顾一下DDoS攻击的原理.DDo ...
- bzoj1026题解
[解题思路] 数位DP.f[i][j]表示以j结尾的i位数中windy数的个数,转移方程f[i][j]=Σf[i-1][k](|j-k|>1). 基于f数组,我们可以统计出1~n内的windy数 ...
- NX二次开发-time.h获取计算机本地时间
NX9+VS2012 #include <stdio.h> #include <time.h> char *wday[] = { "Sun", " ...
- 简单理解vue的slot插槽
slot的意思是插槽,想想你的电脑主板上的各种插槽,有插CPU的,有插显卡的,有插内存的,有插硬盘的,所以假设有个组件是computer,其模板是 <template> <div&g ...
- 洛谷P3916 图的遍历
题目链接:https://www.luogu.org/problemnew/show/P3916 题目大意 略. 分析 以终为始,逆向思维. 代码如下 #include <bits/stdc++ ...
- 过滤'and','or' ''' '*' '=' ‘select’下的注入
遇到一个站:http://www.马赛克.net/intro.aspx?id=6 加了单引号 http://www.马赛克.net/intro.aspx?id=6‘ 页面无变化 加了双引号 h ...
- hashCode和identityHashCode的区别你知道吗?
hashCode 关于hashCode参考之前的文章,点击参考之前文章. identityHashCode identityHashCode是System里面提供的本地方法,java.lang.Sys ...
- spring boot 结合jsp简单示例
引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>sp ...
- 将sparkStreaming的结果保存到S3
将spark解析的结果保存到S3 这个和保存到本地的区别在于,你需要配置aws的key和密码,以及它的region,代码如下 package com.alo7.spark import java.ut ...