【基于Win10】桌面窗口层次记录(Spy++)

PART1  桌面窗口层次(Z-Order 文本文档)

未开启Aero


--------------------------------------------
Z-Order(Z 层次顺序):
Bottom-DesktopWindow:"Program Manager"Program
<-
""WorkerW(""SHELLDLL_DefView("FolderView"SysListView32))
"Program Manager"Program(""WorkerW)
->
--------------------------------------------

以下是开启Aero配色方案后(Win10)

桌面窗口的样式信息,嵌入窗口时要用到
WorkerW 1(""WorkerW)
brush.blackground: COLOR_3DFACE
STYLE:96000000
WS_POPUP
WS_VISIBLE
WS_CLIPSIBLINGS
WS_CLIPCHILDREN
EXSTYLE:00000080
WS_EX_LEFT
WS_EX_LTRREADING
WS_EX_RIGHTSCROLLBAR
WS_EX_TOOLWINDOW
-----------------------------


WorkerW 2(CHILDWINDOW----""WorkerW)
brush.blackground: COLOR_3DFACE
STYLE:9E000000
WS_POPUP
WS_VISIBLE
WS_DISABLED
WS_CLIPSIBLINGS
WS_CLIPCHILDREN
EXSTYLE:080000A0
WS_EX_LEFT
WS_EX_LTRREADING
WS_EX_RIGHTSCROLLBAR
WS_EX_TRANSPARENT
WS_EX_TOOLWINDOW
WS_EX_NOACTIVATE
-----------------------------


""SHELLDLL_DefView
brush.blackground: COLOR_WINDOW
STYLE:56010000
WS_CHILDWINDOW
WS_VISIBLE
WS_CLIPSIBLINGS
WS_CLIPCHILDREN
WS_MAXIMIZEBOX
EXSTYLE:00000000
WS_EX_LEFT
WS_EX_LTRREADING
WS_EX_RIGHTSCROLLBAR
-----------------------------


"FolderView"SysListView32
brush.blackground: COLOR_WINDOW
STYLE:56003B40
WS_CHILDWINDOW
WS_VISIBLE
WS_CLIPSIBLINGS
WS_CLIPCHILDREN
LVS_ICON
LVS_SHAREIMAGELISTS
LVS_AUTOARRANGE
LVS_EDITLABELS
LVS_ALIGNLEFT
LVS_OWNERDATA
LVS_NOSCROLL
EXSTYLE:00000000
WS_EX_LEFT
WS_EX_LTRREADING
WS_EX_RIGHTSCROLLBAR
LVS_EX_HEADERDRAGDROP
LVS_EX_FULLROWSELECT
LVS_EX_INFOTIP
LVS_EX_UNDERLINEHOT
LVS_EX_LABELTIP
LVS_EX_DOUBLEBUFFER
LVS_EX_SNAPTOGRID
LVS_EX_TRANSPARENTSHADOWTEXT
LVS_EX_AUTOSIZECOLUMNS
-----------------------------


"Program Manager"Program
brush.blackground: COLOR_DESKTOP
STYLE:96000000
WS_POPUP
WS_VISIBLE
WS_CLIPSIBLINGS
WS_CLIPCHILDREN
EXSTYLE:00000080
WS_EX_LEFT
WS_EX_LTRREADING
WS_EX_RIGHTSCROLLBAR
WS_EX_TOOLWINDOW

PART2   VC中设置程序窗的排列层级

1. Topmost 窗⼝和 Non-Topmost 窗⼝


Windows 中的应用程序窗口,可以按照显示效果分为 Topmost 和 Non-Topmost 两类。Topmost 类型的窗口,显示时位于 Non-Topmost 类型窗口的上方。当 Topmost 类型窗口不是活动窗口时,它失去了输入焦点,但是依然会位于 Non-Topmost 类型窗口之上。

下面举一个例子,假定某个应用程序 A 的窗口是 Topmost 类型,启动该程序,再启动 Windows 自带的记事本 Notepad.exe 程序。Notepad.exe 程序的窗口类型是 Non-Topmost,此时屏幕上的显示类似于下图:

由于先启动应用程序A(文件管理器 explorer.exe 被设置置顶),再启动记事本。所以当前活动窗口是记事本,输入焦点也在记事本中。如果我们敲击键盘,将在记事本中输入文字。应用程序A的窗口类型是 Topmost,所以它还是覆盖在记事本之上,有可能遮挡住一部分记事本窗口的内容。

