问题背景描述

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 = GetActualPixel(AppTitleBar.ActualWidth);var totalHeight = GetActualPixel(AppTitleBar.ActualHeight);// 自定义控件的左边界相对于整个控件左边界的偏移量。var controlLeftOffset = GetActualPixel(CustomTitleBarControls.ActualOffset.X);// 自定义控件的右边界相对于整个控件左边界的偏移量。var controlRightOffset = GetActualPixel(controlLeftOffset + CustomTitleBarControls.ActualWidth);// 左右两块区域的宽度。var leftSpace = controlLeftOffset;var rightSpace = totalWidth - controlLeftOffset - GetActualPixel(CustomTitleBarControls.ActualWidth) - windowButtonsWidth;// 生成左右两块矩形区域。var leftRect = new RectInt32(0, 0, Convert.ToInt32(leftSpace), Convert.ToInt32(totalHeight));var rightRect = new RectInt32(Convert.ToInt32(controlRightOffset), 0, Convert.ToInt32(rightSpace), Convert.ToInt32(totalHeight));// 定义为拖曳区域。须获取到AppWindow实例,下代名为AppWindow,详见文末备注。AppWindow.TitleBar.SetDragRectangles(new RectInt32[] { leftRect, rightRect });}

获取缩放系数、计算真实像素方法:

     [DllImport("Shcore.dll", SetLastError = true)]public static extern int GetDpiForMonitor(IntPtr hmonitor, Monitor_DPI_Type dpiType, out uint dpiX, out uint dpiY);public enum Monitor_DPI_Type : int{MDT_Effective_DPI = 0,MDT_Angular_DPI = 1,MDT_Raw_DPI = 2,MDT_Default = MDT_Effective_DPI}/// <summary>/// 获取缩放系数。/// </summary>/// <returns>返回缩放系数,表示缩放后像素大小与原始像素大小之比值。</returns>/// <exception cref="Exception">获取缩放系数失败。</exception>private static double GetScaleAdjustment(){DisplayArea displayArea = DisplayArea.GetFromWindowId(MainWindow_ID, DisplayAreaFallback.Primary);IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);// Get DPI.int result = GetDpiForMonitor(hMonitor, Monitor_DPI_Type.MDT_Default, out uint dpiX, out uint _);if (result != 0){throw new Exception("Could not get DPI for monitor.");}uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);return scaleFactorPercent / 100.0;}/// <summary>/// 获取像素数量缩放后对应的像素数量。/// </summary>/// <param name="pixel">缩放前的像素数量。</param>/// <returns>返回缩放后应当由的像素数量。</returns>public static int GetActualPixel(double pixel){return Convert.ToInt32(pixel * PixelZoom);}

测试之后,中间的交互空间已经可以实现鼠标交互了,且不影响两旁的拖曳区域,窗口按钮也能正常工作。问题解决。

总结 & 收获

解决此问题,直接帮助我们在WinUI 3中像UWP一样在标题栏中实现更多交互。

反观问题的形成原因,是WinUI 3和Windows App SDK并不成熟。这也导致此问题的解决方案代码冗长。我们只能期待进一步的开发会支持相关功能的直接实现,或支持在Windows 11以下的版本中实现标题栏自定义。

备注说明:

  • 本文参考博客文章
  • 微软文档页
  • 获取MainWindow、AppWindow、WindowHandle、WindowID实例方法

