【问题标题】:Windows: how to get a list of all visible windows?Windows:如何获取所有可见窗口的列表?
【发布时间】:2010-07-06 17:05:57
【问题描述】:

(一定要用相关技术重新标记:我不知道它们是哪些:)

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

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

  • 这可以做到吗?

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

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

  • 问题是什么?

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

额外问题:假设您需要编写一个很小的 ​​.exe 将窗口名称/位置/大小写入临时文件,每次屏幕上出现窗口变化时,这样的程序需要多长时间大致使用您选择的语言,您需要写多长时间?

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

【问题讨论】:

  • 没人吗?已经 +1 点赞和 1 个最爱... :)
  • 好吧,我猜你会从 FindWindowEx 开始以 Z 顺序枚举所有窗口,然后使用 GetWindowLong 来获取窗口样式。显然你只会考虑带有 WS_VISIBLE 的窗口,也许还有 WS_CAPTION 之类的。
  • .net 框架是一种选择吗?我有一个简单的解决方案,大约需要十秒钟。
  • @Roddy:谢谢...实际上是因为我有点笨:我不知道如何在 SO 上发布图片(我不知道我应该在哪里“放“图片:我没有服务器/网页,我不知道我是否可以直接向SO“发送”图片)。所以,没有图片:让我们去看看一些 ASCII 艺术 :)
  • @Cyclone:.net 不是一个真正的选择:这是适用于 OS X 和 Windows 的东西(它是用 Java 编写的)。我有大约 99% 的 Java 代码和 1% 的平台特定代码(比如在 OS X 上找到窗口位置的代码,它已经在工作了)。但我仍然很好奇:您使用 .net 的解决方案是什么?也许你可以在这里发布它作为答案,指定它是.net?我肯定会赞成的!

标签: java windows cross-platform


【解决方案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 位上运行它,并且得到了相同的结果。

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

【讨论】:

  • 只是为了确定,如果我只是轮询,那么我可以使用 EnumWindows 并且没有 32 位/64 位问题? JNA 是计划(他们有一个示例枚举 Windows 上的窗口顺便说一句,我只需要找到它)但首先我想知道“幕后”涉及什么。我仍然不知道我自己是否有能力做到这一点(我从未做过任何 Windows 编程),但所有这些答案都极大地帮助我理解了所涉及的内容!
  • @NoozNooz42 - 没错,轮询避免了使用钩子和 32/64 位问题。这是一篇从 JNA 调用 EnumWindows 的文章。 javajeff.mb.ca/cgi-bin/mp.cgi?/java/javase/articles/mjnae
  • @mdna:EnumWindows 的问题在于它不按 Z 顺序返回窗口。 (或者至少不是根据它的文档)
  • 非常感谢代码...我给大家+1,我给你代码示例的赏金和你帮助我的时间!这很棒。稍后我可能会在 JNA 上遇到问题并提供更详细的答案,但至少我有一些东西可以让我开始!万分感谢!
  • 嗨,我希望代码对您有所帮助。它不完整,但它应该给你一个好的开始。看起来其他答案之一得到了赏金。
【解决方案2】:

这个可以吗?

是的,尽管您必须注册 hook 才能获得您想要的回调。您可能需要使用CBTProc Callback Hook,它在任何时候都会被调用:

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

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

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

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

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

查看第一个答案:)

问题是什么?

查看第一个答案:)

额外问题:假设每次屏幕上出现窗口变化时,您都需要编写一个很小的 ​​.exe,将窗口名称/位置/大小写入临时文件,这样的程序在您选择的语言中大约需要多长时间你需要写多久?

几百行 C 语言,几个小时。虽然我不得不使用某种形式的轮询——我之前从未做过钩子。如果我需要这些钩子,则需要更长的时间。

【讨论】:

  • 非常感谢您的回答...因此,如果我理解正确,您将进行轮询(这就是我在 OS X 下所做的),然后没有钩子,也没有回调 OR 你会使用钩子,允许回调吗? (再一次,在 OS X AFAICT 中,没有简单的方法可以使用回调来做到这一点,这就是我要轮询的原因)。
  • @Nooz:钩子回调。
  • @Nooz:换句话说,是的。您可以进行轮询,也可以将钩子用作回调。钩子更好,但我只是估计需要的时间,因为我以前从未使用过钩子。
  • Hook 是唯一无需汇集就可以得到通知的机制(请参阅我的问题stackoverflow.com/questions/3122215/…)。它实际上需要一些努力才能让它工作,但是一旦你这样做了,你就会收到有关 Windows 事件的通知。
【解决方案3】:

我记得早在 2006 年就有一个实用程序 WinObj 作为 sysinternals 的一部分,它可能会满足您的需求。部分实用程序由作者 (Mark Russinovich) 提供了源代码。

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

以下可能值得检查:

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

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

【讨论】:

    猜你喜欢
    • 2021-02-25
    • 2012-03-26
    • 2011-05-10
    • 2014-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多