Windows11 22H2 自定义标题栏颜色

在 Windows 11 22H2 中自定义窗口标题栏颜色

适用系统:Windows 11 版本 22H2(Build 22621)及以上
适用场景:Win32 或 .NET 桌面应用(WPF / WinForms)
关键词:DWM、标题栏、自定义颜色、Windows 11、C#、DwmSetWindowAttribute


背景

Windows 11 22H2(2022 年 10 月更新,Build 22621) 中,微软悄悄开放了几个新的 DWM(Desktop Window Manager)属性,其中就包括了:

  • DWMWA_BORDER_COLOR:设置窗口边框色
  • DWMWA_CAPTION_COLOR:设置标题栏背景色
  • DWMWA_TEXT_COLOR:设置标题栏文字(包括按钮图标)颜色

这意味着,我们终于可以在不破坏系统原生体验的前提下,为窗口标题栏指定任意颜色!

本文将带你用 C# 封装这三个 API,并提供完整、可复用的工具类,轻松实现标题栏个性化。


技术原理

Windows 11 的标题栏由 DWM 渲染,而非传统 GDI。因此,要修改其外观,必须通过 DWM 提供的接口。

微软在 dwmapi.dll 中新增了 DwmSetWindowAttribute 函数支持以下两个属性(常量值):

1
2
3
#define DWMWA_BORDER_COLOR  34
#define DWMWA_CAPTION_COLOR 35
#define DWMWA_TEXT_COLOR 36

