Как я могу получить функциональность, аналогичную Spy++, в моем приложении C#?

Я заинтересован в работе над плагином для Keepass, менеджера паролей с открытым исходным кодом. Прямо сейчас Keepass определяет, какой пароль копировать/вставлять для вас, исходя из заголовка окна. Это не позволяет Keepass определять текущий пароль, который вам нужен для приложений, которые не обновляют активно заголовок своего окна в зависимости от текущего сайта (например, Chrome).

Как я могу пройти через элементы окна других процессов (кнопки, метки, текстовое поле), аналогично тому, как работает Spy ++? Когда вы запускаете Spy++, вы можете наводить курсор на окна других программ и получать всевозможную информацию о различных свойствах различных элементов управления (метки, текстовые поля и т. д.). В идеале я хотел бы, чтобы мой плагин Keepass улучшал обнаружение текущего окна, просматривая элементы активного окна, пытаясь найти соответствующую учетную запись для копирования/вставки пароля.

Как я могу просматривать элементы окна других процессов и получать значения меток и текстовых полей с помощью С#?


person Community    schedule 28.12.2009    source источник
comment
Интересный квест, я не удивлюсь, если окажется, что для этого вам придется PInvoke to WINAPI.   -  person Tamas Czinege    schedule 28.12.2009


Ответы (5)


Я отвечаю на подобные вопросы здесь: Как узнать, есть ли у потока дескрипторы окон?. Как говорится, основная идея состоит в том, чтобы перечислить окна процессов и их дочерние окна, используя EnumWindows и EnumChildWindows вызовы API для получения дескрипторов окон, а затем вызов GetWindowText или SendDlgItemMessage с WM_GETTEXT для получения текста окна. Я изменил код, чтобы сделать пример, который должен делать то, что вам нужно (извините, что он немного длинный :). Он перебирает процессы и их окна и выводит текст окна в консоль.

static void Main(string[] args)
{
    foreach (Process procesInfo in Process.GetProcesses())
    {
        Console.WriteLine("process {0} {1:x}", procesInfo.ProcessName, procesInfo.Id);
        foreach (ProcessThread threadInfo in procesInfo.Threads)
        {
            // uncomment to dump thread handles
            //Console.WriteLine("\tthread {0:x}", threadInfo.Id);
            IntPtr[] windows = GetWindowHandlesForThread(threadInfo.Id);
            if (windows != null && windows.Length > 0)
                foreach (IntPtr hWnd in windows)
                    Console.WriteLine("\twindow {0:x} text:{1} caption:{2}",
                        hWnd.ToInt32(), GetText(hWnd), GetEditText(hWnd));
        }
    }
    Console.ReadLine();
}

private static IntPtr[] GetWindowHandlesForThread(int threadHandle)
{
    _results.Clear();
    EnumWindows(WindowEnum, threadHandle);
    return _results.ToArray();
}

// enum windows

private delegate int EnumWindowsProc(IntPtr hwnd, int lParam);

[DllImport("user32.Dll")]
private static extern int EnumWindows(EnumWindowsProc x, int y);
[DllImport("user32")]
private static extern bool EnumChildWindows(IntPtr window, EnumWindowsProc callback, int lParam);
[DllImport("user32.dll")]
public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);

private static List<IntPtr> _results = new List<IntPtr>();

private static int WindowEnum(IntPtr hWnd, int lParam)
{
    int processID = 0;
    int threadID = GetWindowThreadProcessId(hWnd, out processID);
    if (threadID == lParam)
    {
        _results.Add(hWnd);
        EnumChildWindows(hWnd, WindowEnum, threadID);
    }
    return 1;
}

// get window text

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowTextLength(IntPtr hWnd);

private static string GetText(IntPtr hWnd)
{
    int length = GetWindowTextLength(hWnd);
    StringBuilder sb = new StringBuilder(length + 1);
    GetWindowText(hWnd, sb, sb.Capacity);
    return sb.ToString();
}

// get richedit text 

public const int GWL_ID = -12;
public const int WM_GETTEXT = 0x000D;

