Skip to content

WinUI 参考: 完整的属性面与设计指导,参见 Window Features

高级窗口特性

有些外壳窗口的构想,在 Windows 上只有脱离 Reactor 的安全契约才可能实现。我们把这些做法记录下来,是为了让专家级应用能在充分知情的前提下自行取舍;但 Reactor 不会把这些原语作为一等公民的 WindowSpec 字段提供。

FancyZones 风格的鼠标穿透叠加层

类似 FancyZones 的叠加层需要逐像素或颜色键透明,以及鼠标点击穿透。WinUI XAML 合成器无法与经典的 WS_EX_LAYERED 渲染干净地组合,因此 Reactor 只暴露更安全的构建块(OpacityIgnorePointerInputWindowStyle.None),把真正的分层叠加层留给 HWND 互操作。

const int GWL_EXSTYLE = -20;
const nint WS_EX_TRANSPARENT = 0x00000020;
const nint WS_EX_TOOLWINDOW = 0x00000080;
const nint WS_EX_LAYERED = 0x00080000;
const uint LWA_ALPHA = 0x00000002;

public static void MakeClickThrough(ReactorWindow window)
{
    var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window.NativeWindow);
    var ex = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
    SetWindowLongPtr(hwnd, GWL_EXSTYLE,
        ex | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW);
    SetLayeredWindowAttributes(hwnd, 0, 160, LWA_ALPHA);
}

注意事项:

  • WS_EX_LAYERED 可能绕过 WinUI 的合成路径;每次构建都要实测。
  • 点击穿透意味着该窗口无法接收正常的指针输入。
  • 请使用独立的叠加窗口;不要给主应用外壳加分层。

HUD 外观范例

当一个 HUD 风格的表面只需要外观效果、而不需要真正的透明时,它可以留在 Reactor 受支持的契约之内:

static class HudWindow
{
    public static WindowSpec Spec { get; } = new()
    {
        Title = "HUD",
        Style = WindowStyle.None,
        IsMovableByBackground = true,
        Level = WindowLevel.Floating,
        CornerStyle = WindowCornerStyle.Rounded,
        Backdrop = BackdropChoice.Of(BackdropKind.DesktopAcrylic),
    };
}

渲染一个深色调的根 Border,加上自定义的 TitleBar(...),并用 WindowLevel.Floating 让该表面保持在应用本地。随仓库发布的 samples/apps/window-styles 应用就是它的活体对照:在一个窗口上切换全部 WindowStyleWindowLevelWindowCornerStyleBackdropKind,让你在定稿前先看到组合效果;而 samples/apps/tool-palette 则以真实调色板窗口的形式演示了同样的形态。

注意事项:

  • BackdropKind.DesktopAcrylic 不是 vibrancy(活力材质);它是 Windows 的背景材质。
  • WindowStyle.None 会移除系统菜单和标题栏拖拽的交互区域。
  • 无边框窗口请搭配 IsMovableByBackground 或显式调用 BeginDragMove()

通过 SetWindowRgn 实现任意圆角

DWM 只暴露离散的圆角偏好(DefaultSquareRoundedRoundedSmall)。你可以用 SetWindowRgn 强制指定任意区域,但这不是平台品质的默认做法。

public static void ApplyRoundedRegion(nint hwnd, int widthPx, int heightPx, int radiusPx)
{
    // CreateRoundRectRgn's last two args are the width/height of the corner
    // ellipse, not the radius — pass the diameter to get `radiusPx` corners.
    var region = CreateRoundRectRgn(0, 0, widthPx, heightPx, radiusPx * 2, radiusPx * 2);
    if (region == 0) return;

    // SetWindowRgn transfers ownership of the HRGN to the system on
    // success — the region must NOT be deleted afterwards. Delete it
    // only when the call fails, or the next repaint reads freed GDI
    // memory.
    if (SetWindowRgn(hwnd, region, bRedraw: true) == 0)
        DeleteObject(region);
}

取舍:

  • DWM 阴影通常会丢失或被裁剪。
  • 在 100% DPI 下边缘可能有锯齿,因为区域是二值掩码。
  • 调整大小时必须重新计算该区域,这可能引发重绘级联。
  • SetWindowRgn 成功后,HRGN 的所有权归系统所有。 之后再删除它 —— 或用 using / SafeHandle 包装而间接删除 —— 会释放合成器仍在读取的 GDI 内存。只在 SetWindowRgn 调用失败时才删除该区域。

仅在你愿意接受上述代价的高度专用外壳中使用。

无法作为 Reactor 原语提供的能力

场景 为何不是 Reactor 的一等 API
真正透明的 XAML 窗口 WinUI XAML 合成与经典分层窗口渲染相冲突。
NSWindow 式的层级栈 Windows 只有「普通」和「置顶」两档,没有任意的 z 轴阶梯。
SystemBackdrop 之外的 vibrancy/HUD 材质 需要平台合成器支持,而不是框架层面的垫片。
顶层 HWND 上的连续 CornerRadius DWM 只暴露离散的圆角样式;用区域会损失画质。

当你确实需要其中某项时,请把不受支持的互操作隔离在一个小型托管辅助类中,让其余 UI 仍是声明式的 Reactor 组件。

后续阅读