调用时传入一个 RGB 颜色值(格式为 0x00BBGGRR,即可生效。注意:Alpha 通道被忽略,且颜色顺序是 B-G-R,而非常见的 R-G-B。

⚠️ 重要提示:
这两个属性仅在 Windows 11 22H2 及更高版本有效。在 Windows 10 或早期 Windows 11 上调用会失败(返回非零 HRESULT)。


C# 封装实现

下面是一个完整的静态工具类 DwmApi,封装了标题栏颜色设置功能,并附带辅助方法用于获取进程窗口句柄,还贴心的给了详细注释,就算完全不看这篇文章也可以拿来就用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;

/// <summary>
/// 提供对 Windows 桌面窗口管理器(DWM)API 的封装,主要用于在 Windows 11(22H2 及更高版本)中
/// 自定义窗口标题栏的背景色和文字颜色。
/// </summary>
/// <remarks>
/// <para>
/// 本类依赖于 Windows 11 22H2(Build 22621)及以上版本引入的 DWM 属性:
/// <see cref="DWMWA_CAPTION_COLOR" /><see cref="DWMWA_TEXT_COLOR" />
/// 在旧版 Windows(如 Windows 10 或早期 Windows 11)上调用相关方法将失败。
/// </para>
/// <para>
/// 使用前请确保目标窗口属于当前进程或具有适当权限访问目标窗口句柄。
/// </para>
/// </remarks>
public static class DwmApi
{
/// <summary>
/// 定义用于枚举线程窗口的回调委托。
/// </summary>
/// <param name="hWnd">当前枚举到的窗口句柄。</param>
/// <param name="lParam">传递给 <see cref="EnumerateWindows" /> 的附加参数(本实现中未使用)。</param>
/// <returns>返回 <see langword="true" /> 继续枚举,<see langword="false" /> 停止枚举。</returns>
private delegate bool EnumerateWindowsProc(IntPtr hWnd, IntPtr lParam);

/// <summary>
/// 枚举指定线程创建的所有窗口。
/// </summary>
/// <param name="dwThreadId">线程 ID。</param>
/// <param name="lpfn">回调函数,用于处理每个窗口句柄。</param>
/// <param name="lParam">传递给回调函数的附加参数。</param>
/// <returns>如果成功枚举至少一个窗口,则返回 <see langword="true" />;否则返回 <see langword="false" /></returns>
[DllImport("user32.dll", EntryPoint = "EnumThreadWindows")]
private static extern bool EnumerateWindows(
int dwThreadId,
EnumerateWindowsProc lpfn,
IntPtr lParam
);

/// <summary>
/// 设置指定窗口的 DWM 属性。
/// </summary>
/// <param name="hwnd">目标窗口句柄。</param>
/// <param name="dwAttribute">要设置的属性标识符。</param>
/// <param name="pvAttribute">指向属性值的指针(本实现中为 <see cref="uint" /> 引用)。</param>
/// <param name="cbAttribute">属性值的大小(以字节为单位)。</param>
/// <returns>返回 HRESULT。成功时为 <c>S_OK (0)</c></returns>
[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(
IntPtr hwnd,
int dwAttribute,
ref uint pvAttribute,
int cbAttribute
);

// DWM 属性常量(仅在 Windows 11 22H2+ 支持)
private const int DWMWA_BORDER_COLOR = 34; // 窗口边框颜色
private const int DWMWA_CAPTION_COLOR = 35; // 标题栏背景颜色
private const int DWMWA_TEXT_COLOR = 36; // 标题栏文字颜色

/// <summary>
/// 枚举指定进程 ID 创建的所有窗口句柄。
/// </summary>
/// <param name="processId">目标进程的 ID。</param>
/// <returns>包含该进程所有窗口句柄的数组;若进程不存在或无窗口,则返回空数组。</returns>
/// <exception cref="ArgumentException">当指定的 <paramref name="processId" /> 对应的进程不存在时抛出。</exception>
/// <remarks>
/// 此方法通过遍历进程中的每个线程,并调用 <c>EnumThreadWindows</c> 来收集窗口句柄。
/// 注意:某些窗口(如隐藏窗口、消息窗口)可能不会被枚举到。
/// </remarks>
public static IntPtr[] EnumerateProcessWindowHandles(int processId)
{
List<IntPtr> handles = [];

// 获取指定进程
var process = Process.GetProcessById(processId);

foreach (ProcessThread thread in process.Threads)
{
EnumerateWindows(thread.Id, (hWnd, lParam) =>
{
handles.Add(hWnd);

return true; // 继续枚举
}, IntPtr.Zero);
}

return handles.ToArray();
}

/// <summary>
/// 为指定窗口设置 Windows 11 标题栏的背景颜色。
/// </summary>
/// <param name="hwnd">目标窗口的句柄。</param>
/// <param name="color">要设置的 RGB 颜色(Alpha 通道将被忽略)。</param>
/// <returns>如果设置成功(即系统支持且调用成功),返回 <see langword="true" />;否则返回 <see langword="false" /></returns>
/// <remarks>
/// <para>
/// 仅在 Windows 11 22H2(Build 22621)或更高版本中有效。
/// 在不支持的系统上,此方法将返回 <see langword="false" />
/// </para>
/// <para>
/// 颜色格式为 0x00BBGGRR(注意:G 和 B 位移顺序),与常见的 0xAARRGGBB 不同。
/// 本方法内部已正确转换 <see cref="Color" /> 结构。
/// </para>
/// </remarks>
public static bool SetCaptionColor(IntPtr hwnd, Color color)
{
// 构造 DWM 颜色值:格式为 0x00BBGGRR
uint rgb = color.R | (uint)color.G << 8 | (uint)color.B << 16;
int hr = DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, ref rgb, sizeof(uint));

return hr == 0; // S_OK = 0
}

/// <summary>
/// 为指定窗口设置 Windows 11 标题栏的文字颜色。
/// </summary>
/// <param name="hwnd">目标窗口的句柄。</param>
/// <param name="color">要设置的文字颜色(Alpha 通道将被忽略)。</param>
/// <returns>如果设置成功(即系统支持且调用成功),返回 <see langword="true" />;否则返回 <see langword="false" /></returns>
/// <remarks>
/// <para>
/// 仅在 Windows 11 22H2(Build 22621)或更高版本中有效。
/// 在不支持的系统上,此方法将返回 <see langword="false" />
/// </para>
/// <para>
/// 推荐文字颜色与背景颜色具有足够对比度以确保可读性。
/// </para>
/// </remarks>
public static bool SetCaptionTextColor(IntPtr hwnd, Color color)
{
uint rgb = color.R | (uint)color.G << 8 | (uint)color.B << 16;
int hr = DwmSetWindowAttribute(hwnd, DWMWA_TEXT_COLOR, ref rgb, sizeof(uint));

return hr == 0;
}


/// <summary>
/// 为指定窗口设置 Windows 11 窗口边框的颜色。
/// </summary>
/// <param name="hwnd">目标窗口的句柄。</param>
/// <param name="color">要设置的窗口边框颜色(Alpha 通道将被忽略)。</param>
/// <returns>如果设置成功(即系统支持且调用成功),返回 <see langword="true" />;否则返回 <see langword="false" /></returns>
/// <remarks>
/// <para>
/// 仅在 Windows 11 22H2(Build 22621)或更高版本中有效。
/// 在不支持的系统上,此方法将返回 <see langword="false" />
/// </para>
/// <para>
/// 推荐文字颜色与背景颜色具有足够对比度以确保可读性。
/// </para>
/// </remarks>
public static bool SetWindowBorderColor(IntPtr hwnd, Color color)
{
uint rgb = color.R | (uint)color.G << 8 | (uint)color.B << 16;
int hr = DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, ref rgb, sizeof(uint));
return hr == 0; // S_OK
}
}

