Windows: как получить список всех видимых окон?
(конечно же, сделайте пометку с соответствующей технологией: я не знаю, какие они есть:)
Я, возможно, позже приду с более подробными вопросами, касающимися конкретных деталей, но сейчас я пытаюсь понять "общую картину": я ищу способ перечисления "реально видимых окон" в 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 (я действительно не сомневаюсь), и у меня есть несколько вопросов:
это можно сделать?
Есть ли хорошо документированные API-интерфейсы Windows (и работающие согласно их спецификациям), позволяющие это сделать?
Легко ли регистрировать обратный вызов при каждом изменении окна? (если он был изменен, перемещен, перенесен назад / вперед или если всплыло новое окно и т. д.)
что будет с Гочами?
Я знаю, что этот вопрос не является конкретным, поэтому я попытался описать свою проблему настолько четко, насколько это возможно (включая хорошее искусство ASCII, за которое вы можете выразить это): сейчас я смотрю на "общую картину". Я хочу знать, что делает такая вещь под Windows.
Дополнительный вопрос: представьте, что вам нужно было бы писать крошечный .exe файл, записывающий имена / положение / размер окон во временный файл каждый раз, когда на экране изменяется окно, как долго такая программа будет приблизительно на выбранном вами языке и как долго вам нужно было бы написать это?
(еще раз, я пытаюсь получить "общую картину", чтобы понять, что здесь происходит)
3 ответа
Для перечисления окон верхнего уровня вы должны использовать EnumWindows, а не GetTopWindow/GetNextWindow, так как EnumWindows возвращает согласованное представление состояния окна. Вы рискуете получить противоречивую информацию (например, отчеты об удаленных окнах) или бесконечные циклы, используя GetTopWindow/GetNextWindow, когда окна изменяют z-порядок во время итерации.
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 должны иметь разные имена. (Со страницы API SetWindowsHookEx.)
Поскольку вы реализуете это в 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-битной, так и на 64-битной jvm, и получил одинаковые результаты для каждого.
EDIT2: обновлен код для включения z-порядка. Он использует GetNextWindow. В производственном приложении вам, вероятно, следует дважды вызвать GetNextWindow для следующего и предыдущего значений и проверить, что они согласованы и являются допустимыми дескрипторами окна.
это можно сделать?
Да, хотя вам придется зарегистрировать ловушку, чтобы получить то, что вы хотите в отношении обратного вызова. Вам, вероятно, понадобится использовать Callback Hook CBTProc, который вызывается всякий раз, когда:
активация, создание, уничтожение, минимизация, максимизация, перемещение или изменение размера окна; перед выполнением системной команды; перед удалением события мыши или клавиатуры из очереди системных сообщений; перед настройкой фокуса клавиатуры; или перед синхронизацией с системной очередью сообщений
Однако обратите внимание, что я не верю, что такие перехватчики работают в окнах консоли, потому что они являются доменом ядра, а не Win32.
Есть ли хорошо документированные API-интерфейсы Windows (и работающие согласно их спецификациям), позволяющие это сделать?
Да. Вы можете использовать функции GetTopWindow и GetNextWindow, чтобы получить все дескрипторы окна на рабочем столе в правильном Z-порядке.
Легко ли регистрировать обратный вызов при каждом изменении окна? (если он был изменен, перемещен, перенесен назад / вперед или если всплыло новое окно и т. д.)
Смотрите первый ответ:)
что будет с Гочами?
Смотрите первый ответ:)
Дополнительный вопрос: представьте, что вам нужно было бы писать крошечный.exe файл, записывающий имена / положение / размер окон во временный файл каждый раз, когда на экране изменяется окно, как долго такая программа будет приблизительно на выбранном вами языке и как долго вам нужно было бы написать это?
Несколько сотен строк C и пара часов. Хотя мне пришлось бы использовать какую-то форму опроса - я никогда раньше не делал хуков. Если бы мне понадобились крючки, это заняло бы немного больше времени.
Я помню, еще в 2006 году была утилита WinObj в составе sysinternals, которая, возможно, делала то, что вы хотите. Часть этих утилит была предоставлена исходным кодом автора (Марк Руссинович).
С тех пор его компания была куплена Microsoft, поэтому я не знаю, будет ли источник по-прежнему доступен.
Также стоит проверить следующее:
http://msdn.microsoft.com/en-us/library/aa264396(VS.60).aspx