Tag Archives: C++

Как работает AutoIt Window Info Tool

Последнее время пришлось по работе заниматься написанием небольшого приложения на AutoIt-те и в ходе работы выяснилось, что утилита Autoit Window Info Tool не всегда может извлечь информацию о том или ином элементе управления исследуемого приложения. Тут меня и разобрал интерес, а как эта утилита работает и что в действительности значит получаемая ею информация.

Title для главного окна и Text для элемента управления не требуют особого пояснения.

Class – это название так называемого оконного класса (window class ).

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

Ссылка на оконный класс передается функции CreateWindow (или CreateWindowEx), вызываемой для создания окна.

Использование класса окна позволяет создавать множество окон на основе одного и того же оконного класса и, следовательно, использовать одну и ту же оконную процедуру. Например, все кнопки в программах Windows созданы на основе оконного класса BUTTON. Оконная процедура этого класса, расположенная в системной dll, управляет обработкой сообщений для всех кнопок всех окон. Аналогичные системные классы имеются и для других элементов управления, таких как, например, списки и поля редактирования. В совокупности эти классы называются предопределенными или стандартными оконными классами.

Для главного окна приложение обычно создает собственный оконный класс, инициализируя структуру WNDCLASS (или WNDCLASSEX) и регистрируя этот класс вызовом функции RegisterClass (или RegisterClassEx).

Position – для диалогового окна это (x, y) – координаты относительно левого верхнего угла экрана (можно получить например функцией GetWindowRect), для дочерних окон элементов управления это (x, y) – координаты относительно левого верхнего угла клиентской области содержащего их окна.

Вот фрагмент кода, получающий этот параметр:

    HWND hWnd; // дескриптор исследуемого окна
    HWND hWndRoot = GetAncestor(hWnd, GA_ROOT);
    POINT wndPos; // position окна
    RECT  wndRect;
    GetWindowRect(hWnd, &wndRect);
    int wndWidth  = wndRect.right - wndRect.left;
    int wndHeight = wndRect.bottom - wndRect.top;

    if(hWndRoot == hWnd)
    {
        wndPos.x  = wndRect.left;
        wndPos.y  = wndRect.top;
    }
    else
    {
        WINDOWINFO rootWndInfo = {sizeof(WINDOWINFO)};
        GetWindowInfo(hWndRoot, &rootWndInfo);
        wndPos.x  = wndRect.left - rootWndInfo.rcClient.left;
        wndPos.y  = wndRect.top - rootWndInfo.rcClient.top;
    }

Size – ширина и высота окна в пикселях.

Style и ExStyle – стиль и расширенный стиль окна. Они содержаться в структуре WINDOWINFO значение которой для данного окна можно получить, вызывая функцию GetWindowInfo.

ID – идентификатор дочернего окна, который передается в функцию CreateWindow (или CreateWindowEx) при его создании (см. описание параметра hMenu у этих функций). Этот идентификатор передается в оконную функцию при сообщениях WM_COMMAND и WM_NOTIFY и по нему можно определить элемент управления, к которому относится это сообщение.

Handle – дескриптор окна который возвращает функция CreateWindow (или CreateWindowEx) при своем успешном завершении. Он меняет свое значение от запуска к запуску приложения.

Instance. Для выяснения значения этого параметра проведем следующий эксперимент. Рассмотрим следующее диалоговое окно

Вот фрагмент кода файла ресурсов, который отвечает за его создание:

LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDD_MAIN DIALOG 0, 0, 280, 300
STYLE DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_GROUP | WS_THICKFRAME | WS_SYSMENU
EXSTYLE WS_EX_TOPMOST
CAPTION "WinInfo"
MENU IDR_MENU1
FONT 8, "Ms Shell Dlg"
{
    GROUPBOX        "Basic Window Info", IDC_STATIC1, 5, 5, 270, 43
    LTEXT           "Title:", IDC_STATIC, 14, 17, 16, 8, SS_LEFT
    EDITTEXT      IDC_EDIT1, 57, 14, 212, 12, ES_AUTOHSCROLL | ES_READONLY
    LTEXT           "Class:", IDC_STATIC, 15, 32, 20, 8, SS_LEFT
    EDITTEXT       IDC_EDIT2, 57, 30, 212, 12, ES_AUTOHSCROLL | ES_READONLY
    GROUPBOX      "Finder Tool", IDC_STATIC2, 227, 50, 48, 43, BS_NOTIFY
    GROUPBOX      "Basic Control Info", IDC_STATIC, 5, 50, 215, 43
    LTEXT           "Class:", IDC_STATIC, 16, 60, 20, 8, SS_LEFT
    LTEXT           "Instance:", IDC_STATIC, 15, 74, 30, 8, SS_LEFT
    EDITTEXT     IDC_EDIT3, 57, 59, 158, 12, ES_AUTOHSCROLL | ES_READONLY
    EDITTEXT     IDC_EDIT4, 57, 74, 158, 12, ES_AUTOHSCROLL | ES_READONLY
    CONTROL     "", IDC_STATIC3, WC_STATIC, SS_BITMAP | SS_NOTIFY, 233, 61, 32, 25
    CONTROL     "", IDC_TAB1, WC_TABCONTROL, 0, 5, 100, 270, 195
}

