WinUI3 无边框透明窗口实现指南

2026-04-11 14:321阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:

本文档用于说明在 WinUI 3 / Windows App SDK 场景下,如何把一个普通顶层窗口逐步改造成“无边框、透明背景、可选置顶、可选点击穿透”的窗口。

提醒:使用到的部分 API 仅 Windows 11 Build 22000 (21H2)及以上版本系统支持

1. 介绍

在制作一个项目的屏幕批注功能过程中,被窗口样式和层级反复折磨,查了许多内容才最终形成以下方案(当然代码是 GPT 写的,这份文档也有一部分使用 AI 完成)

相关实现代码供参考: WindBoard/WindBoard/Features/ScreenAnnotation at develop · Jerry-Z07/WindBoard

典型目标:

  • 不显示系统标题栏与标准边框
  • 窗口背景真正透明,而不是黑底
  • 在 Win11 21H2 以上系统不残留系统圆角外轮廓或边框描边
  • 可选保持置顶
  • 可选切换点击穿透

WinUI 顶层窗口的“透明”“边框”“系统描边”分别来自 XAML 内容树、Win32 样式、DWM 非客户区绘制 三个层面,必须一起处理。

2. 效果示意图

批注层+悬浮工具条1270×783 20.2 KB

3. 总体实现链路

推荐按下面顺序处理:

  1. 创建普通 WinUI 顶层窗口
  2. 通过 AppWindow / OverlappedPresenter 关闭标题栏与标准边框
  3. 通过 Win32 样式清理标准窗口边框相关位
  4. 把窗口切到 WS_EX_LAYERED
  5. 为窗口添加透明 SystemBackdrop
  6. 拦截背景擦除,避免退化成黑底
  7. 通过 DWM 属性关闭系统描边与圆角外轮廓
  8. 如有需要,再补置顶、相对层级、点击穿透

4. 第一步:关闭 WinUI 标题栏与基础边框

先通过 AppWindowOverlappedPresenter 关闭标准标题栏:

AppWindow appWindow = GetAppWindow(window); if (appWindow.Presenter is OverlappedPresenter presenter) { presenter.IsResizable = false; presenter.IsMaximizable = false; presenter.IsMinimizable = false; presenter.SetBorderAndTitleBar(false, false); } appWindow.IsShownInSwitchers = false; appWindow.MoveAndResize(bounds);

5. 第二步:清理 Win32 标准窗口样式

需要进一步移除会导致系统边框残留的标准样式位:

  • WS_CAPTION
  • WS_THICKFRAME
  • WS_MINIMIZEBOX
  • WS_MAXIMIZEBOX
  • WS_SYSMENU

推荐做法:

const int GWL_STYLE = -16; const uint BorderlessMask = 0x00C00000 | // WS_CAPTION 0x00040000 | // WS_THICKFRAME 0x00020000 | // WS_MINIMIZEBOX 0x00010000 | // WS_MAXIMIZEBOX 0x00080000; // WS_SYSMENU uint newStyle = currentStyle & ~BorderlessMask;

更新样式后,必须再触发一次窗口样式刷新:

SetWindowPos( hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);

说明:

  • 这是去除“普通系统边框”的核心步骤。
  • 如果缺少 SWP_FRAMECHANGED,样式修改有时不会立刻反映到窗口外观。

6. 第三步:启用 Layered Window

透明顶层窗口通常需要启用扩展样式:

  • WS_EX_LAYERED
  • WS_EX_TOOLWINDOW

常见目的:

  • WS_EX_LAYERED:允许透明混合与透明属性生效
  • WS_EX_TOOLWINDOW:避免作为普通主窗口参与 Alt+Tab 等切换器展示

示例:

const int GWL_EXSTYLE = -20; const uint WS_EX_TOOLWINDOW = 0x00000080; const uint WS_EX_LAYERED = 0x00080000; uint newExStyle = currentExStyle | WS_EX_TOOLWINDOW | WS_EX_LAYERED;

设置完扩展样式后,建议再调用一次:

SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);

说明:

  • 即使窗口目标是“看起来完全透明”,也建议显式完成这一层设置。
  • 这一步主要解决透明渲染路径与后续 DWM 行为的兼容性问题。

7. 第四步:不要只依赖 XAML 透明,必须补透明 SystemBackdrop

此处思路来自DevWinUI,感谢原仓库的方案提供

很多人会先写:

<Grid Background="Transparent" />

这一步应该保留,但不能只做这一层

在 WinUI 3 顶层窗口中,仅有 XAML 透明时,窗口仍可能退化成黑底。更稳妥的做法是挂一个真正输出透明色的 SystemBackdrop

这个透明 backdrop 的职责通常包括:

  • 输出 ARGB(0, 0, 0, 0) 的透明画刷
  • 配置 DWM 透明相关行为
  • 在收到 WM_ERASEBKGND 时主动清空背景

核心思路如下:

var backdrop = new TransparentBackdrop(hwnd); window.SystemBackdrop = backdrop;

说明:

  • 这里的 TransparentBackdrop 是一个自定义的透明系统背景实现。

8. 第五步:处理背景擦除,避免黑底

为避免 WinUI 顶层窗体在透明场景下出现黑底,通常需要处理:

  • WM_ERASEBKGND
  • WM_DWMCOMPOSITIONCHANGED

推荐做法:

  • 为窗口挂一个轻量级消息监听
  • 收到 WM_ERASEBKGND 时,用透明画刷填充客户区
  • 收到 WM_DWMCOMPOSITIONCHANGED 时重新应用 DWM 透明配置

典型逻辑:

if (messageId == WM_ERASEBKGND) { ClearBackgroundWithTransparentBrush(hwnd, hdc); handled = true; }

说明:

  • 这是“真正透明”实现里非常容易漏掉的一环。
  • 很多黑底问题并不是 XAML 没透明,而是系统背景擦除仍在按默认路径执行。

9. 第六步:通过 DWM 去掉 Win11 外轮廓与系统描边

此处使用到的 API 仅 Windows 11 Build 22000 (21H2)及以上版本系统支持
在 Win11 上,即使已经:

  • 去掉 Win32 标准边框
  • 使用透明 backdrop
  • 启用 layered window

仍可能残留一圈外轮廓。这个外轮廓往往来自 DWM 的窗口圆角与系统描边。

推荐继续设置:

  • DWMWA_WINDOW_CORNER_PREFERENCE = DWMWCP_DONOTROUND
  • DWMWA_BORDER_COLOR = DWMWA_COLOR_NONE

示例:

uint cornerPreference = 1; // DWMWCP_DONOTROUND uint borderColor = 0xFFFFFFFE; // DWMWA_COLOR_NONE DwmSetWindowAttribute(hwnd, 33, ref cornerPreference, sizeof(uint)); DwmSetWindowAttribute(hwnd, 34, ref borderColor, sizeof(uint));

说明:

  • 这一步主要用于去掉 Win11 的圆角轮廓和系统描边。
  • 某些旧系统不支持这些属性,建议做“尽力而为”处理:
    • 支持则应用
    • 不支持则忽略
    • 不要因为该步骤失败而让窗口初始化整体失败

10. 第七步:可选的置顶与点击穿透

10.1 置顶

如果窗口需要始终浮在桌面或其它应用之上,可通过:

SetWindowPos( hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);

说明:

  • 如果存在多个顶层窗口(例如批注层 + 工具栏),还需要显式维护二者相对层级。

10.2 点击穿透

若窗口在某些模式下需要把输入传给下层应用,可切换:

  • WS_EX_TRANSPARENT

示例:

uint exStyle = enabled ? currentExStyle | WS_EX_TRANSPARENT : currentExStyle & ~WS_EX_TRANSPARENT;

说明:

  • 点击穿透不等于视觉透明。
  • 工具栏类窗口一般不应该永久穿透,否则用户会失去“切回可交互模式”的入口。

