代码之家  ›  专栏  ›  技术社区  ›  NoozNooz42

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

  •  43
  • NoozNooz42  · 技术社区  · 16 年前

    (无论如何,一定要用相关的技术重新标记:我不知道它们是哪一种。)

    稍后我可能会提出更详细的问题,关于具体细节,但目前我正试图把握“大局”:我正在寻找一种方法来枚举窗口上的“真正可见的窗口”。“真正可见的窗口”我的意思是:用户称之为“窗口”。我需要一种方法来获得所有这些可见窗口的列表,按Z顺序排列。

    注意我 真的需要这么做。我已经在OSX上完成了这项工作(在这里,这是一个真正令人头疼的工作,特别是如果你想支持OSX 10.4,因为OSX没有方便的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)
    

    在OSX下执行此操作需要使用非常奇怪的黑客(比如强制用户打开 “启用辅助设备的访问” !)但我已经在OSX下完成了这项工作(在OSX下,每次发生一些窗口更改时,我都无法获得回调,所以我正在进行轮询,但我成功了)。

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

    • 能做到吗?

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

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

    • 会是什么?

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

    额外的问题:假设你需要写一个小的 exe 每当屏幕上出现窗口更改时,将Windows名称/位置/大小写入临时文件,这样的程序大约用您选择的语言编写多长时间,您需要编写多长时间?

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

    3 回复  |  直到 8 年前
        1
  •  26
  •   Adowrath Veebs    8 年前

    要枚举顶级窗口,应使用 EnumWindows 而不是getTopWindow/getNextWindow,因为EnumWindows返回窗口状态的一致视图。当Windows在迭代过程中更改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位JVM上运行它,得到了相同的结果。

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

        2
  •  7
  •   Billy ONeal IS4    16 年前

    能做到吗?

    是的,不过你得注册一个 hook 为了得到你想要的关于回调的信息。您可能需要使用 CBTProc Callback Hook ,在以下情况下调用:

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

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

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

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

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

    见第一个答案:)

    会是什么?

    见第一个答案:)

    额外的问题:假设你需要写一个很小的.exe来写一个临时文件,每当屏幕上有一个窗口更改时,这个程序大约用你选择的语言写多长时间,你需要写多长时间?

    几百行C,还有几个小时。尽管我必须使用某种形式的投票——但我以前从未做过钩子。如果我需要钩子的话,需要更长的时间。

        3
  •  1
  •   Miro A.    16 年前

    我记得2006年有一个实用程序winobj,作为系统内部构件的一部分,它可能满足了您的需要。部分公用设施由作者(MarkRussinovich)提供源代码。

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

    此外,以下内容可能值得检查:

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

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