[DllImport("User32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, int index);
[DllImport("User32.dll")]
public static extern IntPtr SendDlgItemMessage(IntPtr hWnd, int IDDlgItem, int uMsg, int nMaxCount, StringBuilder lpString);
[DllImport("User32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);

private static StringBuilder GetEditText(IntPtr hWnd)
{
    Int32 dwID = GetWindowLong(hWnd, GWL_ID);
    IntPtr hWndParent = GetParent(hWnd);
    StringBuilder title = new StringBuilder(128);
    SendDlgItemMessage(hWndParent, dwID, WM_GETTEXT, 128, title);
    return title;
}

надеюсь, это поможет, с уважением

person serge_gubenko    schedule 28.12.2009
comment
Я заметил, что в нескольких ответах люди говорят рекурсивно вызывать EnumChildWindows. НЕ ДЕЛАЙТЕ ЭТОГО. Прочтите документ MSDN для него - он уже рекурсивный, должен был быть более точно назван EnumDescendants. - person Mike Marynowski; 11.02.2011
comment
EnumChildWindows уже выполняет рекурсию, поэтому здесь вы выполняете двойную рекурсию; см. Старая новая вещь в EnumChildWindows - person Jeroen Wiert Pluimers; 24.01.2012
comment
Как мы можем избежать бесконечного цикла, который происходит на моей машине. Логично, что он должен вычеркнуть после сверления дочерних окон. - person Raheel Khan; 05.06.2013

Взгляните на эту статью здесь, которая содержит информацию об управляемом шпионе и почему автор написал инструмент.

person t0mm13b    schedule 28.12.2009
comment
Имейте в виду, что приложение, о котором вы говорите, работает только с управляемыми приложениями, а не с неуправляемыми, как большинство браузеров. - person Blindy; 28.12.2009

Вы можете использовать EnumWindows, чтобы найти все уровень Chrome, а затем вызовите EnumChildWindows рекурсивно (см. комментарий Jeroen Wiert Pluimers) для получения каждого дочернего элемента главного окна. В качестве альтернативы, если у вас есть главное окно Chrome, вы можете использовать GetWindow для ручного перемещения по дереву, поскольку вы, вероятно, знаете, что ищете (дочерняя коллекция 3-го ребенка или что-то подобное).

Найдя свое окно, вы можете использовать SendMessage с параметром WM_GETTEXT для чтения метки окна.

person Blindy    schedule 28.12.2009
comment
@Blindy, спасибо за информацию! Я хочу сделать это для любого активного окна, а не только для Chrome. Когда у меня есть дескрипторы окон/дочерних окон, как мне перемещаться по этим элементам окон? - person mmcdole; 28.12.2009
comment
Все это собственные вызовы Windows API, а не управляемые вызовы .NET Framework, поэтому для них требуется P/Invoke из CLR. - person Tamas Czinege; 28.12.2009
comment
@DrJokepu, да. Я подумал, что мне придется использовать магию P/Invoke. - person mmcdole; 28.12.2009
comment
@Blindy, когда у меня есть ссылка на окно, как я могу перебирать все его кнопки, метки, текстовые поля и т. д.? - person mmcdole; 28.12.2009
comment
Браузеры обычно используют элементы управления без окон из-за ограничения в 10 КБ в окнах. Возможно, вам придется написать определенные хуки для каждого браузера. - person arul; 28.12.2009
comment
Что ж, если вы можете увидеть это в Spy++, вы можете увидеть это и в этом. Для навигации по окнам вы используете GetWindow с GW_HWNDNEXT/GW_HWNDPREV для братьев и сестер и GW_CHILD для детей. - person Blindy; 28.12.2009
comment
Не рекурсивно используйте EnumChildWindows, так как он уже рекурсивно. См. Старая новая вещь в EnumChildWindows - person Jeroen Wiert Pluimers; 24.01.2012

Вы можете использовать HWndSpy. Исходный код здесь.

введите здесь описание изображения

person Stack    schedule 12.01.2016

Для функциональности указания на окно. Вам нужно SetCapture(), чтобы получать сообщения мыши, которые находятся за пределами вашего окна. Затем используйте WindowFromPoint(), чтобы преобразовать положение мыши в окно. Сначала вам нужно будет преобразовать положение мундштука из координат клиента в координаты окна.

Если вы попытаетесь вызвать SetCapture() в любом месте, кроме сообщения о щелчке мыши, вас, вероятно, проигнорируют. По этой причине Spy++ заставляет вас щелкнуть значок и перетащить его в окно, на которое вы хотите указать.

person John Knoeller    schedule 28.12.2009
comment
То, что я пытаюсь сделать, это просто перебирать элементы Windows без взаимодействия с мышью. Как только я вызову EnumChildWindows, как мне получить текст любых элементов, принадлежащих этому дочернему окну? - person mmcdole; 28.12.2009
comment
Отправка оконных сообщений. WM_GETTEXT и т. д. Если этого недостаточно, вам нужно определить, что вы подразумеваете под «элементами»? - person John Knoeller; 28.12.2009
comment
@John Knoeller, все текстовые метки, текстовые поля и кнопки. - person mmcdole; 28.12.2009
comment
EnumChildWindows ваших дочерних окон. повторяйте рекурсивно, пока не закончатся поколения. - person John Knoeller; 28.12.2009
comment
@Simucal, где вы можете перечислить все элементы дочернего окна и получить соответствующие значения? если да, поделитесь - person Zee99; 17.09.2010