关键细节说明

  1. 颜色格式转换
    Color 结构是 R, G, B,但 DWM 要求 0x00BBGGRR,因此我们通过位运算重组:

    1
    uint rgb = color.R | (uint)color.G << 8 | (uint)color.B << 16;
  2. 错误处理
    DwmSetWindowAttribute 返回 HRESULT,成功时为 S_OK (0)。我们据此判断是否支持。

  3. 窗口句柄获取
    提供了 EnumerateProcessWindowHandles 方法,方便在不知道具体 HWND 时通过进程 ID 获取所有窗口(适用于外部进程控制或调试)。


使用示例

1. 在 WinForms 中使用

1
2
3
4
5
6
7
8
9
10
11
12
public partial class Form1 : Form
{
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);

// 设置标题栏背景为深蓝,文字为白色
DwmApi.SetCaptionColor(this.Handle, Color.FromArgb(30, 30, 60));
DwmApi.SetCaptionTextColor(this.Handle, Color.White);
DwmApi.SetWindowBorderColor(hwnd, Colors.White);
}
}

2. 在 WPF 中使用

WPF 窗口需启用 AllowsTransparency="False"(默认),并通过 WindowInteropHelper 获取句柄:

1
2
3
4
5
6
7
8
9
10
11
12
public partial class MainWindow : Window
{
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);

var hwnd = new WindowInteropHelper(this).Handle;
DwmApi.SetCaptionColor(hwnd, Color.Purple);
DwmApi.SetCaptionTextColor(hwnd, Colors.White);
DwmApi.SetWindowBorderColor(hwnd, Colors.White);
}
}

3. 在 Avalonia 中使用

1
2
3
4
5
6
7
8
9
10
11
12
public MainWindow()
{
InitializeComponent();

var hwnd = TryGetPlatformHandle();
if (hwnd != null)
{
DwmApi.SetCaptionColor(hwnd.Handle, Color.FromArgb(255, 30, 30, 80));
DwmApi.SetCaptionTextColor(hwnd.Handle, Color.White);
DwmApi.SetWindowBorderColor(hwnd, Colors.White);
}
}

4. 修改其他进程的窗口(需权限)

1
2
3
4
5
6
7
8
9
10
11
12
// 修改记事本标题栏(假设已打开)
var notepad = Process.GetProcessesByName("notepad").FirstOrDefault();
if (notepad != null)
{
var windows = DwmApi.EnumerateProcessWindowHandles(notepad.Id);
foreach (var hwnd in windows)
{
DwmApi.SetCaptionColor(hwnd, Color.LightGreen);
DwmApi.SetCaptionTextColor(hwnd, Color.DarkGreen);
DwmApi.SetWindowBorderColor(hwnd, Colors.White);
}
}

🔒 注意:修改其他进程窗口可能需要管理员权限,且某些受保护进程(如任务管理器)会拒绝访问。


注意事项与限制

  1. 仅支持 Windows 11 22H2+
    在旧系统上调用会静默失败(返回 false),建议先检测系统版本:

    1
    2
    3
    4
    if (Environment.OSVersion.Version.Build >= 22621)
    {
    // 安全调用
    }
  2. 不支持透明或半透明
    Alpha 通道被忽略,只能设置纯色。

  3. 最小化/最大化按钮颜色跟随文字色
    设置 DWMWA_TEXT_COLOR 后,三个窗口按钮(关闭、最大化、最小化)的图标颜色也会随之改变,但对于通过美化工具自定义的窗口按钮图标无效。

  4. 高对比度模式下可能被覆盖
    系统高对比度主题会强制使用系统配色,此时自定义颜色可能不生效。


尾声

Windows 11 22H2 终于为开发者打开了标题栏自定义的大门。虽然功能仍较基础(仅支持纯色),但已足够满足大多数品牌化或主题化需求。通过本文提供的 DwmApi 类,我们可以轻松在 .NET 应用中集成这一特性,打造更具个性的桌面体验。


有关的东东


Icon
致谢名单
本作品由 Mioter 于 2025-10-17 01:46:03 发布
作品地址:Windows11 22H2 自定义标题栏颜色
除特别声明外,本站作品均采用 CC BY-NC-SA 4.0 许可协议,转载请注明来自 Mioter的博客
Logo