1. UWP简介
UWP即Windows 10中的Universal Windows Platform简称。即Windows通用应用平台,在Windows 10 Mobile/Surface(Windows平板电脑)/PC/Xbox/HoloLens等平台上运行,uwp不同于传统pc上的exe应用,也跟只适用于手机端的app有本质区别。它并不是为某一个终端而设计,而是可以在所有Windows10设备上运行。
UWP应用可以在所有Windows10设备上运行的本质在于打包时对多个平台进行适配。原生UWP在Windows10电脑客户端中是看不到.exe的痕迹的。这不代表它不是以常规窗口(即WIN32窗口)创建、运作、销毁的机理运行。UWP依旧具有很多特殊的特质,比如使用DirectX进行GPU加速等。
在UWP中,可以通过 CoreApplicationView 的实例,然后设置 TitleBar 的各种属性来扩展或自定义标题栏。关键代码如下:
// lang = visual csharp
//扩展标题栏
var applicationView = CoreApplication.GetCurrentView();
applicationView.TitleBar.ExtendViewIntoTitleBar = true;
//自定义标题栏
var titleBar = ApplicationView.GetForCurrentView().TitleBar;
titleBar.BackgroundColor = Colors.Khaki;
titleBar.ButtonBackgroundColor = Colors.Transparent;
在Win32中,扩展或自定义标题栏不是一个简单的事情,它将涉及一系列关于Non-client区和client区的设置问题,有些时候需要引入Dwmapi来辅助完成。处理不恰当时,可能出现一些各种奇怪的问题,例如不响应WM_NCPAINT时的边框圆角问题;去除WS_CAPTION后,无法响应win键+上下左右的快捷键等。
UWP却可以在能自定义标题栏得情况下,同时拥有系统配置给普通窗口的处理。下面就对uwp的结构,以win32的方式进行初步解读。
2. 以Win32方式解读UWP应用窗口
通过使用spy++,对windows内置的“设置”进行查看。
可以发现,UWP应用主要包含五个窗口句柄,分别是名为ApplicationFrameWindow的主窗口,两个名为ApplicationFrameTitleBarWindow的childwindow,一个Windows.UI.Core.CoreWindow子窗口和ApplicationFrameInputSinkWindow子窗口。
具体信息如下所示:
注册时的窗口类名 | 风格 | 扩展风格 | 窗口矩形 | 客户区矩形 |
---|---|---|---|---|
ApplicationFrameWindow | appFrameWndStyle | appFrameWndStyleEx | [[51,11],[1267,952]] | [[8,0],[1208,933]] |
ApplicationFrameTitleBarWindow | appFrameTitleBarWndStyle | childWndStyleEx | [[59,12],[107,44]] | [[0,0],[48,32]] |
ApplicationFrameTitleBarWindow | appFrameTitleBarWndStyle | childWndStyleEx | [[1071,12],[1259,44]] | [[0,0],[188,32]] |
Windows.UI.Core.CoreWindow | coreWndStyle | childWndStyleEx | [[59,12],[1259,944]] | [[0,0],[1200,932]] |
ApplicationFrameInputSinkWindow | appFrameInputSinkWndStyle | childWndStyleEx | [[59,44],[1259,944]] | [[0,0],[1200,900]] |
表中:
// lang = visual cpp
// wndStyle
auto appFrameWndStyle = WS_CAPTION | WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
auto appFrameTitleBarWndStyle = WS_CHILDWINDOW | WS_VISIBLE | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
auto coreWndStyle = WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPSIBLINGS;
auto appFrameInputSinkWndStyle = WS_CHILDWINDOW | WS_VISIBLE;
// wndStyleEx
auto appFrameWndStyleEx = WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR |
WS_EX_WINDOWEDGE | WS_EX_NOREDIRECTIONBITMAP;
auto childWndStyleEx = WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR |
WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP;
下面给出窗口矩形示意:
- 图中可以看出,主窗口的win32标题栏大小为0,但依旧保留左右和下方的8px非客户区,猜测可能重写了WM_NCCALCSIZE 消息回调,将原有标题栏设为0;由于上方非客户区消失,因此还需重写部分的WM_NCHITTEST 消息回调(可能不是在主窗口中实现,而在CoreWindow中实现)。由于其保留近乎完整的非客户区,所以可以响应窗口吸附和win键操作等。
- 两个子窗口ApplicationFrameTitleBarWindow分别分布于窗口两侧。表内第一个TitleBarWindow是返回键区,(不包含文字“设置”的区域),这个子窗口有时窗口矩形为0(即回到主页面时,按钮消失);第二个则是windows窗口功能按钮区,呈现的按钮区大于三个按钮宽度之和,应该是为了给其他按钮(比如问号/帮助按钮)留的位置。
- 子窗口Windows.UI.Core.CoreWindow是负责整个窗口人机交互逻辑,也就是C#中的CoreWindow实例;由于对UWP了解不深,猜测亚克力效果可能就是由CoreWindow向系统(或显卡)获取桌面显存,然后使用D2D 在GPU中再次加工合成的。对比InputSinkWindow,CoreWindow比其要高32px,这也就是模拟标题栏的高度。由于UWP标题栏是自己绘制的,所以很容易实现神奇的特性,比如 Microsoft To Do 里同步to do list的进度条动画就是在“标题栏”上方。
- InputSinkWindow 暂时不太了解其存在的缘由,名字翻译为 应用程序框架输入接收器窗口 。它存在的意义可能和UWP窗口调度CoreDispatcher有关。