[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

原文地址:https://www.cnblogs.com/lonelyxmas/p/11015950.html

时间: 2024-08-29 10:11:35

[WPF自定义控件库]使用WindowChrome自定义RibbonWindow的相关文章

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

一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: 自定义Window窗体样式: 基于自定义窗体实现自定义MessageBox消息提示框: 二.自定义Window窗体样式 自定义的Window窗体效果:   因为WPF默认的窗体比较简陋,大都需要自己实现Window窗体样式效果,基本思路很简单: 第一步:干掉默认样式:WindowStyle = Windo

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

一.前言.效果图 还是先看看效果图吧:   定义Button按钮名称叫FButton,主要是集成了字体图标(参考上一篇:WPF自定义控件与样式1-矢量字体图标(iconfont)).其实在WPF里,要实现本文FButton的需求,完全可以不用自定义控件,使用样式.模板就可以搞定了的. 二.按钮FButton控件定义 2.1 FButton继承自微软基础控件Button (C#代码) FButton继承自微软基础控件Button,没有什么逻辑代码,主要扩展了几个属性: 控件外观控制的属性,如圆角.

[WPF自定义控件库]为Form和自定义Window添加FunctionBar

1. 前言 我常常看到同一个应用程序中的表单的按钮----也就是"确定"."取消"那两个按钮----实现得千奇百怪,其实只要使用统一的Style起码就可以统一按钮的大小,而我喜欢更进一步将"确定"."取消"或其它按钮封装进一个自定义控件里. 这篇文章介绍了另一种ItemsControl的实现方式,并使用它为表单及自定义Window添加常用的按钮及其它功能. 2. 为Form添加FunctionBar 本来打算派生自ToolBa

[WPF自定义控件库]自定义Expander

1. 前言 上一篇文章介绍了使用Resizer实现Expander简单的动画效果,运行效果也还好,不过只有展开/折叠而缺少了淡入/淡出的动画(毕竟Resizer模仿Expander只是附带的功能).这篇继续Measure的话题,自定义了一个带有动画的ExtendedExpander. 2. ExtendedExpander的需求 使用Resizer实现的简易Expander没办法在折叠时做淡出动画,因为ControlTemplate中的ExpandSite在Collapsed状态下直接设置为隐藏

[WPF自定义控件库]以Button为例谈谈如何模仿Aero2主题

1. 为什么选择Aero2 除了以外观为卖点的控件库,WPF的控件库都默认使用"素颜"的外观,然后再提供一些主题包.这样做的最大好处是可以和原生控件或其它控件库兼容,而且对于大部分人来说模仿原生的主题也比自己设计一套好看的UI容易得多. WPF有以下几种原生主题: 主题文件 桌面主题 Classic.xaml Windows XP 操作系统上的经典 Windows 外观(Windows 95.Windows 98 和 Windows 2000). Luna.NormalColor.xa

[WPF自定义控件库] 自定义控件的代码如何与ControlTemplate交互

1. 前言 WPF有一个灵活的UI框架,用户可以轻松地使用代码控制控件的外观.例设我需要一个控件在鼠标进入的时候背景变成蓝色,我可以用下面这段代码实现: protected override void OnMouseEnter(MouseEventArgs e) { base.OnMouseEnter(e); Background = new SolidColorBrush(Colors.Blue); } 但一般没人会这么做,因为这样做代码和UI过于耦合,难以扩展.正确的做法应该是使用代码告诉C

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

1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录"对话框加载后"用户名"应该马上获得焦点,用户只需输入用户名,点击Tab,再输入密码,点击回车就完成了登录操作. 在WPF中要让一个控件在加载时获得焦点应该很简单,只需要在Loaded事件后调用Focus()就行了.但有时表单是动态添加的,或者第一个表单元素会根据某些条件显示或隐藏,这时很难简单地让第一个控件获得焦点. 为了实现这个功能我创建了一个叫F

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

1. 什么是滚动轮劫持 这篇文章介绍一个很简单的继承自ScrollViewer的控件: public class ExtendedScrollViewer : ScrollViewer { protected override void OnMouseWheel(MouseWheelEventArgs e) { if (ViewportHeight + VerticalOffset >= ExtentHeight && e.Delta <= 0) return; if (Ver

WPF自定义控件与样式(7)-列表控件DataGrid与ListView自定义样式

一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: DataGrid自定义样式: ListView自定义样式: 二.DataGrid自定义样式 DataGrid是常用的数据列表显示控件,先看看实现的效果(动态图,有点大): DataGrid控件样式结构包括以下几个部分: 列头header样式 调整列头宽度的列分割线样式 行样式 行头调整高度样式 行头部样式