11. 推荐的初始化顺序

推荐顺序如下:

  1. 获取 HWND
  2. 获取 AppWindow
  3. 通过 OverlappedPresenter 关标题栏和基础边框
  4. 调整窗口大小和位置
  5. 挂透明 SystemBackdrop
  6. 清理 Win32 标准样式
  7. 设置扩展样式为 WS_EX_TOOLWINDOW | WS_EX_LAYERED
  8. 调用 SetLayeredWindowAttributes
  9. 调用 DWM 去圆角与去描边
  10. 如有需要,再设置置顶与点击穿透

这个顺序的核心原因是:

  • 先让 WinUI 窗口对象稳定创建
  • 再处理透明背景
  • 最后做原生样式与 DWM 外观收敛

12. 最小伪代码示例

下面给出一个通用示意:

void InitializeTransparentOverlayWindow(Window window, RectInt32 bounds) { IntPtr hwnd = GetWindowHandle(window); AppWindow appWindow = GetAppWindow(hwnd); ConfigurePresenter(appWindow, bounds); window.SystemBackdrop = new TransparentBackdrop(hwnd); RemoveStandardWindowFrame(hwnd); ApplyToolLayeredExStyle(hwnd); SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA); TryDisableDwmRoundedCorners(hwnd); TryDisableDwmBorder(hwnd); SetTopMost(hwnd); }

如果还需要模式切换:

void SetPassThrough(IntPtr hwnd, bool enabled) { UpdateTransparentExStyle(hwnd, enabled); }

13. 常见问题排查

13.1 只有 XAML 透明,但窗口还是黑底

优先检查:

  • 是否挂了透明 SystemBackdrop
  • 是否处理了 WM_ERASEBKGND
  • 是否启用了 WS_EX_LAYERED

13.2 标题栏没了,但窗口仍然有边框

优先检查:

  • 是否真正清除了 WS_CAPTION / WS_THICKFRAME
  • 样式修改后是否调用了 SWP_FRAMECHANGED

13.3 Win11 下仍然有一圈外轮廓

优先检查:

  • 是否设置 DWMWA_WINDOW_CORNER_PREFERENCE = DWMWCP_DONOTROUND
  • 是否设置 DWMWA_BORDER_COLOR = DWMWA_COLOR_NONE

13.4 窗口透明了,但拖动或切换时闪烁

优先检查:

  • WM_ERASEBKGND 是否被透明清空
  • WM_DWMCOMPOSITIONCHANGED 后是否重新应用 DWM 配置

14. 验证清单

建议至少人工验证以下项目:

  1. 窗口启动后无标题栏、无标准系统边框
  2. 背景是真正透明,而不是黑底
  3. Win11 下没有残留外轮廓或圆角描边
  4. 置顶行为符合预期
  5. 点击穿透开启后,下层应用可接收输入
  6. 点击穿透关闭后,窗口恢复可交互
  7. 多窗口场景下,相对层级稳定
  8. 窗口关闭时无残留资源、无异常日志

15. 兼容性注意事项

  • DWMWA_BORDER_COLORDWMWA_WINDOW_CORNER_PREFERENCE 仅在 Windows 11 Build 22000 以上系统支持。
  • 对旧系统建议采用“忽略不支持属性”的降级策略,而不是把整个透明窗口初始化判定为失败。

16. 参考

  • DWMWINDOWATTRIBUTE (dwmapi.h) - Win32 apps | Microsoft Learn
  • DevWinUI/dev/DevWinUI/Common/Backdrop at main · ghost1372/DevWinUI

现在这套方案有些复杂,如果佬友有更好的方案欢迎交流!

网友解答:
--【壹】--:

感谢分享


--【贰】--:

补充:引入 API

  • DWMWA_WINDOW_CORNER_PREFERENCE = DWMWCP_DONOTROUND
  • DWMWA_BORDER_COLOR = DWMWA_COLOR_NONE

是为去除Win11特有的窗口样式,Win10下无需进行此步处理


--【叁】--:

感谢大佬!

标签:软件开发
问题描述:

本文档用于说明在 WinUI 3 / Windows App SDK 场景下,如何把一个普通顶层窗口逐步改造成“无边框、透明背景、可选置顶、可选点击穿透”的窗口。

提醒:使用到的部分 API 仅 Windows 11 Build 22000 (21H2)及以上版本系统支持

1. 介绍

在制作一个项目的屏幕批注功能过程中,被窗口样式和层级反复折磨,查了许多内容才最终形成以下方案(当然代码是 GPT 写的,这份文档也有一部分使用 AI 完成)

相关实现代码供参考: WindBoard/WindBoard/Features/ScreenAnnotation at develop · Jerry-Z07/WindBoard

典型目标:

  • 不显示系统标题栏与标准边框
  • 窗口背景真正透明,而不是黑底
  • 在 Win11 21H2 以上系统不残留系统圆角外轮廓或边框描边
  • 可选保持置顶
  • 可选切换点击穿透

WinUI 顶层窗口的“透明”“边框”“系统描边”分别来自 XAML 内容树、Win32 样式、DWM 非客户区绘制 三个层面,必须一起处理。

2. 效果示意图

批注层+悬浮工具条1270×783 20.2 KB

3. 总体实现链路

推荐按下面顺序处理:

  1. 创建普通 WinUI 顶层窗口
  2. 通过 AppWindow / OverlappedPresenter 关闭标题栏与标准边框
  3. 通过 Win32 样式清理标准窗口边框相关位
  4. 把窗口切到 WS_EX_LAYERED
  5. 为窗口添加透明 SystemBackdrop
  6. 拦截背景擦除,避免退化成黑底
  7. 通过 DWM 属性关闭系统描边与圆角外轮廓
  8. 如有需要,再补置顶、相对层级、点击穿透

4. 第一步:关闭 WinUI 标题栏与基础边框

先通过 AppWindowOverlappedPresenter 关闭标准标题栏:

AppWindow appWindow = GetAppWindow(window); if (appWindow.Presenter is OverlappedPresenter presenter) { presenter.IsResizable = false; presenter.IsMaximizable = false; presenter.IsMinimizable = false; presenter.SetBorderAndTitleBar(false, false); } appWindow.IsShownInSwitchers = false; appWindow.MoveAndResize(bounds);

5. 第二步:清理 Win32 标准窗口样式

需要进一步移除会导致系统边框残留的标准样式位:

  • WS_CAPTION
  • WS_THICKFRAME
  • WS_MINIMIZEBOX
  • WS_MAXIMIZEBOX
  • WS_SYSMENU

推荐做法:

const int GWL_STYLE = -16; const uint BorderlessMask = 0x00C00000 | // WS_CAPTION 0x00040000 | // WS_THICKFRAME 0x00020000 | // WS_MINIMIZEBOX 0x00010000 | // WS_MAXIMIZEBOX 0x00080000; // WS_SYSMENU uint newStyle = currentStyle & ~BorderlessMask;

更新样式后,必须再触发一次窗口样式刷新:

SetWindowPos( hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);

说明:

  • 这是去除“普通系统边框”的核心步骤。
  • 如果缺少 SWP_FRAMECHANGED,样式修改有时不会立刻反映到窗口外观。

6. 第三步:启用 Layered Window

透明顶层窗口通常需要启用扩展样式:

  • WS_EX_LAYERED
  • WS_EX_TOOLWINDOW

常见目的:

  • WS_EX_LAYERED:允许透明混合与透明属性生效
  • WS_EX_TOOLWINDOW:避免作为普通主窗口参与 Alt+Tab 等切换器展示

示例:

const int GWL_EXSTYLE = -20; const uint WS_EX_TOOLWINDOW = 0x00000080; const uint WS_EX_LAYERED = 0x00080000; uint newExStyle = currentExStyle | WS_EX_TOOLWINDOW | WS_EX_LAYERED;

设置完扩展样式后,建议再调用一次:

SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);