上图显示的情景是存在一个 Topmost 窗口和一个 Non-Topmost 窗口。可能同时存在多个 Topmost 类型的窗口、以及多个 Non-Topmost 类型窗口,那么同一个类型的窗口会按照某种顺序上下排列起来。对于不同类型的窗口来说,在 Topmost 窗口集合中的任何一个窗口,都将位于所有 Non-Topmost 窗口之上。

2. Z-Order(Z坐标轴顺序)
我们可以将计算机屏幕看作一个二维平面,那么可以假想它有水平坐标轴X和垂直坐标轴 Y,如下图所示:

当计算机屏幕上有多个窗⼝时,我们可以将屏幕想象成⼀个拓展的三维空间,即增加⼀个坐标轴Z,如下图所⽰:

当应用程序新建了一个窗口时,操作系统将根据窗口的类型,将它放置到同类型窗口的最上方,即同类型窗口 Z-Order 的最顶层。当用户激活某一个窗口时(比如用鼠标点击该窗口的标题栏操作),该窗口的 Z-Order 也会被改变,操作系统会将该窗口的 Z-Order 提升到同类型窗口的最顶层。比如该窗口是一个 Non-Topmost 类型窗口,用户激活它之后,它将位于 Non-Topmost 类型窗口集的最上层,但仍然位于所有 Tompost 窗口之下。当一个程序窗口被操作系统调整到 Z-Order 的最顶层时,它的子窗口也会被放到最顶层。

3. VC中设置窗口排列层级的函数
VC中可以改变窗口排列层级的函数很多,最主要的⼀个函数是 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
);

第1个参数HWND hWnd代表窗⼝的句柄,第2个参数hWndInsertAfter⽤来指定窗⼝将被调整到上下层中的哪⼀个层次中去,它有以下四种宏定义的取值:

HWND_BOTTOM——将该窗口放到 Z-Order 排列顺序的最底层。如果这个窗口是一个 Topmost 窗口,使用 HWND_BOTTOM 参数调用 SetWindowPos 函数之后,这个窗口将不再是 Topmost 类型,并被放到所有其他窗口之下,即如果此时还存在 Non-Topmost 类型的其他窗口,哪怕当前窗口是一个 Topmost 类型窗口,它也将被放到其他 Non-Topmost 类型窗口之下,也就是变为所有窗口中最底层的那⼀个。
HWND_NOTOPMOST——将该窗口放到所有 Non-Topmost 窗口之上、所有 Topmost 窗口之下。即该窗口将被放到 Non-Topmost 类型窗口集与 Topmost 窗口集之间的位置。
HWND_TOP——将该窗口放到同类型窗口中 Z-Order 顶层的位置。注意仅仅是同类型窗口集的最上层。
HWND_TOPMOST——将该窗口放到所有 Non-Topmost 窗口之上,即将该窗口变成一个 Topmost 类型的窗口,而且该窗口在 Topmost 窗口集中也处于最顶层,也就是说,将该窗口的位置放到屏幕Z坐标轴方向的最上层。这时如果再启动一个 Non-Topmost 类型的窗口(比如记事本程序),该窗口就不再是活动窗口,但它依然位于 Non-Topmost 窗口之上。读者可以回想⼀下前⾯举的应用程序A与记事本程序窗口前后排列的例子。

当 SetWindowPos 的第2个参数取值为 HWND_TOP 时,调用该函数与 BringWindowToTop 类似,都只能将窗口放到同类型窗口集中的最顶层。我们不能望文生义,以为 HWND_TOP 或BringWindowToTop 的意思就将窗口放到屏幕最前端。因为如果此时窗口类型是 Non-Topmost,而且存在 Topmost 类型的窗口,调用 BringWindowToTop 或将 SetWindowPos 的第2个参数设为HWND_TOP,其效果只是将该窗口放到了 Non-topmost 窗口集的最上层,但在该窗口之上还覆盖着 Topmost 窗口集。
如果我们希望将应用程序的窗口放到屏幕最前端,在其他所有窗口之上显示,可以调用SetWindowPos 函数并将其第2个参数取值为 HWND_TOPMOST,或者调用SetForegroundWindow 函数。SetForegroundWindow 函数有很多使用限制,详细信息可以查阅MSDN。

当 SetWindowPos 的第2个参数取值为 HWND_TOPMOST 时,调用该函数会将窗口放置于屏幕的最上层。但是并不能保证该窗口会⼀直保持在最上层。因为其他程序也可能会修改Z-order,比如再启动另⼀个程序,那个程序也用同样的方法(调用 SetWindowPos 并把第2个参数取值为HWND_TOPMOST)设置窗口,那么新启动的程序窗口将出现在最上层,即 Topmost 窗口集的最外层,而原来的程序窗口的层次会下调⼀级,即位于 Topmost 窗口集的次外层。

