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

Noo*_*z42 43 java windows cross-platform

(通过所有意味着重新标记相关技术:我不知道他们是谁:)

我可能会在稍后提出更详细的问题,关于具体的细节但是现在我正试图抓住"大局":我正在寻找一种在Windows上枚举"真实可见窗口"的方法.通过"真实可见窗口"我的意思是:用户称之为"窗口".我需要一种方法来获取所有这些可见窗口的列表,按Z顺序.

请注意,我真的需要做到这一点.我已经在OS X上完成了它(这是一个非常令人头痛的问题,特别是如果你想支持OS X 10.4,因为OS X没有方便的Windows API),现在我需要在Windows下完成它.

这是一个例子,假设屏幕上有三个可见窗口,如下所示:

 +------------------------------------------+
 |                                          |
 |           +=============+                |
 |           |             |                |
 |           |    A   +--------------------------+
 |           |        |                          |
 |    C      |        |             B            |
 |           |        +--------------------------+
 |           |             |                |
 +-----------|             |----------------+
             |             |
             +-------------+
Run Code Online (Sandbox Code Playgroud)

然后我需要找回这样的列表:

 windows B is at (210,40)
 windows A is at (120,20)
 windows C is at (0,0)
Run Code Online (Sandbox Code Playgroud)

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

 +------------------------------------------+
 |                                          |
 |           +=============+                |
 |           |             |                |
 |           |    A        |---------------------+
 |           |             |                     |
 |    C      |             |        B            |
 |           |             |---------------------+
 |           |             |                |
 +-----------|             |----------------+
             |             |
             +-------------+
Run Code Online (Sandbox Code Playgroud)

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

windows A is at (120,20)
windows B is at (210,40)
windows C is at (0,0)
Run Code Online (Sandbox Code Playgroud)

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

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

  • 可以这样做吗?

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

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

  • 会有什么问题?

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

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

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

mdm*_*dma 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经创建了大多数相关的类和接口内部类,以保持示例紧凑和可粘贴以便立即编译.在实际的实现中,它们将是常规的顶级类.命令行应用程序打印出可见窗口及其位置.我在32位jvm和64位上运行它,并且每个都得到了相同的结果.

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


Bil*_*eal 7

可以这样做吗?

是的,虽然您必须注册一个钩子才能获得关于回调的所需内容.您可能需要使用CBTProc回调挂钩,只要以下情况调用它:

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

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

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

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

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

看第一个答案:)

会有什么问题?

看第一个答案:)

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

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