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

2022-08-31 22:35:05

(无论如何,请使用相关技术重新标记:我不知道它们:)

我可能会在稍后提出更详细的问题,关于具体细节,但现在我试图掌握“大局”:我正在寻找一种方法来枚举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下执行此操作需要使用非常奇怪的黑客(例如强制用户打开“为辅助设备启用访问权限”!)但是我已经在OS X下完成了此操作并且它的工作原理(在OS X下,我没有设法在每次发生某些窗口更改时都获得回调,所以我正在轮询,但我让它工作)。

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

  • 这可以做到吗?

  • 是否有记录良好的Windows API(并按照其规范工作)允许这样做?

  • 每次窗口更改时注册回调是否容易?(如果它被调整大小,移动,带到后面/前面,或者弹出一个新窗口等。

  • 会有什么陷阱?

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

额外的问题:想象一下,每次屏幕上有窗口更改时,您都需要编写一个小.exe将窗口名称/位置/大小写入临时文件,这样的程序大约用您选择的语言编写多长时间,您需要多长时间才能编写它?

(再一次,我试图让“大局”理解这里的工作原理)


答案 1

要枚举顶级窗口,您应该使用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位上运行它,并且每个都得到了相同的结果。

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


答案 2

这可以做到吗?

是的,尽管您必须注册一个钩子才能获得有关回调的内容。您可能需要使用 CBTProc 回调钩子,每当执行以下操作时都会调用它:

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

但请注意,我不相信这样的钩子在控制台窗口中有效,因为它们是内核的域,而不是Win32。

是否有记录良好的Windows API(并按照其规范工作)允许这样做?

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

每次窗口更改时注册回调是否容易?(如果它被调整大小,移动,带到后面/前面,或者弹出一个新窗口等。

查看第一个答案:)

会有什么陷阱?

查看第一个答案:)

额外的问题:想象一下,每次屏幕上有窗口更改时,您都需要编写一个小.exe将窗口名称/位置/大小写入临时文件,这样的程序大约用您选择的语言编写多长时间,您需要多长时间才能编写它?

几百行C,几个小时。虽然我必须使用某种形式的轮询 - 但我从未在自己之前做过钩子。如果我需要钩子,那需要更长的时间。


推荐