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

时间:2010-07-06 17:05:58

标签: java windows cross-platform

(通过所有意思重新标记相关技术:我不知道它们是哪一个:)

我可能会在稍后提出更详细的问题,关于具体的细节但是现在我正试图抓住“大局”:我正在寻找一种在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)

然后,如果用户(或OS)将窗口A带到前面,它将变为:

 +------------------------------------------+
 |                                          |
 |           +=============+                |
 |           |             |                |
 |           |    A        |---------------------+
 |           |             |                     |
 |    C      |             |        B            |
 |           |             |---------------------+
 |           |             |                |
 +-----------|             |----------------+
             |             |
             +-------------+

我得到(理想情况下)一个回调给我这个:

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

在OS X下执行此操作需要使用非常奇怪的黑客(例如强制用户打开“启用辅助设备的访问权限”!)但我已经在OS X下完成了它工作(在OS X下,每次发生一些窗口更改时我都没有设法回调,所以我正在进行轮询,但是我让它工作了。)

现在我想在Windows下执行此操作(我确实这样做,毫无疑问)并且我有几个问题:

  • 可以这样做吗?

  • 是否有很好的文档Windows API(并根据他们的规范工作)允许这样做?

  • 每次窗口更改时都可以轻松注册回调吗? (如果它被调整大小,移动,带到后面/前面或者如果弹出一个新窗口等等)

  • 会有什么问题?

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

奖金问题:想象一下,每当屏幕上有一个窗口更改时,你需要写一个小的 .exe 将windows名称/位置/大小写入一个临时文件,这样的程序需要多长时间大约用你选择的语言,你需要多长时间来写它?

(再一次,我想要了解“大局”,了解这里的工作原理)

3 个答案:

答案 0 :(得分:27)

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

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

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

请注意,使用挂钩时,不能混用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。

编辑:你问了它的代码是多少以及写了多长时间。这是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顺序的代码。它确实使用GetNextWindow。在生产应用程序中,您可能应该为下一个和上一个值调用GetNextWindow两次,并检查它们是否一致并且是有效的窗口句柄。

答案 1 :(得分:7)

  

可以这样做吗?

是的,虽然您必须注册hook才能获得有关回调的内容。您可能需要使用CBTProc Callback Hook,只要在以下情况下调用它:

  

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

但请注意,我不相信此类挂钩在控制台窗口上有效,因为它们是内核的域,而不是Win32。

  

是否存在记录良好的Windows API(并根据其规范工作)允许这样做?

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

  

每次窗口更改时都可以轻松注册回调吗? (如果它被调整大小,移动,带到后面/前面或者如果弹出一个新窗口等)。

见第一个答案:)

  

会有什么问题?

见第一个答案:)

  

奖金问题:想象一下,每当屏幕上有一个窗口更改时,你需要写一个小的.exe将windows名称/位置/大小写入一个临时文件,这样的程序大约会用你选择的语言多长时间你需要多长时间才能写出来?

几百行C,几个小时。虽然我不得不使用某种形式的民意调查 - 我以前从未做过勾手。如果我需要钩子,它需要更长的时间。

答案 2 :(得分:1)

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

从那时起,他的公司被微软收购,因此我不知道该来源是否仍然可用。

以下值得检查:

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

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