关于 Z-Order 的实现细节其实是有一个 ZOrderManager_Service 的 COM 接口微软没有公开的,也就是直接管理 ZOrder 的细节环节是被保护的。在不知道内部细节的情况下,我们只能使用 SetWindowPos 去间接调整窗口层次,并且有多种消息会将窗口层次改变,即使有时候设置了 SWP_NOZORDER。

我们有两种方法实现将 A 窗口插入到 B 窗口的前面或者后面一个槽位上,一种就是通过设置 HWND_BOTTOM 将窗口移动到 Z 序的底部,通过递归调用不停的将下方的窗口移动次序,实现窗口 Z 序的调整(或者设置 HWND_TOP 反向调整);另外一种就是利用窗口激活的时候 Z 序会移动到前面的方法来调整顺序。具体的细节之后会在新的文章中给出例子,关于桌面窗口的学习已经太久了,有些资料需要斟酌,不一定是正确的。

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

根据 IDA 分析 BingWindowToTop 函数就是指定了 NtUser32SetWindowPos 的第二个参数为 HWND_TOP 来实现的。

我们给出 BingWindowToTop 和 BringWindowToBottom 的定义:

// 移动窗口到同级窗口的 Z 序的顶部,直到下一个窗口被激活到顶部
BOOL WINAPI MyBringWindowToTop(HWND hWnd)
{
    return SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}

// 移动窗口到同级窗口的 Z 序的低部,直到下一个窗口被移动到低部
BOOL WINAPI MyBringWindowToBottom(HWND hWnd)
{
    return SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}

注意这里的同级窗口才能移动,如果不同级是会失败的。

当窗口层次发生变化的时候一般会首先收到 WM_WINDOWPOSCHANGING 消息,按照道理我们窗口需要在消息回调中处理该消息,并调用 BringWindowToXX 函数移动窗口,确保窗口层次不发生变化,这比 NOZORDER 标识更为有效。

但是我们也发现 W+D 或者显示桌面按钮按下时,窗口会先发生变化(具体机制还未研究),然后再收到 WM_WINDOWPOSCHANGING 消息,所以只处理该消息没有用,全局(系统的)窗口层次的调整不是在窗口回调中完成的,而是线程级别的,我们需要独立一个线程去检查窗口层次,并恢复窗口层次,比如:

while (true) // 不要用死循环
{
    // 判断我们的窗口是否在 SHELLDLL_DefView 窗口的下方,
    // 如果不是则调整窗口层次。
    if (GetWindow(hNotepad, GW_HWNDNEXT) == hDefView)
    {
        ShowWindow(hNotepad, SW_SHOWMAXIMIZED); // 保证最大化显示
        MyBringWindowToBottom(hNotepad);
    }
                 
    Sleep(0);//  改成 WaitForSingleObject 等待窗口句柄
}

这一篇用于记录学习过程中发现的一些东西,没怎么排版。

更新于:2023.10.19

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spy4Win v0.20b =================================== Spy for Window(探测更多的窗口信息) ● 软件简介:   Spy4Win(Spy for Window)是一个类似MS Spy++的辅助工具,主要功能是探测和获取窗口的更多信息(窗口基本信息,窗口的样式描述以及动态改变窗口样式,识别控件来源, 窗口类的相关信息,关系窗口的获取(父窗口/子窗口等),窗口内容的读取,窗口消息截获,可视窗口截图等)。提供了多种方法查找窗口(拖拽鼠标/枚举窗口(EnumWindow)/查找窗口(FindWindow)/直接指定窗口句柄/用热键获取当前鼠标下窗口等);可以从可执行文件中提取窗体可重用单元并生成Delphi和C++Builder支持的单元文件;克隆其他程序中的窗体菜单或系统菜单并应用到Delphi和C++Builder中;IE页面分析功能包括读取IE页面元素,缩放页面,高亮页面中的关键字,提取所有链接/图片链接/Flash链接,运行JavaScript和VBScript脚本等;程序代码生成可直接生成查找窗口和读取/设置窗口样式的代码以及窗口样式描述的参考,目前支持C++/Delphi/VB;屏幕颜色拾取获取屏幕任何一点的颜色并可将其储存起来,一组支持6个颜色,还兼有放大镜功能;进程管理包括当前进程/模块/线程查看;软件界面可根据用户的爱好自定义主题色彩,新版本中提供了对插件的支持,可以通过编写Dll来扩充软件功能。 ● 程序制作:   by ccrun(老妖),用C++Builder 6.0编写,未使用第三方控件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涟幽516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值