WinUI3 无边框透明窗口实现指南
- 内容介绍
- 文章标签
- 相关推荐
本文档用于说明在 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. 总体实现链路
推荐按下面顺序处理:
- 创建普通 WinUI 顶层窗口
- 通过
AppWindow/OverlappedPresenter关闭标题栏与标准边框 - 通过 Win32 样式清理标准窗口边框相关位
- 把窗口切到
WS_EX_LAYERED - 为窗口添加透明
SystemBackdrop - 拦截背景擦除,避免退化成黑底
- 通过 DWM 属性关闭系统描边与圆角外轮廓
- 如有需要,再补置顶、相对层级、点击穿透
4. 第一步:关闭 WinUI 标题栏与基础边框
先通过 AppWindow 和 OverlappedPresenter 关闭标准标题栏:
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_CAPTIONWS_THICKFRAMEWS_MINIMIZEBOXWS_MAXIMIZEBOXWS_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_LAYEREDWS_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_ERASEBKGNDWM_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_DONOTROUNDDWMWA_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. 推荐的初始化顺序
推荐顺序如下:
- 获取
HWND - 获取
AppWindow - 通过
OverlappedPresenter关标题栏和基础边框 - 调整窗口大小和位置
- 挂透明
SystemBackdrop - 清理 Win32 标准样式
- 设置扩展样式为
WS_EX_TOOLWINDOW | WS_EX_LAYERED - 调用
SetLayeredWindowAttributes - 调用 DWM 去圆角与去描边
- 如有需要,再设置置顶与点击穿透
这个顺序的核心原因是:
- 先让 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. 验证清单
建议至少人工验证以下项目:
- 窗口启动后无标题栏、无标准系统边框
- 背景是真正透明,而不是黑底
- Win11 下没有残留外轮廓或圆角描边
- 置顶行为符合预期
- 点击穿透开启后,下层应用可接收输入
- 点击穿透关闭后,窗口恢复可交互
- 多窗口场景下,相对层级稳定
- 窗口关闭时无残留资源、无异常日志
15. 兼容性注意事项
DWMWA_BORDER_COLOR、DWMWA_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_DONOTROUNDDWMWA_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. 总体实现链路
推荐按下面顺序处理:
- 创建普通 WinUI 顶层窗口
- 通过
AppWindow/OverlappedPresenter关闭标题栏与标准边框 - 通过 Win32 样式清理标准窗口边框相关位
- 把窗口切到
WS_EX_LAYERED - 为窗口添加透明
SystemBackdrop - 拦截背景擦除,避免退化成黑底
- 通过 DWM 属性关闭系统描边与圆角外轮廓
- 如有需要,再补置顶、相对层级、点击穿透
4. 第一步:关闭 WinUI 标题栏与基础边框
先通过 AppWindow 和 OverlappedPresenter 关闭标准标题栏:
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_CAPTIONWS_THICKFRAMEWS_MINIMIZEBOXWS_MAXIMIZEBOXWS_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_LAYEREDWS_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_ERASEBKGNDWM_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_DONOTROUNDDWMWA_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. 推荐的初始化顺序
推荐顺序如下:
- 获取
HWND - 获取
AppWindow - 通过
OverlappedPresenter关标题栏和基础边框 - 调整窗口大小和位置
- 挂透明
SystemBackdrop - 清理 Win32 标准样式
- 设置扩展样式为
WS_EX_TOOLWINDOW | WS_EX_LAYERED - 调用
SetLayeredWindowAttributes - 调用 DWM 去圆角与去描边
- 如有需要,再设置置顶与点击穿透
这个顺序的核心原因是:
- 先让 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. 验证清单
建议至少人工验证以下项目:
- 窗口启动后无标题栏、无标准系统边框
- 背景是真正透明,而不是黑底
- Win11 下没有残留外轮廓或圆角描边
- 置顶行为符合预期
- 点击穿透开启后,下层应用可接收输入
- 点击穿透关闭后,窗口恢复可交互
- 多窗口场景下,相对层级稳定
- 窗口关闭时无残留资源、无异常日志
15. 兼容性注意事项
DWMWA_BORDER_COLOR、DWMWA_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_DONOTROUNDDWMWA_BORDER_COLOR = DWMWA_COLOR_NONE
是为去除Win11特有的窗口样式,Win10下无需进行此步处理
--【叁】--:
感谢大佬!

