问题描述
(无论如何,请使用相关技术重新标记:我不知道它们是哪些:)
稍后我可能会提出更详细的问题,关于具体细节,但现在我试图掌握“大局”:我正在寻找一种方法来枚举 Windows 上的“真实可见窗口”。 “真正可见的窗口”我的意思是:用户会称之为“窗口”。 我需要一种方法来以 Z 顺序获取所有这些可见窗口的列表。
请注意,我真的需要做到这一点。 我已经在 OS X 上完成了(这真的很头疼,特别是如果你想支持 OS X 10.4,因为 OS X 没有方便的 Windows API),现在我需要在 Windows 下完成。
这是一个示例,假设屏幕上有三个可见窗口,如下所示:
+------------------------------------------+
| |
| +=============+ |
| | | |
| | A +--------------------------+
| | | |
| C | | B |
| | +--------------------------+
| | | |
+-----------| |----------------+
| |
+-------------+
然后我需要取回这样的列表:
windows B is at (210,40)
windows A is at (120,20)
windows C is at (0,0)
然后,如果用户(或操作系统)将窗口 A 放在前面,它将变为:
+------------------------------------------+
| |
| +=============+ |
| | | |
| | A |---------------------+
| | | |
| C | | B |
| | |---------------------+
| | | |
+-----------| |----------------+
| |
+-------------+
我得到(理想情况下)一个回调给我这个:
windows A is at (120,20)
windows B is at (210,40)
windows C is at (0,0)
在 OS X 下执行此操作需要使用非常奇怪的 hack(例如强制用户打开“启用辅助设备的访问” !)每次发生某些窗口更改时都会设法获得回调,所以我正在轮询,但我让它工作了)。
现在我想在 Windows 下执行此操作(我真的这样做,毫无疑问)并且我有几个问题:
可以这样做吗?
是否有记录良好的 Windows API(并按照他们的规范工作)允许这样做?
每次窗口更改时都容易注册回调吗? (如果它被调整大小、移动、移到后面/前面或者如果一个新窗口弹出等)
问题是什么?
我知道这个问题并不具体,这就是为什么我试图尽可能清楚地描述我的问题(包括您可以为此投票的漂亮 ASCII 艺术):现在我正在研究“大局”。 我想知道在Windows下做这样的事情涉及什么。
额外问题:想象一下,每次屏幕上有窗口更改时,您都需要编写一个很小的.exe将窗口名称/位置/大小写入临时文件,这样的程序在您选择的语言中大约需要多长时间以及多长时间你需要写吗?
(再一次,我试图获得“大局”以了解这里的工作原理)
1楼
要枚举顶级窗口,您应该使用而不是 GetTopWindow/GetNextWindow,因为 EnumWindows 返回窗口状态的一致视图。 当窗口在迭代期间更改 z 顺序时,您可能会使用 GetTopWindow/GetNextWindow 获得不一致的信息(例如报告已删除的窗口)或无限循环。
EnumWindows 使用回调。 在每次调用回调时,您都会获得一个窗口句柄。 可以通过将该句柄传递给来获取窗口的屏幕坐标。 您的回调以 z 顺序构建窗口位置列表。
您可以使用轮询,并重复构建窗口列表。 或者,您设置 CBTHook 以接收窗口更改的通知。 并非所有 CBT 通知都会导致顶级窗口的顺序、位置或可见性发生更改,因此明智的做法是重新运行 EnmWindows 以按 z 顺序构建新的窗口位置列表,并在进一步处理列表之前将其与之前的列表进行比较,这样只有在发生真正的变化时才进行进一步的处理。
请注意,使用挂钩,您不能混合使用 32 位和 64 位。 如果您运行的是 32 位应用程序,那么您将收到来自 32 位进程的通知。 64 位也类似。 因此,如果您想在 64 位机器上监控整个系统,似乎有必要运行两个应用程序。 我的推理来自阅读这个:
SetWindowsHookEx 可用于将 DLL 注入另一个进程。 32位DLL不能注入64位进程,64位DLL不能注入32位进程。 如果应用程序需要在其他进程中使用钩子,则要求32位应用程序调用SetWindowsHookEx向32位进程注入32位DLL,64位应用程序调用SetWindowsHookEx注入64位DLL DLL 转换为 64 位进程。 32 位和 64 位 DLL 必须具有不同的名称。 (来自 SetWindowsHookEx api 页面。)
当您在 Java 中实现此功能时,您可能需要查看 - 它使对本机库的写入访问变得更加简单(在 Java 中调用代码)并且不需要您自己的本机 JNI DLL。
编辑:你问它有多少代码以及编写多长时间。 这是java中的代码
import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.win32.StdCallLibrary;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
Main m = new Main();
final List<WindowInfo> inflList = new ArrayList<WindowInfo>();
final List<Integer> order = new ArrayList<Integer>();
int top = User32.instance.GetTopWindow(0);
while (top != 0) {
order.add(top);
top = User32.instance.GetWindow(top, User32.GW_HWNDNEXT);
}
User32.instance.EnumWindows(new WndEnumProc() {
public boolean callback(int hWnd, int lParam) {
if (User32.instance.IsWindowVisible(hWnd)) {
RECT r = new RECT();
User32.instance.GetWindowRect(hWnd, r);
if (r.left > -32000) { // If it's not minimized
byte[] buffer = new byte[1024];
User32.instance.GetWindowTextA(hWnd, buffer, buffer.length);
String title = Native.toString(buffer);
inflList.add(new WindowInfo(hWnd, r, title));
}
}
return true;
}
}, 0);
Collections.sort(inflList, new Comparator<WindowInfo>() {
public int compare(WindowInfo o1, WindowInfo o2) {
return order.indexOf(o1.hwnd)-order.indexOf(o2.hwnd);
}
});
for (WindowInfo w : inflList) {
System.out.println(w);
}
}
public static interface WndEnumProc extends StdCallLibrary.StdCallCallback {
boolean callback(int hWnd, int lParam);
}
public static interface User32 extends StdCallLibrary {
final User32 instance = (User32) Native.loadLibrary ("user32", User32.class);
final int GW_HWNDNEXT = 2;
boolean EnumWindows(WndEnumProc wndenumproc, int lParam);
boolean IsWindowVisible(int hWnd);
int GetWindowRect(int hWnd, RECT r);
void GetWindowTextA(int hWnd, byte[] buffer, int buflen);
int GetTopWindow(int hWnd);
int GetWindow(int hWnd, int flag);
}
public static class RECT extends Structure {
public int left, top, right, bottom;
}
public static class WindowInfo {
public final int hwnd;
public final RECT rect;
public final String title;
public WindowInfo(int hwnd, RECT rect, String title) {
this.hwnd = hwnd;
this.rect = rect;
this.title = title;
}
public String toString() {
return String.format("(%d,%d)-(%d,%d) : \"%s\"",
rect.left, rect.top,
rect.right, rect.bottom,
title);
}
}
}
我已经制作了大部分相关的类和接口内部类,以保持示例的紧凑和可粘贴以便立即编译。 在实际实现中,它们将是常规的顶级类。 命令行应用程序打印出可见窗口及其位置。 我在 32 位 jvm 和 64 位上运行它,并得到相同的结果。
EDIT2:更新代码以包含 z-order。 它确实使用了 GetNextWindow。 在生产应用程序中,您可能应该为下一个和上一个值调用两次 GetNextWindow 并检查它们是否一致并且是有效的窗口句柄。
2楼
可以这样做吗?
是的,虽然你必须注册一个 才能获得你想要的回调。 您可能需要使用 ,它在以下任何时候被调用:
激活、创建、销毁、最小化、最大化、移动或调整窗口大小; 在完成系统命令之前; 在从系统消息队列中删除鼠标或键盘事件之前; 在设置键盘焦点之前; 或在与系统消息队列同步之前
但是请注意,我不相信这样的钩子在控制台窗口上工作,因为它们是内核的域,而不是 Win32。
是否有记录良好的 Windows API(并按照他们的规范工作)允许这样做?
是的。 您可以使用和函数以正确的 Z 顺序获取桌面上的所有窗口句柄。
每次窗口更改时都容易注册回调吗? (如果它被调整大小、移动、移到后面/前面或者如果一个新窗口弹出等)
见第一个答案:)
问题是什么?
见第一个答案:)
额外问题:想象一下,每次屏幕上有窗口更改时,您都需要编写一个很小的 ??.exe 将窗口名称/位置/大小写入临时文件,这样的程序在您选择的语言中大约需要多长时间以及多长时间你需要写吗?
几百行 C 和几个小时。 尽管我必须使用某种形式的轮询——我以前从未做过钩子。 如果我需要挂钩,则需要更长的时间。
3楼
我记得早在 2006 年就有一个实用程序 WinObj 作为 sysinternals 的一部分,它可能会做你想做的事。 这些实用程序的一部分由作者 (Mark Russinovich) 提供了源代码。
从那以后,他的公司被微软收购了,所以我不知道源是否仍然可用。
此外,以下内容可能值得检查: