在Windows窗口中使用Mica云母和Acrylic亚克力材质
Mica & Acrylic backdrop in Windows apps
为Windows app启用 Mica(云母)、Acrylic(亚克力) 等材质背景
Inroduction
Windows中的Mica云母是一种在窗口背景上显示的“材质”,在未启用时,软件窗口的背景通常是静态、纯色的,本文将教导如何在自己的软件中启用Mica材质,且主要以 C++ 的API调用为主。
像Mica这样材质还有 Acrylic(亚克力)、Aero(Windows Vista/7 时期大量采用),在Windows10中主要以Acrylic为主,Windows11中,主要以Mica为主。
Code
窗口会被DWM进行装饰,通常使用dwmapi对指定窗口操作即可更换样式。Windows11中,不仅包括背景材质,还包括窗口圆角、深色模式等等。
快速开始
在原生Win32应用、Qt(QWidget)应用中快速使用
//MSVC (visual C++) 环境中
#include <windows.h>
#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")
/* 前置条件
需要有窗口背景透明操作。若窗口没有透明化,则会遮挡特殊背景材质
*/
void applyMicaEffect(HWND hwnd)
{
MARGINS margins {-1, -1, -1, -1};
DWM_SYSTEMBACKDROP_TYPE type = DWM_SYSTEMBACKDROP_TYPE::DWMSBT_MAINWINDOW;
DwmExtendFrameIntoClientArea(hwnd, &margins);
DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &type, sizeof(type))
}
// 对Qt应用 (QWidget)
#include <QWidget>
void QtWidget(QWidget* widget)
{
HWND hwnd = reinterpreter_cast<HWND>(widget->winId());
applyMicaEffect(hwnd);
}// 前置条件:透明背景窗口操作
LRESULT NativeWindows(HWND hwnd)
{
//...
switch (/*?窗口消息类别?*/) {
//消息处理循环中 在处理窗口的消息 WM_PAINT 和 WM_ERASEBKGND 时
case WM_ERASEBKGND: {
return 1;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT clientRect;
GetClientRect(hwnd, &clientRect);
HBRUSH brush = CreateSolidBrush(RGB(0, 0, 0));
FillRect(hdc, &clientRect, brush);
DeleteObject(brush);
EndPaint(hwnd, &ps);
break;
}
}
// ...
}
void QtWidget(QWidget* widget)
{
widget->setAttribute(Qt::WA_TranslucentBackground);
}API
核心函数:DwmSetWindowAttribute 微软官方文档
HRESULT DwmSetWindowAttribute(
HWND targetWindowHandle,
DWORD attributeType,
LPCVOID attributeValuePointer,
DWORD attributeValueSize
);in header dwmapi.h
函数参数解释
HWND句柄,原生控制句柄,通常框架也好获得;属性类型是需要修改的属性,在枚举中DWMWA_SYSTEMBACKDROP_TYPE就是背景类型属性;背景属性值也是在枚举DWM_SYSTEMBACKDROP_TYPE 中为DWMSBT_AUTO, DWMSBT_NONE, DWMSBT_MAINWINDOW, DWMSBT_TRANSIENTWINDOW, DWMSBT_TABBEDWINDOW。在Windows11中,一般有三个直接值,分别为 MainWindow:Mica、TransientWindow:Acrylic、TabbedWindow:MicaAlt(特殊云母)。其中剩余的两个枚举值中NONE为关闭,AUTO为自动控制,通常会因ExtendFrameIntoClientArea使窗口变为NONE。
typedef enum DWM_SYSTEMBACKDROP_TYPE {
DWMSBT_AUTO, // 无样式
DWMSBT_NONE, // 自动样式(默认值)
DWMSBT_MAINWINDOW, // 主窗口:Mica(云母)
DWMSBT_TRANSIENTWINDOW, // 透明窗口:Acrylic(亚克力)(后续可能改变)
DWMSBT_TABBEDWINDOW // 选项卡窗口:MicaAlt(云母变体)(后续可能改变)
};
void applyBackdrop(HWND hwnd, DWM_SYSTEMBACKDROP_TYPE type)
{
DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &type, sizeof(type))
}取值:DwmGetWindowAttribute (指针获取) - 微软官方文档
必要设置
直接使用该API并不能直接看到效果,因为两点:1. 背景类型只直接作用于边框,使用DwmExtendFrameIntoClientArea控制边框实际位置至整个窗口即可;2. 窗口通常都会在表面绘制一层纯色背景,而这就遮挡了实际背景。
1. 将背景样式作用于整个窗口
API DwmExtendFrameIntoClientArea in header dwmapi.h
将margins设为负值,整个窗口都会成为边框区,从而整个窗口都能显示样式背景。
void extendFrameIntoClientArea(HWND hwnd)
{
MARGINS margins{ -1, -1, -1, -1 };
DwmExtendFrameIntoClientArea(hWnd, &margins);
}

如果margins成员没有负数,则按实际值显示,若其中有一个为负则全窗口都是背景样式区域
2. 取消/移除 自带纯色背景
移除自带纯色背景(Windows11通常在浅色模式下为白色)是必要的,否则会在设置的材质上在次绘制一层,导致等同于无效果。对于原生Win32窗口,处理WM_ERASEBKGND和WM_PAINT两个消息即可
LRESULT CALLBACK windowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
//case WM_COMMAND等 其他消息
case WM_ERASEBKGND:
return 1;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT clientRect;
GetClientRect(hWnd, &clientRect);
HBRUSH brush = CreateSolidBrush(RGB(0, 0, 0));
FillRect(hdc, &clientRect, brush);
DeleteObject(brush);
EndPaint(hWnd, &ps);
break;
}
//case WM_DESTROY等其他消息
}
return 0;
}若是使用一些GUI框架,如Qt 通常都有较为方便的办法
void EraseQWidgetBackgroud(QWidget* w)
{
w->setAttribute(Qt::WA_TranslucentBackground);
}Final
总结为三个点
透明背景窗口 - 放置材质被覆盖看不见
扩展边框 - 使材质能应用在整个窗口
应用材质 - 枚举中选择,可选Mica云母的同时还可以选择主动关闭、使用亚克力等材质
需要注意的是本文主要在Windows11环境中使用,Windows10等API会有些差别,比如根本没有Mica云母材质。
Dwm作为Windows的窗口管理器,可玩性很高,更多可以查阅官方文档 包括DwmSetWindowAttribute 的可选属性
References in Microsoft Learn
DwmExtendFrameIntoClientArea 函数 - Win32 apps | Microsoft Learn
DwmSetWindowAttribute 函数 - Win32 apps | Microsoft Learn