(by all mean do re-tag with the relevant technology: I don't know which ones they are :)
(也就是说,一定要用相关的技术重新标记:我不知道它们是什么:)
I'll probably come later with more detailed questions, about specific details but for now I'm trying to grasp the "big picture": I'm looking for a way to enumerate "real visible windows" on Windows. By "real visible window" I mean just that: what a user would call a "window". I need a way to get a list of all these visible windows, in Z-order.
我稍后可能会提出更详细的问题,关于具体细节,但目前我正试图把握“大局”:我正在寻找一种方法来列举windows上“真实可见的窗口”。所谓“真正可见的窗口”,我的意思是:用户所谓的“窗口”。我需要一种方法来获取所有这些可视窗口的列表,以z顺序。
Note that I do really need to do that. I've already done it on OS X (where it is a real headache to do, especially if you want to support OS X 10.4, because OS X doesn't have convenient windows API) and now I need to do it under Windows.
注意,我确实需要这么做。我已经在OS X上做过了(这是一件令人头疼的事情,尤其是如果你想支持OS X 10.4,因为OS X没有方便的windows API),现在我需要在windows下做。
Here's an example, suppose there are three visible windows on the screen, like this:
这里有一个例子,假设屏幕上有三个可见窗口,如下所示:
+------------------------------------------+
| |
| +=============+ |
| | | |
| | A +--------------------------+
| | | |
| C | | B |
| | +--------------------------+
| | | |
+-----------| |----------------+
| |
+-------------+
Then I need to get back a list like this:
然后我需要得到这样一个列表:
windows B is at (210,40)
windows A is at (120,20)
windows C is at (0,0)
Then if the user (or the OS) brings the window A to the front, it becomes:
然后,如果用户(或操作系统)将窗口A带到前面,它就变成:
+------------------------------------------+
| |
| +=============+ |
| | | |
| | A |---------------------+
| | | |
| C | | B |
| | |---------------------+
| | | |
+-----------| |----------------+
| |
+-------------+
And I get (ideally) a callback giving me this:
我得到(理想的)一个回叫给我:
windows A is at (120,20)
windows B is at (210,40)
windows C is at (0,0)
Doing this under OS X requires the use of amazingly weird hacks (like mandating the user to turn on "Enable Access for assistive device"!) but I've done it under OS X and it works (under OS X, I didn't manage to get a callback everytime some window changes occurs, so I'm polling, but I got it to work).
这样做在OS X需要使用非常奇怪的攻击(如强制用户打开“启用访问辅助设备”!)但我做到了OS X和它的工作原理(在OS X,我没能得到一个回调每次一些窗口的变化发生,所以我轮询,但我明白了工作)。
Now I want to do this under Windows (I really do, no question about it) and I've got a few questions:
现在我想在Windows下做这个(我真的这么做了,毫无疑问)我有几个问题:
-
can this be done?
这个可以做吗?
-
are there well documented Windows APIs (and working as per their specs) allowing to do that?
有很好的文档化的Windows api(并按照他们的规范工作)允许这样做吗?
-
is it easy to register a callback everytime a window changes? (if it is resized, moved, brought to back/front or if a new window pops-up, etc.)
每当窗口发生变化时,注册一个回调是否容易?(如果它被调整了大小、移动了、移到了后面/前面,或者新窗口弹出了等等)
-
what would the gotchas be?
陷阱是什么?
I know this question is not specific, which is why I've tried to describe my problem as clearly as possible (including nice ASCII art for which you can upvote this): for now I'm looking at the "big picture". I want to know what doing such a thing involves under Windows.
我知道这个问题不是特别的,这就是为什么我要尽可能清晰地描述我的问题(包括你可以对其进行投票的漂亮的ASCII艺术):现在我要看的是“大局”。我想知道在Windows下做这样的事情涉及到什么。
Bonus question: imagine you'd need to write a tiny .exe writing the windows names/position/size to a temporary file everytime there's a window change on screen, how long would such a program be approximately in your language of choice and how long would you need to write it?
额外的问题:假设您需要编写一个很小的.exe,将windows名称/位置/大小写入一个临时文件中,每次在屏幕上出现窗口更改时,这个程序大约需要多长时间以您所选择的语言编写?
(once again, I'm trying to get the "big picture" to understand what is at work here)
(再说一遍,我想从大局上理解这里的工作是什么)
3 个解决方案
#1
23
To enumerate the top-level windows, you should use EnumWindows rather than GetTopWindow/GetNextWindow, since EnumWindows returns a consistent view of the window state. You risk getting inconsistent information (such as reporting on deleted windows) or infinite loops using GetTopWindow/GetNextWindow, when windows change z-order during iteration.
要枚举*窗口,应该使用枚举窗口而不是GetTopWindow/GetNextWindow,因为枚举窗口返回窗口状态的一致视图。当windows在迭代期间更改z-order时,您可能会使用GetTopWindow/GetNextWindow获得不一致的信息(例如报告已删除的窗口)或无限循环。
The EnumWindows uses a callback. On each call of the callback you get a window handle. The screen co-ordinates of the window can be fetched by passing that handle to GetWindowRect. Your callback builds a list of the window positions in z-order.
枚举窗口使用回调。在回调的每个调用上都有一个窗口句柄。通过将该句柄传递给GetWindowRect,可以获取窗口的屏幕坐标。回调按z顺序构建窗口位置的列表。
You can use polling, and build the window list repeatedly. Or, you set up a CBTHook to receive notifications of window changes. Not all CBT notifications will result in changes to order,position or visibility of top level windows, so it's wise to rerun EnmWindows to build a new list of window positions in z-order and compare this to the previous list before processing the list further, so that futher processing is done only when a real change has occurred.
您可以使用轮询,并重复构建窗口列表。或者,设置一个CBTHook来接收窗口更改的通知。并不是所有CBT通知将导致改变顺序,位置或顶层窗口的可见性,所以它是明智的重新运行EnmWindows建造一个新的列表窗口位置在z值和比较进一步处理之前之前的清单列表,以便进一步处理只有当一个真正的改变发生。
Note that with hooking, you cannot mix 32- and 64-bit. If you are running a 32-bit app, then you will get notifications from 32-bit processes. Similarly for 64-bit. Thus, if you want to monitor the entire system on a 64-bit machine, it would seem that it's necessary to run two apps. My reasoning comes from reading this:
注意,使用挂钩,您不能混合32位和64位。如果您正在运行一个32位的应用程序,那么您将得到32位进程的通知。同样对于64位。因此,如果您想要在64位机器上监视整个系统,似乎需要运行两个应用程序。我的推理是这样的:
SetWindowsHookEx can be used to inject a DLL into another process. A 32-bit DLL cannot be injected into a 64-bit process, and a 64-bit DLL cannot be injected into a 32-bit process. If an application requires the use of hooks in other processes, it is required that a 32-bit application call SetWindowsHookEx to inject a 32-bit DLL into 32-bit processes, and a 64-bit application call SetWindowsHookEx to inject a 64-bit DLL into 64-bit processes. The 32-bit and 64-bit DLLs must have different names. (From the SetWindowsHookEx api page.)
SetWindowsHookEx可用于将DLL注入另一个进程。32位DLL不能注入到64位进程中,64位DLL也不能注入到32位进程中。如果一个应用程序需要在其他进程中使用钩子,那么需要一个32位的应用程序调用SetWindowsHookEx将一个32位的DLL注入32位进程,一个64位应用程序调用SetWindowsHookEx将64位DLL注入64位进程。32位dll和64位dll必须有不同的名称。(来自SetWindowsHookEx api页面)
As you're implementing this in Java, you might want to look at JNA - it makes writing access to native libraries much simpler (calling code in java) and removes the need for your own native JNI DLL.
当您在Java中实现这一点时,您可能想看看JNA——它使对本机库的编写访问变得更简单(在Java中调用代码),并消除了对您自己的本机JNI DLL的需要。
EDIT: You asked how much code it is and how long to write. Here's the code in java
编辑:你问它有多少代码,写多长时间。这是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);
}
}
}
I've made most of the related classes and interfaces inner classes to keep the example compact and pasteable for immediate compilation. In a real implementation, they would be regular top-level classes. The command line app prints out the visible windows and their position. I ran it on both 32-bit jvm and 64-bit, and got the same results for each.
我已经制作了大部分相关的类和接口内部类,以保持示例的紧凑性和可粘贴性,以便立即编译。在真正的实现中,它们将是常规的*类。命令行应用程序打印出可见窗口及其位置。我在32位jvm和64位jvm上运行它,得到的结果都是一样的。
EDIT2: Updated code to include z-order. It does use GetNextWindow. In a production application, you should probably call GetNextWindow twice for the next and previous values and check they are consistent and are valid window handles.
EDIT2:更新的代码包括z顺序。它使用GetNextWindow。在生产应用程序中,您可能应该为下一个和上一个值调用GetNextWindow两次,并检查它们是否一致,它们是有效的窗口句柄。
#2
7
can this be done?
这个可以做吗?
Yes, though you'd have to register a hook in order to get what you want with regards to a callback. You would probably need to use a CBTProc Callback Hook, which is called whenever:
是的,尽管你必须注册一个钩子才能得到你想要的回调。您可能需要使用CBTProc回调钩子,每当:
activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the keyboard focus; or before synchronizing with the system message queue
激活、创建、销毁、最小化、最大化、移动或调整窗口大小;在完成系统命令之前;在从系统消息队列中删除鼠标或键盘事件之前;在设置键盘焦点之前;或在与系统消息队列同步之前
Note however that I don't believe such hooks work on Console windows because they are the domain of the kernel, not Win32.
但是请注意,我不认为这种钩子可以在控制台窗口中工作,因为它们是内核的域,而不是Win32。
are there well documented Windows APIs (and working as per their specs) allowing to do that?
有很好的文档化的Windows api(并按照他们的规范工作)允许这样做吗?
Yes. You can use the GetTopWindow and GetNextWindow functions to get all the window handles on the desktop in correct Z order.
是的。您可以使用GetTopWindow和GetNextWindow函数以正确的Z顺序获得桌面上的所有窗口句柄。
is it easy to register a callback everytime a window changes? (if it is resized, moved, brought to back/front or if a new window pops-up, etc.)
每当窗口发生变化时,注册一个回调是否容易?(如果它被调整了大小、移动了、移到了后面/前面,或者新窗口弹出了等等)
See first answer :)
见第一个回答:)
what would the gotchas be?
陷阱是什么?
See first answer :)
见第一个回答:)
Bonus question: imagine you'd need to write a tiny .exe writing the windows names/position/size to a temporary file everytime there's a window change on screen, how long would such a program be approximately in your language of choice and how long would you need to write it?
额外的问题:假设您需要编写一个很小的.exe,将windows名称/位置/大小写入一个临时文件中,每次在屏幕上出现窗口更改时,这个程序大约需要多长时间以您所选择的语言编写?
A few hundred lines of C, and a couple hours. Though I'd have to use some form of polling -- I've never done hooks before myself. If I'd need the hooks it'd take somewhat longer.
几百行C,几个小时。虽然我必须使用某种形式的轮询——我以前从来没有做过钩子。如果我需要钩子,那就需要更长一点的时间。
#3
1
I remember back in 2006 there was a utility WinObj as part of sysinternals that possibly did what you want. Part of these utilities were provided with source code by author (Mark Russinovich).
我记得在2006年有一个实用程序WinObj作为sysinternals的一部分,它可能实现了你想要的。作者(Mark Russinovich)提供了部分实用程序的源代码。
Since that time, his company was bought by Microsoft so I do not know whether the source would be still available.
从那时起,他的公司被微软收购了,所以我不知道这个消息来源是否还在。
Also the following may be worth checking:
以下内容也值得一查:
http://msdn.microsoft.com/en-us/library/aa264396(VS.60).aspx
http://msdn.microsoft.com/en-us/library/aa264396(VS.60). aspx
http://www.codeproject.com/KB/dialog/windowfinder.aspx
http://www.codeproject.com/KB/dialog/windowfinder.aspx
#1
23
To enumerate the top-level windows, you should use EnumWindows rather than GetTopWindow/GetNextWindow, since EnumWindows returns a consistent view of the window state. You risk getting inconsistent information (such as reporting on deleted windows) or infinite loops using GetTopWindow/GetNextWindow, when windows change z-order during iteration.
要枚举*窗口,应该使用枚举窗口而不是GetTopWindow/GetNextWindow,因为枚举窗口返回窗口状态的一致视图。当windows在迭代期间更改z-order时,您可能会使用GetTopWindow/GetNextWindow获得不一致的信息(例如报告已删除的窗口)或无限循环。
The EnumWindows uses a callback. On each call of the callback you get a window handle. The screen co-ordinates of the window can be fetched by passing that handle to GetWindowRect. Your callback builds a list of the window positions in z-order.
枚举窗口使用回调。在回调的每个调用上都有一个窗口句柄。通过将该句柄传递给GetWindowRect,可以获取窗口的屏幕坐标。回调按z顺序构建窗口位置的列表。
You can use polling, and build the window list repeatedly. Or, you set up a CBTHook to receive notifications of window changes. Not all CBT notifications will result in changes to order,position or visibility of top level windows, so it's wise to rerun EnmWindows to build a new list of window positions in z-order and compare this to the previous list before processing the list further, so that futher processing is done only when a real change has occurred.
您可以使用轮询,并重复构建窗口列表。或者,设置一个CBTHook来接收窗口更改的通知。并不是所有CBT通知将导致改变顺序,位置或顶层窗口的可见性,所以它是明智的重新运行EnmWindows建造一个新的列表窗口位置在z值和比较进一步处理之前之前的清单列表,以便进一步处理只有当一个真正的改变发生。
Note that with hooking, you cannot mix 32- and 64-bit. If you are running a 32-bit app, then you will get notifications from 32-bit processes. Similarly for 64-bit. Thus, if you want to monitor the entire system on a 64-bit machine, it would seem that it's necessary to run two apps. My reasoning comes from reading this:
注意,使用挂钩,您不能混合32位和64位。如果您正在运行一个32位的应用程序,那么您将得到32位进程的通知。同样对于64位。因此,如果您想要在64位机器上监视整个系统,似乎需要运行两个应用程序。我的推理是这样的:
SetWindowsHookEx can be used to inject a DLL into another process. A 32-bit DLL cannot be injected into a 64-bit process, and a 64-bit DLL cannot be injected into a 32-bit process. If an application requires the use of hooks in other processes, it is required that a 32-bit application call SetWindowsHookEx to inject a 32-bit DLL into 32-bit processes, and a 64-bit application call SetWindowsHookEx to inject a 64-bit DLL into 64-bit processes. The 32-bit and 64-bit DLLs must have different names. (From the SetWindowsHookEx api page.)
SetWindowsHookEx可用于将DLL注入另一个进程。32位DLL不能注入到64位进程中,64位DLL也不能注入到32位进程中。如果一个应用程序需要在其他进程中使用钩子,那么需要一个32位的应用程序调用SetWindowsHookEx将一个32位的DLL注入32位进程,一个64位应用程序调用SetWindowsHookEx将64位DLL注入64位进程。32位dll和64位dll必须有不同的名称。(来自SetWindowsHookEx api页面)
As you're implementing this in Java, you might want to look at JNA - it makes writing access to native libraries much simpler (calling code in java) and removes the need for your own native JNI DLL.
当您在Java中实现这一点时,您可能想看看JNA——它使对本机库的编写访问变得更简单(在Java中调用代码),并消除了对您自己的本机JNI DLL的需要。
EDIT: You asked how much code it is and how long to write. Here's the code in java
编辑:你问它有多少代码,写多长时间。这是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);
}
}
}
I've made most of the related classes and interfaces inner classes to keep the example compact and pasteable for immediate compilation. In a real implementation, they would be regular top-level classes. The command line app prints out the visible windows and their position. I ran it on both 32-bit jvm and 64-bit, and got the same results for each.
我已经制作了大部分相关的类和接口内部类,以保持示例的紧凑性和可粘贴性,以便立即编译。在真正的实现中,它们将是常规的*类。命令行应用程序打印出可见窗口及其位置。我在32位jvm和64位jvm上运行它,得到的结果都是一样的。
EDIT2: Updated code to include z-order. It does use GetNextWindow. In a production application, you should probably call GetNextWindow twice for the next and previous values and check they are consistent and are valid window handles.
EDIT2:更新的代码包括z顺序。它使用GetNextWindow。在生产应用程序中,您可能应该为下一个和上一个值调用GetNextWindow两次,并检查它们是否一致,它们是有效的窗口句柄。
#2
7
can this be done?
这个可以做吗?
Yes, though you'd have to register a hook in order to get what you want with regards to a callback. You would probably need to use a CBTProc Callback Hook, which is called whenever:
是的,尽管你必须注册一个钩子才能得到你想要的回调。您可能需要使用CBTProc回调钩子,每当:
activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the keyboard focus; or before synchronizing with the system message queue
激活、创建、销毁、最小化、最大化、移动或调整窗口大小;在完成系统命令之前;在从系统消息队列中删除鼠标或键盘事件之前;在设置键盘焦点之前;或在与系统消息队列同步之前
Note however that I don't believe such hooks work on Console windows because they are the domain of the kernel, not Win32.
但是请注意,我不认为这种钩子可以在控制台窗口中工作,因为它们是内核的域,而不是Win32。
are there well documented Windows APIs (and working as per their specs) allowing to do that?
有很好的文档化的Windows api(并按照他们的规范工作)允许这样做吗?
Yes. You can use the GetTopWindow and GetNextWindow functions to get all the window handles on the desktop in correct Z order.
是的。您可以使用GetTopWindow和GetNextWindow函数以正确的Z顺序获得桌面上的所有窗口句柄。
is it easy to register a callback everytime a window changes? (if it is resized, moved, brought to back/front or if a new window pops-up, etc.)
每当窗口发生变化时,注册一个回调是否容易?(如果它被调整了大小、移动了、移到了后面/前面,或者新窗口弹出了等等)
See first answer :)
见第一个回答:)
what would the gotchas be?
陷阱是什么?
See first answer :)
见第一个回答:)
Bonus question: imagine you'd need to write a tiny .exe writing the windows names/position/size to a temporary file everytime there's a window change on screen, how long would such a program be approximately in your language of choice and how long would you need to write it?
额外的问题:假设您需要编写一个很小的.exe,将windows名称/位置/大小写入一个临时文件中,每次在屏幕上出现窗口更改时,这个程序大约需要多长时间以您所选择的语言编写?
A few hundred lines of C, and a couple hours. Though I'd have to use some form of polling -- I've never done hooks before myself. If I'd need the hooks it'd take somewhat longer.
几百行C,几个小时。虽然我必须使用某种形式的轮询——我以前从来没有做过钩子。如果我需要钩子,那就需要更长一点的时间。
#3
1
I remember back in 2006 there was a utility WinObj as part of sysinternals that possibly did what you want. Part of these utilities were provided with source code by author (Mark Russinovich).
我记得在2006年有一个实用程序WinObj作为sysinternals的一部分,它可能实现了你想要的。作者(Mark Russinovich)提供了部分实用程序的源代码。
Since that time, his company was bought by Microsoft so I do not know whether the source would be still available.
从那时起,他的公司被微软收购了,所以我不知道这个消息来源是否还在。
Also the following may be worth checking:
以下内容也值得一查:
http://msdn.microsoft.com/en-us/library/aa264396(VS.60).aspx
http://msdn.microsoft.com/en-us/library/aa264396(VS.60). aspx
http://www.codeproject.com/KB/dialog/windowfinder.aspx
http://www.codeproject.com/KB/dialog/windowfinder.aspx