提示:本文所采用的方法属于本人猜想方案(目前没有在Win7及XP尝试过,但在NT的较高版本Win10在不发送052C消息,即不使用系统生成的WorkerW分层窗口实践成功。Win8开始DWM无法关闭,虽然Win11可以关闭Aero和动画,但我是在Win10上尝试的,和不发送052C消息的效果是一样的),目的是在DWM(桌面窗口管理器)的Aero配色方案被关闭的情况下脱离DWM实现分层窗口的部分功能,以便于能够在不支持透明配色方案的系统上实现在桌面图标列表窗口下面嵌入任意窗口,该项目可能应用于桌面壁纸等Web前端。
一、桌面窗口层次回顾
首先,回顾一下Aero开启时桌面分层窗口的层次,打开任务视图,然后退出视图界面:
用Spy++ 刷新 看一下窗口层次:
做过桌面Web小程序的开发者都知道Program是桌面窗口(总窗口),Program被隐藏或关闭时候,它应该是这样(我同时将图标列表也隐藏了,实际上可以看见图标):
而在分层窗口没有诞生时候,桌面窗口层次应该这样:
我们知道这种窗口是无法嵌入窗口的,动态壁纸只能用刷HDC缓存的方法不停刷到桌面。
然而真的就无法嵌入一个可见的窗口了吗?
我的想法是,自己创建分层WorkerWs,几经尝试,发现可行。
以下给一个思路:
二、编写代码
首先创建一个空的桌面窗口程序,以VS2019为例:
创建新项目→选择Windows桌面应用程序→把程序原有的代码全部删除,就可以写了。
以下是创建一个空白窗口(无任何增加功能,不适用于WorkerW)的示例代码:
#include "windows.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //窗口函数说明
//------------------------------以下是入口函数的代码------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcex;
HWND hWnd;
MSG msg;
TCHAR szWindowClass[] = L"窗口示例"; //窗口类名
TCHAR szTitle[] = L"My Windows"; //窗口标题名
//------------------------------以下初始化窗口类------------------------------
wcex.cbSize = sizeof(WNDCLASSEX); //窗口类的大小
wcex.style = 0; //窗口类型为默认类型
wcex.lpfnWndProc = WndProc; //窗口处理函数为WndProc
wcex.cbClsExtra = 0; //窗口类无扩展
wcex.cbWndExtra = 0; //窗口实例无扩展
wcex.hInstance = hInstance; //当前实例句柄
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
//窗口的图标为默认图标
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
//窗口采用箭头光标
wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //窗口背景为白色
wcex.lpszMenuName = NULL; //窗口中无菜单
wcex.lpszClassName = szWindowClass; //窗口类名为“窗口示例”
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
//窗口的小图标为默认图标
//------------------------------以下进行窗口类的注册------------------------------
if (!RegisterClassEx(&wcex)) //如果注册失败则发出警告
{
MessageBox(NULL, L"窗口类注册失败!", L"窗口注册", NULL);
return 1;
}
//------------------------------以下创建窗口------------------------------
hWnd = CreateWindow(
szWindowClass, //窗口类名
szTitle, //窗口实例的标题名
WS_OVERLAPPEDWINDOW, //窗口的风格
CW_USEDEFAULT, CW_USEDEFAULT, //窗口左下角坐标为默认值
CW_USEDEFAULT, CW_USEDEFAULT, //窗口的高和宽为默认值
NULL, //此窗口无父窗口
NULL, //此窗口无主菜单
hInstance, //创建此窗口应用程序的当前句柄
NULL //不使用该值
);
if (!hWnd) //如果创建窗口失败则发出警告
{
MessageBox(NULL, L"创建窗口失败!", L"创建窗口", NULL);
return 1;
}
ShowWindow(hWnd, nCmdShow); //显示窗口
UpdateWindow(hWnd); //绘制用户区
while (GetMessage(&msg, NULL, 0, 0)) //消息循环
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return(int)msg.wParam; //程序终止时将信息返回系统
}
//------------------------------以下是窗口函数的代码------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0); //调用PostQuitMessage发出WM_QUIT消息
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
//默认时采用系统消息默认处理函数
break;
}
return 0;
}
生成窗口如下图所示:
值得注意的是我们的自定义WorkerW窗口必须满足以下条件:
1. 窗口可视(可以自己设置透明度)
2. 窗口类、风格与系统生成的要大致相同。
3. 窗口类名、窗口标题与系统生成窗口相同。
4. 窗口背景画刷与系统设定一致
5. 能处理相应的消息
两个WorkerW都是无标题类名为“WorkerW”的窗口,由于系统有很多WorkerW窗口(DWM开启时才有),为了使我们找自定义的窗口方便,第一个WorkerW的标题我们设置为“ ”(一个单空格),第二个WorkerW 我们设置为“ ”(两个单空格)。Spy++可以看到这几个系统窗口的信息,请把它们一一记录下来,这里有我已经整理好的:桌面窗口层次(Z-Order 记录文档)。
好了,现在我们已经知道开如何创建WorkerW窗口了,以WorkerW 1为例:
我们的操作是:创建WorerW 1窗口》找到SHELLDLL_DefView和SysListView32窗口》同步窗口风格否则SetParent会出现意料之外的结果(这个MSDN未讲,属个人方案,对Win10桌面要这样,如果你的DWM可能仍然处于未知的正常的话)》SetParent重置父窗口。
代码如下:
#include "windows.h"
HWND workerw1 = nullptr; // SHELLDLL_DefView窗口句柄
HWND workerw2 = nullptr; // 第二个WorkerW窗口句柄
HWND workerw3 = nullptr; // FolderView
HWND defview = nullptr;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); //窗口函数说明
//------------------------------以下是窗口函数的代码------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0); //调用PostQuitMessage发出WM_QUIT消息
break;
default:
HWND windowHandle = FindWindow(L"Progman", NULL);
HWND defview1 = FindWindowEx(windowHandle, 0, L"SHELLDLL_DefView", NULL);
//PostMessageA(windowHandle,message,wParam,lParam);
PostMessageA(defview1, message, wParam, lParam);
Sleep(100);//Sleep 可无,时间不可超过100,否则阻塞
return DefWindowProc(hWnd, message, wParam, lParam);
//默认时采用系统消息默认处理函数
break;
}
return 0;
}
BOOL CALLBACK EnumWindowsProc(HWND handle, LPARAM lparam)
{
// 获取Progman窗口
HWND windowHandle = FindWindow(L"Progman", NULL);
defview = FindWindowEx(windowHandle, 0, L"SHELLDLL_DefView", NULL);
if (defview != nullptr) // 找到SHELLDLL_DefView窗口
{
workerw1 = defview;
//获取第二个WorkerW窗口的窗口句柄
workerw2 = FindWindowEx(0, handle, L"WorkerW", 0);
workerw3 = FindWindowEx(defview, nullptr, L"SysListView32", L"FolderView");
}
return true;
}
//------------------------------以下是入口函数的代码------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcex;
HWND hWnd, hwnd;
MSG msg;
TCHAR szWindowClass[] = L"WorkerW"; //窗口类名
TCHAR szTitle[] = L" "; //窗口标题名
static int iKeep[NUM][4];
//------------------------------以下初始化窗口类------------------------------
wcex.cbSize = sizeof(WNDCLASSEX); //窗口类的大小
wcex.style = 0; //窗口类型为默认类型
wcex.lpfnWndProc = WndProc; //窗口处理函数为WndProc
wcex.cbClsExtra = 0; //窗口类无扩展
wcex.cbWndExtra = 0; //窗口实例无扩展
wcex.hInstance = hInstance; //当前实例句柄
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
//窗口的图标为默认图标
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
//窗口采用箭头光标
wcex.hbrBackground = (HBRUSH)(COLOR_3DFACE); // 窗口背景3dface
wcex.lpszMenuName = NULL; //窗口中无菜单
wcex.lpszClassName = szWindowClass; //窗口类名为“WorkerW”
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
//窗口的小图标为默认图标
//------------------------------以下进行窗口类的注册------------------------------
if (!RegisterClassEx(&wcex)) //如果注册失败则发出警告
{
MessageBox(NULL, L"窗口类注册失败!", L"窗口注册", NULL);
return 1;
}
//------------------------------以下创建窗口------------------------------
//RECT rect;
int Width = GetSystemMetrics(SM_CXSCREEN);
int Height = GetSystemMetrics(SM_CYSCREEN);
hWnd = CreateWindowEx(WS_EX_TOOLWINDOW, // 窗口扩展风格,任务栏不显示图标
szWindowClass, //窗口类名
szTitle, //窗口实例的标题名
WS_POPUP, //窗口的风格,建议POPUP弹出窗口风格
CW_USEDEFAULT, CW_USEDEFAULT, //窗口左下角坐标为默认值
Width, Height, //窗口的高和宽为默认值
NULL, //此窗口无父窗口
NULL, //此窗口无主菜单
hInstance, //创建此窗口应用程序的当前句柄
NULL //不使用该值
);
if (!hWnd) //如果创建窗口失败则发出警告
{
MessageBox(NULL, L"创建窗口失败!", L"创建窗口", NULL);
return 1;
}
ShowWindow(hWnd, nCmdShow); //显示窗口
// 以前是发送052C消息方法让系统自动生成,现在不发送,我们自己干
// HWND windowHandle = FindWindow(L"Progman", NULL);
// SendMessageTimeout(windowHandle, 0x052c, 0, 0, SMTO_NORMAL, 0x3e8, (PDWORD_PTR)&result);
//枚举窗口
EnumWindows(EnumWindowsProc, (LPARAM)NULL);
hwnd = GetDesktopWindow();// 获取桌面
//同步窗口的风格,否则SetParent()将出现意料之外的结果。
SetWindowLongA(hWnd, GWL_EXSTYLE,
GetWindowLongA(hWnd, GWL_EXSTYLE)|WS_EX_LAYERED);
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, Width, Height-25, SWP_FRAMECHANGED);// 立即生效
// 不要用 SetLayeredWindowAttributes 设置透明度
//SetLayeredWindowAttributes(hWnd, 0, 10, 2);
//SetLayeredWindowAttributes(defview, 0, 1, 2);
//SetLayeredWindowAttributes(workerw3, 0, 1, 2);
UpdateWindow(hWnd);// 立即更新窗口
SetParent(workerw3, workerw3);
SetWindowLongA(workerw3, GWL_EXSTYLE,
GetWindowLongA(workerw3, GWL_EXSTYLE) &~WS_CHILD|~WS_CHILDWINDOW);
SetParent(defview, hWnd);
UpdateWindow(defview);// 立即更新窗口
UpdateWindow(workerw3);// 立即更新窗口
SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
// 不能遮挡任务栏,全屏时任务栏自动隐藏
// 也可以通过置顶使任务栏处于上方,如下
HWND desktop, task, hStartMenuIcon;
desktop = FindWindow(L"ProgMan", NULL);// 取窗口句柄
task = FindWindow(L"Shell_TrayWnd", NULL);
hStartMenuIcon = FindWindow(L"Button", NULL); //find startmenu icons
SetWindowPos(task, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
SetWindowPos(hStartMenuIcon, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
while (GetMessage(&msg, NULL, 0, 0)) //消息循环
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return(int)msg.wParam; //程序终止时将信息返回系统
}
运行效果如图(由于Z-Order不正确,任务栏未显示):
刷新并使用Spy++的搜索功能,标题:单空格:类名WorkerW,注意:检查输入是否正确!
展开窗口层次:
至此WorkerW 1大体完成,但我们发现两个问题:
1. 任务栏未显示,原因是Z-Order错误
2. 鼠标移动到图标上无选中时的Highlight效果,原因可能是窗口CS类设置不正确。但不影响双击打开功能。
关于Z-Order的设置,你必须设置成:
你可以参考我在桌面窗口层次(Z-Order 记录文档)中的方法,使用SetWindowPos:
收藏博客:博客


![[测试项目]Windows 桌面自定义WorkerW分层窗口 [测试项目]Windows 桌面自定义WorkerW分层窗口](http://www.mshxw.com/aiimages/31/1002214.png)
