问题背景描述
Windows为每个窗口提供默认标题栏,并允许自定义它以匹配应用的个性。 默认标题栏附带一些标准组件和核心功能,例如拖动和调整窗口大小。
WinUI 3 中的窗口功能是通过基于 Win32 HWND 模型的 Microsoft.UI.Xaml.Window 类。 Window 类包含 API,可用于将标准标题栏替换为自己的自定义内容。
WinUI 3 也是Windows 应用 SDK的一部分,因此 Windows 类和 AppWindow 类都可用于自定义标题栏。 可以将 XAML 窗口的窗口句柄传递给 AppWindow 对象,并将 AppWindow 功能与窗口 API 结合使用, (查看Windows 应用 SDK选项卡) 。 但是,仅Windows 11支持 AppWindow 的标题栏自定义。
(摘自:微软文档)
众所周知,Windows App SDK支持同UWP类似的标题栏自定义功能,以支持在标题栏加入更多设计与功能。从UWP类比,我们甚至可以在标题栏加入交互式内容,使标题栏既美观又实用。
但,根据微软文档,WinUI 3并不建议添加交互元素,因为标题栏将默认接管鼠标输入:
传递给 SetTitleBar 的元素支持与标准标题栏相同的系统交互,包括拖动、双击以调整大小,并右键单击以显示窗口上下文菜单。 因此,所有指针输入 (鼠标、触摸、笔等) 由系统处理。 标题栏元素及其子元素不再识别它。 标题栏元素占用的矩形区域充当用于指针目的的标题栏,即使元素被另一个元素阻止,或者该元素是透明的。 但是,可以识别键盘输入,子元素可以接收键盘焦点。
这意味着,除了通过键盘输入和焦点,你不能与标题栏区域中的元素进行交互。 我们不建议这样做,因为它提供了可发现性和辅助功能问题。
致使加入交互控件后,当用户尝试与其交互,只会触发标准的标题栏事件——右键显示窗口命令,按住拖曳窗口。
问题分析
分析问题,发现其根本原因在于:整个标题栏包括自定义的内容控件,都会被定义为拖曳区域,从而被接管鼠标输入,致使无法交互。
(拖曳区域:在窗口标题栏中被定义的区域,根据微软文档,该区域的鼠标输入会被接管,实现与标准标题栏相同的系统交互。)
不过,根据文档在“Windows应用SDK”栏中给出的代码,我们得到了另一种思路——AppWindow.TitleBar中给出了方法:public void SetDragRectangles(RectInt32[] value);
。根据原型,我们可以使用该方法自定义标题栏的若干个矩形拖曳区域,从而使交互控件被排除在拖曳区域外。
则如图,我们可以把标题栏分为三部分看:
①包含Icon、Title与空白区域,无需鼠标输入;
②包含定义交互控件,需要鼠标输入;
③包含窗口按钮与空白区域,无需鼠标输入(窗口按钮区域须排除)。
目标很明确,我们要计算①、③区域对应的矩形位置,并将其定义为拖曳区域,使②成功接收鼠标输入。
算法非常简单:①的宽度即②的横坐标、高度即标题栏高度、坐标为(0, 0);③的横坐标即(②的横坐标 + ②的宽度)、③的高度即标题栏高度、③的宽度即(标题栏宽度 - ②的横坐标 - ②的宽度 - 按钮区域宽度)。此时,我们已经基本解决问题了,只需加一行代码将两个矩形区域定义为拖曳区域,然后注意窗口初始化时、窗口尺寸改变时都需要重新计算并定义拖曳区域。
但,须注意到一个问题——Win32API无视了HiDpi下的屏幕缩放。即当200%缩放时,计算出来的区域尺寸仅是实际区域尺寸的四分之一。所以,我们仍需要编写方法适应HiDpi情境,计算屏幕缩放,并在区域高宽的计算中加入缩放系数。
解决方案
刷新标题栏拖曳区域方法,须注册到窗口尺寸改变时、窗口载入完成时:
private void UpdateDragRects()
{
// 标题栏尺寸。
var totalWidth =