提示:本文所采用的方法属于本人猜想方案(目前没有在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;                            //窗口处理函数为WndProcwcex.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" ";                        //窗口标题名//------------------------------以下初始化窗口类------------------------------wcex.cbSize = sizeof(WNDCLASSEX);                        //窗口类的大小wcex.style = 0;                                        //窗口类型为默认类型wcex.lpfnWndProc = WndProc;                            //窗口处理函数为WndProcwcex.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);           // 窗口背景3dfacewcex.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);                            //显示窗口//枚举窗口EnumWindows(EnumWindowsProc, (LPARAM)NULL);hwnd = GetDesktopWindow();// 获取桌面//同步窗口的风格,否则SetParent()将出现意料之外的结果。SetWindowLongA(hWnd, GWL_EXSTYLE,GetWindowLongA(hWnd, GWL_EXSTYLE));SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, Width, Height, SWP_FRAMECHANGED);// 立即生效UpdateWindow(hWnd);// 立即更新窗口SetParent(workerw3, workerw3);SetWindowLongA(workerw3, GWL_EXSTYLE,GetWindowLongA(workerw3, GWL_EXSTYLE) &~WS_CHILD|~WS_CHILDWINDOW);SetParent(defview, hWnd);UpdateWindow(defview);// 立即更新窗口UpdateWindow(workerw3);// 立即更新窗口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类设置不正确,但主要在于没处理WM_MOUSEMOVE或者WM_SETCURSOR消息。但不影响双击打开功能,后面会将解决方案。

关于Z-Order的设置,你必须设置成:

然而,由于微软Z-Order Band(Z顺序段)原则,第三方的序列无法设置成这样的Z序,即使使用了DeferWindowPos这样的函数。而SetWindowPos只能设置HWND_TOPMOST、HWND_TOP、HWND_BOTTOM、HWND_NOTOPMOST这四个值,并且只是改变了在DEAKTOP BAND中改变次层次,依然是无法达到Program所在的段层次的。

除非用注入DLL获取系统用于注册窗口段层次的CreateWindowInBandEx函数,相比于CreateWindowEx他多出了dwBand 和 dwTypeFlags这两个可传入参数。其中dwBand就是控制Z顺序段层次的,像开始菜单和任务栏显示时就处于ZBID_IMMERSIVE_MOGO的段序列。不同的段就像一个个隔板一样划分Z顺序的大层次。而就如ADeltaX在2020年2月1日发布的博客Window z-order in Windows 10一文中指出的那样,这种注入方法也没有原生的那么好用。


Is there a way to bypass it? You did it, so I guess yes?

Yes…ish. You will not like the way I did it but that’s (currently) the only way.

... This sucks but oh, I haven’t found yet a better way.


不过,如果只是为了让任务栏显示出来,可以不用这么麻烦,尽管Z顺序不正确的话。

比如,方法一:任务栏和开始菜单置顶

使用Spy的查找窗口功能,将十字框(查找窗口工具字样右侧图标单击)取下并移动到我们要的窗口上,你就可以获取到相应的父(子)窗口的信息,我们只要父窗口的标题或者类名即可。

任务栏父窗口:标题:(无)类名:Shell_TrayWnd

开始菜单父窗口也是Shell_TrayWnd窗口。也就是说,只要让这个窗口始终处于上方即可。

可以使用SetWindowPos,该函数定义如下:

BOOL WINAPI SetWindowPos(
_In_ HWND hWnd,
_In_opt_ HWND hWndInsertAfter,
_In_ int X,
_In_ int Y,
_In_ int cx,
_In_ int cy,
_In_ UINT uFlags
);

要使得窗口置顶也就是HWND_TOPMOST,就要这样写:

SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
// SWP_NOMOVE 设置后x,y参数无效,以原始设置为准,
// SWP_NOSIZE 设置后cx,cy参数无效,以原始设置为准

这时候,我们的窗口目前处于Topmost层级的最上面,直到同段(Z-Band)的其他窗口新调用了SetWindowPos的HWND_TOPMOST,这时候我们的窗口这会在Topmost集内部下移一层。

这样的话,就可以保证任务栏等在我们的窗口的上方。

使用Findwindow即可找到Shell_TrayWnd:

    // 不能遮挡任务栏,全屏时任务栏自动隐藏// 法1:可以通过置顶使任务栏处于上方,如下HWND desktop, task;desktop = FindWindow(L"ProgMan", NULL);// 取窗口句柄task = FindWindow(L"Shell_TrayWnd", NULL);// 使得 Shell_TrayWnd 窗口置顶SetWindowPos(task, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

这一次任务栏显示了。但是,一旦激活图标窗口,任务栏就又会被隐藏,这是因为GetSystemMetrics不能直接用SM_CXSCREEN和SM_CYSCREEN,因为他们获取的是全屏的,当窗口全屏且被激活时,的确会使得任务栏被遮挡或者隐藏。


持续更新,最近比较忙,来不及全写上来,后面补全!如有错误,欢迎指正,谢谢!

收藏博客:博客

[测试项目]Windows 桌面自定义WorkerW分层窗口相关推荐

  1. 学习笔记(01):Qt视频教程-基础实例进阶-嵌入windows桌面图标下的窗口程序

    立即学习:https://edu.csdn.net/course/play/5252/95608?utm_source=blogtoedu 1.Windows的所有窗口都有一个描述符,被称为句柄. 2 ...

  2. Windows核心编程_将窗口嵌入到桌面图标下面不被遮挡 spy 分析过程

    近年很流行动态视频桌面,实则上早期的windows vista系统上有一个Windows DreamScene软件将桌面壁纸设置成视频,但是是收费的! 首先先来观察一下Windows桌面的组成单元: ...

  3. 对Windows桌面应用程序进行UI自动化测试

    所谓UI自动化测试,就是模拟一个用户,对应用程序的UI进行操作,以完成特定场景的功能性集成测试. 要对Windows桌面应用程序进行UI自动化测试,目前可选的技术主要是两种:VS自带的CodedUI ...

  4. python 桌面程序自动化测试_对Windows桌面应用程序进行UI自动化测试

    题记:本文简述如何利用appium对Windows桌面应用程序进行UI自动化测试. 所谓UI自动化测试,就是模拟一个用户,对应用程序的UI进行操作,以完成特定场景的功能性集成测试. 要对Windows ...

  5. C#【自动化测试】对Windows桌面应用程序进行UI自动化测试

    文章目录 1.文章一: 题记 UI自动化测试 参考 2.文章二: 3.重要资源[实例]: 1)已验证可用[非常好的例子,便于理解] 关键代码 2)未验证 1.文章一: 题记 本文简述如何利用appiu ...

  6. windows控制linux桌面图标,完全控制你的Windows桌面 (转)

    完全控制你的Windows桌面 (转)[@more@] 完全控制你的桌面 对于Windows的桌面界面相信大家已经十分熟悉了,占据屏幕大半部分的是桌面,在上面排列的是桌面图标. 任务栏一般位于桌面的下 ...

  7. windows桌面窗口层次解析以及C语言设置壁纸原理应用

    windows桌面层次 首先要知道windows的桌面是由哪个窗口显示的.打开spy++工具.窗口列表翻到最下面有一个 类名是Progman窗口 标题是Program Manager的窗口.这个窗口下 ...

  8. 怎样取消Windows 10的虚拟桌面切换动画和窗口动画

    怎样取消Windows 10的虚拟桌面切换动画和窗口动画 对于晕3D的人来说这是真的要命. 步骤: 在"这台电脑"上点击右键(如bai何在Win10桌面上显示"du这台电 ...

  9. 重新想象 Windows 8 Store Apps (27) - 选取器: 联系人选取窗口, 自定义联系人选取窗口...

    原文:重新想象 Windows 8 Store Apps (27) - 选取器: 联系人选取窗口, 自定义联系人选取窗口 [源码下载] 重新想象 Windows 8 Store Apps (27) - ...

最新文章

  1. 基因组关联分析中的交互作用研究
  2. 深入浅出Android系统启动流程
  3. 艾伟:ASP.NET MVC,深入浅出IModelBinder,在Post方式下慎用HtmlHelper
  4. vue require动态路径图片报错_Vue 动态生成路由结构
  5. .NetCore SkyWalking APM实现服务器监控环境安装及基础使用
  6. 对HTML标记的理解,初学者接触HTML了解一些HTML标记(1)
  7. linux gdb基本调试及其多线程调试
  8. Redis 6.0 源码阅读笔记(3) -- 概述 Redis 重要数据结构及其 6 种数据类型
  9. Atitit.软件开发概念说明--io系统区--特殊文件名称保存最佳实践文件名称编码...filenameEncode 1.1. 不个网页title保存成个个文件的时候儿有无效字符的问题... 1
  10. 微信小程序开发 -- 获取当前页面路径
  11. winhex数据恢复入门教程
  12. window.open 服务器运行失败,win10 openssh服务器安装失败的最佳解决方法
  13. excel加密文档破解
  14. Beyond Compare 4 远程对比
  15. 什么是优化问题 Optimization Problem
  16. 【爬虫】应用Python爬虫爬取豆瓣租房上的帖子信息
  17. 让逆向工程师们头疼的代码混淆,就像永远也走不出的“浪浪山”
  18. Spring Boot将端口8080重定向到8443
  19. 2021高级消防设施操作员模拟真题集及答案解析
  20. 本科、硕士、博士的区别(终极版)

热门文章

  1. compose安装pgAdmin+postgis
  2. 创意前沿:30个别具创意的科技广告欣赏
  3. 重磅发布 |《2023年消费品企业数字化转型调研白皮书》
  4. java读书心得_java夜未眠的读书心得
  5. 将SQL2000数据库升级到SQL2005
  6. 5-35 城市间紧急救援 (25分)
  7. 不要迷恋哥,嫂子会揍你
  8. 机器视觉工业相机和镜头选型
  9. 使用C++调用Python模块(Linux)
  10. 人机猜拳小游戏(类和对象)