Windows:如何获得所有可见窗口的列表?

(所有的意思是重新标记相关的技术:我不知道他们是哪个:)

我稍后可能会提出更详细的问题,关于具体的细节,但现在我正在努力把握“大局”:我正在寻找一种方法来枚举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 | | | |---------------------+ | | | | +-----------| |----------------+ | | +-------------+ 

我得到(理想情况下)给我这个callback:

 windows A is at (120,20) windows B is at (210,40) windows C is at (0,0) 

在OS X下这样做需要使用奇怪的黑客(例如强制用户打开“Enable Access for Assistive Device” ),但是我已经在OS X下完成了这个工作(在OS X下,我没有每次发生一些窗口更改时都设法callback,所以我正在轮询,但是我得到了它的工作)。

现在我想在Windows下这样做(我真的这样做,毫无疑问),我有几个问题:

  • 这可以做到吗?

  • 是否有良好的loggingWindows API(并按照他们的规格工作)允许这样做?

  • 每当一个窗口改变时,是否容易注册callback? (如果resize,移动,回到前面或者popup一个新窗口等等)

  • 陷阱是什么?

我知道这个问题并不具体,这就是为什么我试图尽可能清楚地描述自己的问题(包括可以使用的漂亮的ASCII艺术):现在我正在看“大图”。 我想知道在Windows下做什么这样的事情。

奖金的问题:想象一下,每当屏幕上出现一个窗口变化时,您需要写一个小小的.exe文件,将窗口名称/位置/大小写入临时文件,这样的程序大约需要多长时间才能用您的语言select,你需要写吗?

(再一次,我试图弄清楚这里的“大局”)

要枚举顶层窗口,应该使用EnumWindows而不是GetTopWindow / GetNextWindow,因为EnumWindows返回窗口状态的一致视图。 当窗口在迭代期间改变z顺序时,您可能会使用GetTopWindow / GetNextWindow获取不一致的信息(例如报告删除的窗口)或无限循环。

EnumWindows使用callback。 在callback的每次调用中,您都会看到一个窗口句柄。 窗口的屏幕坐标可以通过将该句柄传递给GetWindowRect来获取。 您的callback按z顺序构build窗口位置列表。

您可以使用轮询,并重复build立窗口列表。 或者,你设置一个CBTHook来接收窗口变化的通知。 不是所有的CBT通知都会导致顶层窗口的顺序,位置或可见性发生变化,所以重新运行EnmWindows以z顺序构build窗口位置的新列表,并在进一步处理列表之前将其与之前的列表进行比较是明智的,所以只有在发生真正的变化时才能进行进一步的处理。

请注意,挂钩时,不能混合使用32位和64位。 如果您运行的是32位应用程序,那么您将收到来自32位进程的通知。 同样适用于64位。 因此,如果要在64位机器上监视整个系统,似乎需要运行两个应用程序。 我的推理来自这个:

SetWindowsHookEx可以用来将DLL注入到另一个进程中。 一个32位的DLL不能被注入一个64位的进程,并且一个64位的DLL不能被注入一个32位的进程。 如果应用程序需要在其他进程中使用钩子,则需要32位应用程序调用SetWindowsHookEx将32位DLL注入到32位进程中,而64位应用程序调用SetWindowsHookEx来注入64位DLL转换成64位进程。 32位和64位DLL必须具有不同的名称。 (从SetWindowsHookEx api页面。)

正如你在Java中实现这一点,你可能想看看JNA – 它使得本地库的写入访问变得更简单(在java中调用代码),并且不需要你自己的本地JNI DLL。

编辑:你问了多less代码,并写了多长时间。 这是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) { // 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); 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); final int GW_HWNDNEXT = 2; } public static class RECT extends Structure { public int left,top,right,bottom; } public static class WindowInfo { int hwnd; RECT rect; 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位上运行它,并得到相同的结果。

编辑2:更新的代码包含z顺序。 它确实使用GetNextWindow。 在生产应用程序中,您应该为下一个和上一个值调用GetNextWindow两次,并检查它们是否一致,并且是有效的窗口句柄。

这可以做到吗?

是的,虽然你必须注册一个钩子才能得到你想要的callback。 您可能需要使用一个CBTProccallback挂钩 ,这是每当调用:

激活,创build,销毁,最小化,最大化,移动或调整窗口的大小; 在完成系统命令之前; 在从系统消息队列中删除鼠标或键盘事件之前; 在设置键盘焦点之前; 或者在与系统消息队列同步之前

请注意,我不相信这样的挂钩在控制台窗口上工作,因为它们是内核的域,而不是Win32。

是否有良好的loggingWindows API(并按照他们的规格工作)允许这样做?

是。 您可以使用GetTopWindow和GetNextWindow函数以正确的Z顺序获取桌面上的所有窗口句柄。

每当一个窗口改变时,是否容易注册callback? (如果resize,移动,回到前面或者popup一个新窗口等等)

看到第一个答案:)

陷阱是什么?

看到第一个答案:)

奖金的问题:想象一下,每当屏幕上出现一个窗口变化时,您需要写一个小小的.exe文件,将窗口名称/位置/大小写入临时文件,这样的程序大约需要多长时间才能用您的语言select,你需要写吗?

几百行C,几个小时。 虽然我不得不使用某种forms的投票 – 我从来没有在自己之前做过钩。 如果我需要钩子,需要更长的时间。

我记得在2006年,有一个实用程序WinObj作为sysinternals的一部分,可能做你想做的。 部分这些工具是由作者(Mark Russinovich)提供的源代码。

从那时起,他的公司就被微软收购了,所以我不知道这个消息源是否还可用。

以下可能值得检查:

http://msdn.microsoft.com/en-us/library/aa264396(VS.60).aspx

http://www.codeproject.com/KB/dialog/windowfinder.aspx