在传统 Win32 窗口中使用 UI Composition 技术
本文使用 DraftDepot 稿仓 https://github.com/xland/DraftDepot 编辑并发布,DraftDepot 可以把一篇文章一键发布到多个平台。
建立 WinRT 消息分发队列
应用启动时,首先要创建 WinRT 消息分发队列:
DispatcherQueueOptions options{ sizeof(DispatcherQueueOptions), DQTYPE_THREAD_CURRENT, DQTAT_COM_NONE };
static winrt::Windows::System::DispatcherQueueController controller{ nullptr };
auto hr = CreateDispatcherQueueController(options, reinterpret_cast<ABI::Windows::System::IDispatcherQueueController**>(winrt::put_abi(controller)));
if (FAILED(hr))
{
MessageBox(NULL, L"无法创建DispatcherQueueController", L"系统提示", MB_OK);
ExitProcess(-1);
}
这段代码在当前线程创建 DispatcherQueueController(变量名 controller),用于建立 WinRT 消息分发队列并管理其生命周期。
它是使用 Windows UI Composition 等异步 API 的前提,为后续创建合成器(Compositor)及构建视觉树提供必要的消息循环基础。
桥接原生窗口与 WinRT 合成器
这个任务完成后,就可以创建窗口了,如下代码所示
// winrt::Windows::UI::Composition::Compositor compositor{ nullptr };
compositor = Composition::Compositor();
auto hIns = GetModuleHandle(nullptr);
auto cls = getWinClsName(hIns);
hwnd = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP | exStyle, cls.data(), title.data(), style, x, y, (float)w, (float)h, NULL, NULL, hIns, NULL);
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
auto interop = compositor.as<ABI::Windows::UI::Composition::Desktop::ICompositorDesktopInterop>();
// winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget winTarget{ nullptr };
auto r = reinterpret_cast<ABI::Windows::UI::Composition::Desktop::IDesktopWindowTarget**>(winrt::put_abi(winTarget));
interop->CreateDesktopWindowTarget(hwnd, false, r);
// winrt::Windows::UI::Composition::SpriteVisual visual{ nullptr };
visual = compositor.CreateSpriteVisual();
winTarget.Root(visual);
这段代码在 DispatcherQueue 就绪后,负责创建原生窗口并将其与 WinRT 合成器桥接:
- 创建合成器:
首先实例化 Compositor 对象,它是整个 Windows UI Composition 视觉系统的工厂和入口。 - 创建透明窗口:
通过 CreateWindowEx 创建窗口,其中 WS_EX_NOREDIRECTIONBITMAP 标志至关重要,
它禁用了传统的 GDI/DWM 重定向表面,使窗口成为仅由 DirectComposition 驱动的纯合成器窗口。
这种窗口天生支持背景半透明效果。 - 关联上下文:
使用 SetWindowLongPtr 将当前对象指针存入窗口的用户数据区,以便在窗口过程(WndProc)中访问类实例。
这部分代码不是本文的重点,我没有给出。 - 桥接 HWND 与合成器:
利用 ICompositorDesktopInterop 接口调用 CreateDesktopWindowTarget,
将原生窗口句柄注册为合成器的桌面渲染目标(winTarget),
这一步打通了 Win32 窗口与 WinRT Composition 视觉树之间的通道,使后续创建的 Visual 能够正确呈现到该窗口上。 - 建立视觉树根节点:
创建一个 SpriteVisual 作为根视觉对象,并将其设置为 winTarget 的 Root。
此后所有子 Visual 都挂载在该根节点下,最终通过合成器呈现到窗口上。
绘制元素
如果你想调整窗口的背景色,可以使用如下方法修改 visual 的颜色
winrt::Windows::UI::Color color = winrt::Windows::UI::ColorHelper::FromArgb(a, r, g, b);
visual.Brush(win->compositor.CreateColorBrush(color));
如你所见,颜色支持透明通道,可以设置透明颜色。
如果你想增加子元素,可以用如下代码:
auto childVisual = compositor.CreateSpriteVisual();
visual.Children().InsertAtTop(childVisual);
//移除子元素的代码
//visual.Children().Remove(borderVisual);
如果你想设置元素的位置和尺寸,可以用如下方式:
visual.Offset({ x, y, 0.0f });
visual.Size({ w, h });
可以通过如下方式控制元素的显隐
visual.IsVisible(false);
visual.IsVisible(true);
所有绘图操作都是直接完成的,你不需要通过类似这样的方法刷新窗口
InvalidateRect(hwnd, nullptr, FALSE);
通过这种方式编译的程序可以单exe执行(不需要分发一大堆dll),单exe的体积小于1M
如果大家感兴趣的话,可以点赞、关注。
点赞的人多的话,后面我再介绍如何做布局、圆角、滚动条、渲染文本、画布、字体图标等一系列的知识。
甚至可以介绍如何让这个技术与FaceBook的Yoga布局引擎整合,开发GUI框架(我做过验证了)






浙公网安备 33010602011771号