动态壁纸实现原理
2024-03-17 17:33:41

桌面窗口层次关系

桌面窗口层次是这样的

检查系统是否开启了Aero

动态壁纸技术需要系统开启Aero才行,原因下面会讲到。
检测系统是否开启了Aero可以使用 DwmIsCompositionEnabled 函数。
只有在Win7及以前的系统上需要检测,Win8后这个函数总是返回 TRUE。

未公开的消息

桌面窗口内部有一个神秘的消息:WM_USER + 300,数值为0x52C
它收到这个消息后就会在其上创建两个窗口,并将图标层移动上去,然后窗口层次就变成这样了

如果系统没有开启Aero,它收到消息后就不会创建这两个窗口,也就无法实现动态壁纸了,这就是系统必须开启Aero的原因。

创建壁纸窗口

最后一步就是创建一个Win32窗口了,然后将其父窗口设置为上面提到的Worker窗口(靠下面的那一个)。

1
2
3
4
5
6
7
8
9
10
11
12
HWND createWindow(HWND hParent)
{
// 获取父窗口尺寸
RECT rect;
GetWindowRect(hParent, &rect);

// 创建动态壁纸窗口并挂到"Worker"窗口下
HWND hWnd = CreateWindowExW(0, CLASSNAME, L"DynamicWallpaper", WS_CHILDWINDOW, 0, 0, rect.right, rect.bottom, hParent, nullptr, nullptr, nullptr);
ShowWindow(hWnd, SW_SHOWNOACTIVATE);

return hWnd;
}

还有个细节要注意,当动态壁纸窗口销毁时需要将父窗口刷新一下,因为父窗口的扩展样式用到了WS_EX_TRANSPARENT,这是一个透明窗口,如果不刷新的话,动态壁纸窗口的残影会一直留在桌面上。
最简单的刷新办法是隐藏它,我们在窗口消息过程中处理即可

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
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lparam)
{
switch (uMsg) {
case WM_CREATE:
{
// 上次隐藏了,这次再显示出来
HWND hParent = GetParent(hWnd);
ShowWindow(hParent, SW_SHOWNOACTIVATE);
break;
}

case WM_CLOSE:
DestroyWindow(hWnd);
break;

case WM_DESTROY:
{
// 隐藏父窗口,避免残影
HWND hParent = GetParent(hWnd);
ShowWindow(hParent, SW_HIDE);
PostQuitMessage(0);
break;
}

default:
return DefWindowProc(hWnd, uMsg, wParam, lparam);
}
return 0;
}

剩下的就是在窗口上播放一个短视频了。还有的人甚至在上面放置浏览器,这不是一个好注意,浏览器太占用内存资源,会比较卡。
至此,一个简单的动态壁纸功能就实现了。

思考

如果要使软件更加完美,还需要考虑以下问题
1、当桌面窗口大小变化时(修改分辨率),壁纸也要跟着变化。
2、用户执行全屏程序时,应停止播放动态壁纸以便节省系统资源。
3、交互功能,一般不用实现交互功能,如果要做,可以考虑HOOK、消息钩子的手段。

参考

如何实现一个 windows 桌面动态壁纸
自定义·自制HTML壁纸
Draw Behind Desktop Icons in Windows 8+
Draw on Windows 10 wallpaper in C++
Drawing on the desktop background as wallpaper replacement (Windows/C#)