【WinUI 3】实现自定义标题栏交互控件捕获输入相关推荐

  1. Angular19 自定义表单控件

    1 需求 当开发者需要一个特定的表单控件时就需要自己开发一个和默认提供的表单控件用法相似的控件来作为表单控件:自定义的表单控件必须考虑模型和视图之间的数据怎么进行交互 2 官方文档 -> 点击前 ...

  2. WPF自定义控件与样式(8)-ComboBox与自定义多选控件MultComboBox

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

  3. Angular: [ControlValueAccessor] 自定义表单控件

    Angular: [ControlValueAccessor] 自定义表单控件 我们在实际开发中,通常会遇到各种各样的定制化功能,会遇到有些组件会与 Angular 的表单进行交互,这时候我们一般会从 ...

  4. android日历价格控件,Android 自定义价格日历控件

    介绍 上个星期项目有一个日历价格的需求,类似一个商品在不同的日期价格可能会不同,由于时间给得特别紧所以打算找个合适的开源项目进行修改.参考了网上大多数是通过继承view直接draw一个monthVie ...

  5. C# Winform 通过FlowLayoutPanel及自定义的编辑控件,实现快速构建C/S版的编辑表单页面...

    个人理解,开发应用程序的目的,不论是B/S或是C/S结构类型,无非就是实现可供用户进行查.增.改.删,其中查询用到最多,开发设计的场景也最为复杂,包括但不限于:表格记录查询.报表查询.导出文件查询等等 ...

  6. 自定义用户验证控件CustomValidator

    背景:VisualStudio2005; 使用自定义控件判断注册用户名是否已经存在: 实现: html: <%@ Page Language="C#" AutoEventWi ...

  7. 自定义工作流任务控件

    读moss sdk中的自定义工作流任务控件. 自定义工作流任务控件:任务的创建,修改,删除,完成于一体,同时定义了这四个动作的历史纪录. 自定义时封装属性:  1. 封装任务属性 IsTaskComp ...

  8. android中的标题栏是什么意思,Android通用标题栏组合控件

    原标题:Android通用标题栏组合控件 快,点击蓝色"字体"关注这个公众号,一起涨姿势 由于项目中经常用到此种组合控件,就封装了下,具体效果看下图,老司机可以绕道哈! 一.主要功 ...

  9. kettle中java组件_kettle系列-[KettleUtil]kettle插件,类似kettle的自定义java类控件

    该kettle插件功能类似kettle现有的定义java类插件,自定java类插件主要是支持在kettle中直接编写java代码实现自定特殊功能,而本控件主要是将自定义代码转移到jar包,就是说自定义 ...

最新文章

  1. 春意袭人,春装网店大比拼!
  2. 初学Java的5个阶段,你在哪个阶段?
  3. 一个运维老将的自我修养
  4. 前端设置使用rem最经典代码
  5. .NET 开源项目 StreamJsonRpc 介绍[上篇]
  6. makefile中的shell调用---注意事项
  7. 如何在linux下启动和关闭oracle服务
  8. JAVA中增强循环中用线程_在Java中以循环方式运行线程
  9. IE环境下判断IE版本的语句...[if lte IE 6]……[endif][if lte IE 7]……[endif]
  10. 04:数组逆序重放【一维数组】
  11. centos6.4 安装wireless驱动
  12. acs880变频器静态辨识_(完整版)ABB-ACS880变频器调试参数
  13. C# pdf转jpg文件
  14. php dwg文件,dwf文件怎么转成dwg
  15. 研究生预备军:论文选题与写作
  16. 那些年我们追过的网络小说
  17. 云片网发送短信验证码
  18. 【水晶报表内功心法】--公式、函数与运行时总计 注:(文章来自阿泰博客)
  19. 直播:京东大数据的应用!
  20. 如何将word文档转换成电子书?

热门文章

  1. 软件开发专业需要学习多少年
  2. 移动端网页录音上传,服务端智能语音识别
  3. MATLAB----Roberts锐化滤波器
  4. excel教程中daverage函数应用实例(一):统计部门平均工资
  5. git已经设置了name和email但是还是提示please tell who you are
  6. Validator框架的使用
  7. 小百合海外站 lilybbs.us or woft.net 教育网 paradiso.cn:10078
  8. jzxx1177买蛋糕II
  9. 分析listen状态
  10. 评分卡:WOE、IV、PSI计算及ROC和KS曲线