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

39次阅读
没有评论

共计 5074 个字符,预计需要花费 13 分钟才能阅读完成。

(内容来自我于 2026 年 3 月 21 日在 LINUX DO 平台上的帖子,原帖:https://linux.do/t/1790806
本文档用于说明在 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. 效果示意图

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


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 外轮廓与系统描边

在 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. 验证清单

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

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

15. 兼容性注意事项

DWMWA_BORDER_COLORDWMWA_WINDOW_CORNER_PREFERENCE 仅在 Windows 11 Build 22000 以上系统支持。

对旧系统建议采用 " 忽略不支持属性 " 的降级策略。


16. 参考


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

正文完
 0
一言一句话
-「
评论(没有评论)