说明:

  • 即使窗口目标是“看起来完全透明”,也建议显式完成这一层设置。
  • 这一步主要解决透明渲染路径与后续 DWM 行为的兼容性问题。

7. 第四步:不要只依赖 XAML 透明,必须补透明 SystemBackdrop

此处思路来自DevWinUI,感谢原仓库的方案提供

很多人会先写:

<Grid Background="Transparent" />

这一步应该保留,但不能只做这一层

在 WinUI 3 顶层窗口中,仅有 XAML 透明时,窗口仍可能退化成黑底。更稳妥的做法是挂一个真正输出透明色的 SystemBackdrop

这个透明 backdrop 的职责通常包括:

  • 输出 ARGB(0, 0, 0, 0) 的透明画刷
  • 配置 DWM 透明相关行为
  • 在收到 WM_ERASEBKGND 时主动清空背景

核心思路如下:

var backdrop = new TransparentBackdrop(hwnd); window.SystemBackdrop = backdrop;

说明:

  • 这里的 TransparentBackdrop 是一个自定义的透明系统背景实现。

8. 第五步:处理背景擦除,避免黑底

为避免 WinUI 顶层窗体在透明场景下出现黑底,通常需要处理:

  • WM_ERASEBKGND
  • WM_DWMCOMPOSITIONCHANGED

推荐做法:

  • 为窗口挂一个轻量级消息监听
  • 收到 WM_ERASEBKGND 时,用透明画刷填充客户区
  • 收到 WM_DWMCOMPOSITIONCHANGED 时重新应用 DWM 透明配置

典型逻辑:

if (messageId == WM_ERASEBKGND) { ClearBackgroundWithTransparentBrush(hwnd, hdc); handled = true; }

说明:

  • 这是“真正透明”实现里非常容易漏掉的一环。
  • 很多黑底问题并不是 XAML 没透明,而是系统背景擦除仍在按默认路径执行。

9. 第六步:通过 DWM 去掉 Win11 外轮廓与系统描边

此处使用到的 API 仅 Windows 11 Build 22000 (21H2)及以上版本系统支持
在 Win11 上,即使已经:

  • 去掉 Win32 标准边框
  • 使用透明 backdrop
  • 启用 layered window

仍可能残留一圈外轮廓。这个外轮廓往往来自 DWM 的窗口圆角与系统描边。

推荐继续设置:

  • DWMWA_WINDOW_CORNER_PREFERENCE = DWMWCP_DONOTROUND
  • DWMWA_BORDER_COLOR = DWMWA_COLOR_NONE

示例:

uint cornerPreference = 1; // DWMWCP_DONOTROUND uint borderColor = 0xFFFFFFFE; // DWMWA_COLOR_NONE DwmSetWindowAttribute(hwnd, 33, ref cornerPreference, sizeof(uint)); DwmSetWindowAttribute(hwnd, 34, ref borderColor, sizeof(uint));

说明:

  • 这一步主要用于去掉 Win11 的圆角轮廓和系统描边。
  • 某些旧系统不支持这些属性,建议做“尽力而为”处理:
    • 支持则应用
    • 不支持则忽略
    • 不要因为该步骤失败而让窗口初始化整体失败

10. 第七步:可选的置顶与点击穿透

10.1 置顶

如果窗口需要始终浮在桌面或其它应用之上,可通过:

SetWindowPos( hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW);

说明:

  • 如果存在多个顶层窗口(例如批注层 + 工具栏),还需要显式维护二者相对层级。

10.2 点击穿透

若窗口在某些模式下需要把输入传给下层应用,可切换:

  • WS_EX_TRANSPARENT

示例:

uint exStyle = enabled ? currentExStyle | WS_EX_TRANSPARENT : currentExStyle & ~WS_EX_TRANSPARENT;

说明:

  • 点击穿透不等于视觉透明。
  • 工具栏类窗口一般不应该永久穿透,否则用户会失去“切回可交互模式”的入口。

11. 推荐的初始化顺序

