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

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

Другие вопросы по тегам