За создание четырех текстовых полей Title, Class (в группе Basic Window Info), Class и ID (в Basic Control Info) отвечают выделенные строки. Autoit Window Info Tool для этих полей вернет соответственно значения Instance равные 1, 2, 3 и 4.

Если теперь взять и отредактировать файл ресурсов, поставив строку создающую, скажем, IDC_EDIT4 перед IDC_EDIT1, перекомпилировать проект и посмотреть какие Autoit Window Info Tool возвращает значения, то мы получим:

для IDC_EDIT1 => Instance = 2 , для IDC_EDIT2 => Instance = 3 , для IDC_EDIT3 => Instance = 4 , для IDC_EDIT4 => Instance = 1. То есть, Instance всего лишь указывает на порядок, в котором создаются элементы управления при инициализации приложения. У меня на практике был случай, когда у элементов управления менялся instance при переключении между вкладками в диалоговом окне.

В заключение хочу немного рассказать об алгоритме поиска дочернего окна по оконным координатам указателя мыши. В WinApi для этого есть следующие функции WindowFromPoint , ChildWindowFromPoint , RealChildWindowFromPoint. Но проблема в том, что если, скажем, несколько элементов управления находятся в группирующей их рамке, то по наведению на них мы не получим их дескриптора, а будем получать дескриптор рамки (даже использую RealChildWindowFromPoint у меня не получилось получить дескриптор элемента на котором находился курсор, хотя в документации по RealChildWindowFromPoint и сказано “… For example, if the point is in a transparent area of a groupbox, RealChildWindowFromPoint returns the child window behind a groupbox, whereas ChildWindowFromPoint returns the groupbox.”)

Я делал так. Вызывая WindowFromPoint, находил дескриптор окна, потом для этого окна получал самого верхнего его родителя GetAncestor(hWnd, GA_ROOT) . Потом для этого родителя находил все его дочерние окна. И из них выбирал наименьшее по размерам видимое окно, которому принадлежит точка с координатами указателя мыши. Вот класс, который это реализует.

/* WindowFinder.h */

#pragma once

#include <windows.h>
#include <stack>


#include "Debug.h"

using namespace std;

class WindowFinder
{
    public:
        static HWND findWindow(HWND hWnd, POINT point);
        
    private:
        static BOOL CALLBACK addChildWnd(HWND hWnd, LPARAM lParam);
        static stack<HWND> childWnd;
};

/* WindowFinder.cpp */

#include "WindowFinder.h"

stack<HWND> WindowFinder::childWnd = stack<HWND>();

HWND WindowFinder::findWindow(HWND hWnd, POINT point)
{
    HWND root = GetAncestor(hWnd, GA_ROOT);

    if(root == NULL){
        return NULL;
    }


    HWND findWnd = NULL;
    int  wndSize = INT_MAX;


    EnumChildWindows(root, addChildWnd, 0);

    while(!childWnd.empty())
    {
        HWND testWnd = childWnd.top();
        childWnd.pop();

        RECT testWndRect;

        if(!GetWindowRect(testWnd, &testWndRect))
        {
            DWORD dwError = GetLastError();
            _tprintf(_T("GetWindowRect: dwError = %i\n"), dwError);
            Debug::printErrorMessage(dwError);
            continue;
        }

        if(IsWindowVisible(testWnd) && PtInRect(&testWndRect, point))
        {
            int rectSize = abs((testWndRect.right - testWndRect.left) * (testWndRect.bottom - testWndRect.top));

            if(rectSize < wndSize)
            {
                findWnd = testWnd;
                wndSize = rectSize;
            }
        }
    }

    return findWnd;
}


BOOL WindowFinder::addChildWnd(HWND hWnd, LPARAM lParam)
{
    childWnd.push(hWnd); 
    return TRUE;
}

А вот моя реализация утилиты, которая работает, аналогично Autoit Window Info Tool. На вкладке Wnd Info отображается информация об элементе управления, выбранном при помощи Finder Tool, его непосредственном родителе и корневом родителе. На вкладке All Windows строится дерево из всех окон всех приложений, создавших элементы графического интерфейса на текущий момент. По двойному клику на узле дерева появляется окно с подробной информацией об элементе.

Вот и все, спасибо за внимание. :)