推荐顺序如下:

  1. 获取 HWND
  2. 获取 AppWindow
  3. 通过 OverlappedPresenter 关标题栏和基础边框
  4. 调整窗口大小和位置
  5. 挂透明 SystemBackdrop
  6. 清理 Win32 标准样式
  7. 设置扩展样式为 WS_EX_TOOLWINDOW | WS_EX_LAYERED
  8. 调用 SetLayeredWindowAttributes
  9. 调用 DWM 去圆角与去描边
  10. 如有需要,再设置置顶与点击穿透

这个顺序的核心原因是:

  • 先让 WinUI 窗口对象稳定创建
  • 再处理透明背景
  • 最后做原生样式与 DWM 外观收敛

12. 最小伪代码示例

下面给出一个通用示意:

void InitializeTransparentOverlayWindow(Window window, RectInt32 bounds) { IntPtr hwnd = GetWindowHandle(window); AppWindow appWindow = GetAppWindow(hwnd); ConfigurePresenter(appWindow, bounds); window.SystemBackdrop = new TransparentBackdrop(hwnd); RemoveStandardWindowFrame(hwnd); ApplyToolLayeredExStyle(hwnd); SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA); TryDisableDwmRoundedCorners(hwnd); TryDisableDwmBorder(hwnd); SetTopMost(hwnd); }

如果还需要模式切换:

void SetPassThrough(IntPtr hwnd, bool enabled) { UpdateTransparentExStyle(hwnd, enabled); }

13. 常见问题排查

13.1 只有 XAML 透明,但窗口还是黑底

优先检查:

  • 是否挂了透明 SystemBackdrop
  • 是否处理了 WM_ERASEBKGND
  • 是否启用了 WS_EX_LAYERED

13.2 标题栏没了,但窗口仍然有边框

优先检查:

  • 是否真正清除了 WS_CAPTION / WS_THICKFRAME
  • 样式修改后是否调用了 SWP_FRAMECHANGED

13.3 Win11 下仍然有一圈外轮廓

优先检查:

  • 是否设置 DWMWA_WINDOW_CORNER_PREFERENCE = DWMWCP_DONOTROUND
  • 是否设置 DWMWA_BORDER_COLOR = DWMWA_COLOR_NONE

13.4 窗口透明了,但拖动或切换时闪烁

优先检查:

  • WM_ERASEBKGND 是否被透明清空
  • WM_DWMCOMPOSITIONCHANGED 后是否重新应用 DWM 配置

14. 验证清单

建议至少人工验证以下项目:

  1. 窗口启动后无标题栏、无标准系统边框
  2. 背景是真正透明,而不是黑底
  3. Win11 下没有残留外轮廓或圆角描边
  4. 置顶行为符合预期
  5. 点击穿透开启后,下层应用可接收输入
  6. 点击穿透关闭后,窗口恢复可交互
  7. 多窗口场景下,相对层级稳定
  8. 窗口关闭时无残留资源、无异常日志

15. 兼容性注意事项

  • DWMWA_BORDER_COLORDWMWA_WINDOW_CORNER_PREFERENCE 仅在 Windows 11 Build 22000 以上系统支持。
  • 对旧系统建议采用“忽略不支持属性”的降级策略,而不是把整个透明窗口初始化判定为失败。

16. 参考

  • DWMWINDOWATTRIBUTE (dwmapi.h) - Win32 apps | Microsoft Learn
  • DevWinUI/dev/DevWinUI/Common/Backdrop at main · ghost1372/DevWinUI

现在这套方案有些复杂,如果佬友有更好的方案欢迎交流!

网友解答:
--【壹】--:

感谢分享


--【贰】--:

补充:引入 API

  • DWMWA_WINDOW_CORNER_PREFERENCE = DWMWCP_DONOTROUND
  • DWMWA_BORDER_COLOR = DWMWA_COLOR_NONE

是为去除Win11特有的窗口样式,Win10下无需进行此步处理


--【叁】--:

感谢大佬!

标签:软件开发