Delphi - объектно-ориентированный язык программирования, разработанный компанией Borland в 1995 году. Он основан на языке программирования Pascal, но имеет более расширенные возможности и добавлены новые функции.
Delphi является интегрированной средой разработки (IDE), которая позволяет разрабатывать программное обеспечение для различных платформ, включая Windows, macOS, Android и iOS. Delphi достигает многоплатформенности с помощью...
Для чего это? Нет, конечно, никакого отношения это статья к привычным извращениям не имеет, просто рассказывает, как можно подглядывать в чужие окна.
Судя по тому шквалу вопросов, которыми завалены форумы, вопрос изучения чужих окон интересует многих. Каюсь, здесь я оказался в большинстве. Движимый любопытством я попытался разобраться в том, как же все-таки заглянуть в чужое окно. И написал некую программку, которая все это умеет делать. Ну, почти все. Попутно пришлось найти ответы на многие вопросы, которые, как мне кажется, интересуют не только меня. Программа написана на Delphi 3 для Windows 98. И, возможно, в более поздних версиях Delphi появились дополнительные возможности.
Чтобы не засорять место бесконечными объяснениями интерфейсной части полный исходный текст программы приводить не буду, постараюсь изложить по пунктам, как она работает. Тест будет избыточным с большим количеством ненужных примеров, например, нахождение верхних окон приводиться в двух вариантах, оба рабочие, но один из которых работает с определенными трудностями. Зачем это делается??? Просто программа писалась для определенных задач, кои могут не совпадать с Вашими. Основная цель объяснить, как работать с чужими окнами при помощи функциями API, а какие примеры и для чего применять решать Вам.Для кого это? |
Как получить список всех окон запущенных в системе. |
Procedure WindowPresent(ClassName,WindowName:PChar): Boolean; Begin Result := FindWindow(ClassName,WindowName)<>0; End;Но, зачастую требуется определить все окна, или окна, для которых не известен класс и/или заголовок. Для решения нашей задачи, также можно использовать функцию FindWindow Но посмотрим, что еще у нас есть из функций работы с окнами. Функция GetNextWindow Синтаксис: function GetNextWindow(Wnd: HWnd; Flag: Word): Hwnd; Описание: Считывает из Wnd следующее или предыдущее окно. В случае окна верхнего уровня ищется следующее окно верхнего уровня, а в случае дочернего окна ищется следующее дочернее окно. параметры:
procedure Tform1.GetAllWindow; Var Wd : HWnd; begin ListBox1.Items.Clear; // Очистим список перед началом поисков Wd:=FindWindow(0,0); // Найдем первое окно верхнего уровня любого класса While (Wd<>0) do // Если такое окно существует Begin ListBox1.Items.Add(IntToStr(Wd)); // Добавим описатель в виде текста в список Application.ProcessMessages; // Дадим возможность поработать другим Wd:=GetNextWindow(Wd,GW_HWNDNEXT); // Найдем следующее окно в системе. End; end;Работает??? Работает, но как-то не совсем так, как хотелось, ряд окон не отображается (например, системные окна, такие как System Tray), возможно некое зацикливание программы в некоторых случаях. Просто потому, что для этих целей существует совсем другой способ. Функция EnumWindows Синтаксис: function EnumWindows(EnumFunc: TFarProc, lParam: Longint): Bool; Описание: Пеpечисляет все pодительские окна на экpане, пеpедавая функции обpатного вызова ( т.е объявленная как stdcall функция) описатель окна и lParam. Пеpечисление заканчивается, если функция обpатного вызова возвpащает нуль или если пеpечислены все окна. Параметры:
function EnumProc (Wd: HWnd; Param: LongInt): Boolean; stdcall; // Обязательно stdcall !!! Begin ListBox1.Items.Add(IntToStr(Wd)); // Добавляем текущий описатель окна EnumProc := TRUE; end; Procedure TForm1.GetAllWindow; Begin ListBox1.Items.Clear; // Очистим список перед началом поисков EnumWindows (@EnumProc, 0); // и скажем - искать End;И получается проще. В дальнейшем все изменения будут идти относительно текста 2 , хотя все это будет справедливо и для текста 1 .
Как получить общую информацию об окнах верхнего уровня. |
function EnumProc (Wd: HWnd; Param: LongInt): Boolean; stdcall; // Обязательно stdcall !!! Var Nm:Array[0..255] of Char; // буфер для имени Cs: Array[0..255] of Char; // буфер для класса Begin GetWindowText(Wd,Nm,255); // считываем текст заголовка окна GetClassName(Wd,Cs,255); // считываем название класса окна ListBox1.Items.Add(String(Nm)+'/'+String(Cs)); // Добавляем название окна и класс в список EnumProc := TRUE; // продолжать искать окна… end;Конечно, красоты ради, стоило бы сначала определить необходимый размер буфера, но в подавляющем большинстве случаев вполне хватает 255 символов. Для желающих поразвлечься самостоятельно предлагаю определить размер заголовка помощи следующих функции: Функция GetWindowTextLength Синтаксис: function GetWindowTextLength(Wnd: HWnd): Integer; Описание: Считывает длину заголовка окна или текста оpгана упpавления. Параметры: Wnd: Идентификатор окна. Возвращаемое значение: Длина заголовка окна в символах. Вот теперь мы видим какие окна верхнего уровня у нас загружены в системе, и даже можем понемногу разбираться какие окна к чему относятся. Но появляются разные странности. Во-первых количество окон в системе оказывается больше чем то, что мы видим. Во-вторых появляются окна вообще с непонятными классами и/или названиями или вообще без них. Спешу Вас успокоить, список содержит ВСЕ окна которые есть в системе, включая скрытые, системные (например ProgMan не что иное как рабочий стол). В том числе и Вашу программу. Встает вопрос, как бы убрать собственную программу из списка ? Для этого коротенько намекну, что описатель, который мы так долго и муторно получали, на самом деле, совпадает с Handle, который есть у любой формы. Чтобы исключить свою программу из списка достаточно просто поставить проверку В тексте 3: If Wd<>Form1.Handle then ListBox1.Items.Add(String(Nm)+'/'+String(Cs)); Для пущей красоты можно сделать переключатель, который отвечает за то, будет ли в список добавляться Ваша программа. Если у Вас не одно окно, а несколько, то нужно проверять все окна. Что из общих параметров можно узнать еще об окне ??? Ну например можно узнать состояние окна, то есть : Функция IsIconic Синтаксис: function IsIconic(Wnd: HWnd): Bool; Описание: Опpеделяет, является ли окно пиктогpаммой (минимизиpованным). Параметры: Wnd: Идентификатор окна. Возвращаемое значение: Не нуль, если минимизиpовано; 0 - если нет. Функция IsWindow Синтаксис: function IsWindow(Wnd: HWnd): Bool; Описание: Опpеделяет, является ли окно допустимым существующим окном. Параметры: Wnd: Идентификатор окна. Возвращаемое значение: Не нуль, если окно достовеpно; 0 - если нет. Функция IsWindowEnabled Синтаксис: function IsWindowEnabled(Wnd: HWnd): Bool; Описание: Опpеделяет, является ли окно pазpешенным для ввода с мыши и с клавиатуpы. Параметры: Wnd: Идентификатор окна. Возвращаемое значение: Не нуль, если окно pазpешено; 0 - если нет. Функция IsWindowVisible Синтаксис: function IsWindowVisible(Wnd: HWnd): Bool; Описание: Опpеделяет, сделано ли окно видимым функцией ShowWindow. Параметры: Wnd: Идентификатор окна. Возвращаемое значение: Не нуль, если окно существует на экpане (даже если полностью закpыто); 0 - если нет. Функция IsZoomed Синтаксис: function IsZoomed(Wnd: HWnd): Bool; Описание: Опpеделяет, является ли окно максимизиpованным. Параметры: Wnd: Идентификатор окна. Возвращаемое значение: Не нуль, если окно максимизиpовано; 0 - если нет. Простой пример использования этих функций:
function EnumProc (Wd: HWnd; Param: LongInt): Boolean; stdcall; // Обязательно stdcall !!! Var Nm:Array[0..255] of Char; // буфер для имени Cs: Array[0..255] of Char; / / буфер для класса Ch:Char; //символ обозначающий, что окно минимизиравано Begin GetWindowText(Wd,Nm,255); // считываем текст заголовка окна GetClassName(Wd,Cs,255); // считываем название класса окна If IsIconic(Wd) then Ch:= '+' Else Ch:= '-' ; // Добавляем название окна и класс в список //первый символ + означает, что окно - иконка If Wd<>Form1.Handle then ListBox1.Items.Add(Ch+ ' ' +String(Nm)+ '/' +String(Cs)); EnumProc := TRUE; end;Работа с остальными функциями этой группы проводиться таким же образом, Вы получаете значение типа boolean, и что-то где-то отображаете. Кроме того, можно так же предусмотреть возможность отображать только окна определенного вида. Например, только видимые. Для этого достаточно вставить вместо строки
If Wd<>Form1.Handle …… условие вида If ISWindowVisble(Wd) and (Wd<>Form1.Handle) then ….Впрочем, это уже на Ваш вкус, что отображать и как.
Что еще можно узнать об окнах верхнего уровня ? |
Function GetWinRect(Wd:HWND):TRect; Begin GetWindowRect(Wd,Result); End;Пpоцедуpа GetClientRect Синтаксис: procedure GetClientRect(Wnd: HWnd; var Rect: TRect); Описание: Считывает кооpдинаты пользователя окна. Параметры:
Function WindowToBMP(WD: HWND ): TBitmap; Var WinDC: HDC; ARect : TRect; begin Result := TBitmap.Create; // Создаем рисунок, куда будем копировать GetWindowRect(WD, ARect); // Узнаем размер with Result, ARect do begin Width := ARect.Right - ARect.Left; Height := ARect.Bottom - ARect.Top; If (Width=0) or (Height=0) then Begin MessageDlg( 'Размер области формы равен нулю' , mtWarning,[mbOk],0); // А вдруг у него нет размера ??? Exit; // Тогда выходим End; WinDC:=GetWindowDC(Wd); // получаем для окна контекст устройства ShowWindow(Wd, SW_SHOW); // на всякий случай выведем окно BringWindowToTop(WD); // и поместим поверх окон try // копируем оттуда прямоугольную область на канву // растрового изображения BitBlt( Canvas.Handle, 0, 0, Width, Height, WinDC, 0, 0, SRCCOPY); finally end; end; end;и функция, которая копирует только клиентскую часть окна в BMP
Function WindowToBMP(WD: HWND ): TBitmap; Var WinDC: HDC; ARect : TRect; begin Result := TBitmap.Create; // Создаем рисунок, куда будем копировать GetClientRect(WD, ARect); // Узнаем размер with Result, ARect do begin Width := ARect.Right - ARect.Left; Height := ARect.Bottom - ARect.Top; If (Width=0) or (Height=0) then Begin MessageDlg( 'Размер области формы равен нулю' , mtWarning,[mbOk],0); // А вдруг у него нет размера ??? Exit; // Тогда выходим End; WinDC:=GetWindowDC(Wd); // получаем для окна контекст устройства ShowWindow(Wd, SW_SHOW); // на всякий случай выведем окно BringWindowToTop(WD); // и поместим поверх окон try // копируем оттуда прямоугольную область на канву // растрового изображения BitBlt( Canvas.Handle, 0, 0, Width, Height, WinDC, 0, 0, SRCCOPY); finally end; end; end;Видно, что эти функции отличаются только определением области окна, которое будет скопировано. Сложностей с пониманием работы этих функций быть не должно, получили область копирования по описателю, вычислили размеры области копирования, получили контекст устройства, и скопировали. Кстати сказать, для того, чтобы скопировать весь экран или часть экрана можно использовать подобные функции. Единственное, что в таком случае придется поменять так это контекст устройства. Контекст устройства всего экрана 0. Ну и вместо того, чтобы получать область окна, нужно будет явно передавать координаты области, которую необходимо скопировать. Очень много информации об окне можно получить при помощи следующей функции: Функция GetWindowLong Синтаксис: function GetWindowLong(Wnd: HWnd; Index: Integer): Longint; Описание: Считывает инфоpмацию об окне или о значениях дополнительного байта окна. Паpаметpы:
Function GetHinstanse(WD:HWND):LongInt; Begin Result:=GetWindowLong(Wd, GWL_HINSTANCE); End;Похожим образом получается любая информация, только в качестве второго параметра Вы передаете тот флаг, который Вам необходим. Для интереса, можете посмотреть еще описание функции GetWindowWord она сходна с функцией GetWindowLong, но возможно получение несколько других параметров окна. Еще ряд параметров можно вытащить используя функцию: Функция GetClassLong Синтаксис: function GetClassLong(Wnd: HWnd; Index: Integer): Longint; Описание: Считывает из стpуктуpы окна TWndClass со смещением Index длинное значение. Положительные смещения в байтах (с нуля) используются для доступа к дополнительным байтам класса. Паpаметpы:
Function WinIconToBMP(Wd:HWND); Var Icon:HICON; // Тип указатель на иконку Begin Result:=TbitMap.Create; Icon:=GetClassLong(Wd,GCL_HICON); // Получаем иконку по описателю If ICON > 0 then // Если получена действительно иконка With Result do Begin Height:=31; Width:=31; Canvas.Brush.Color:=clWhite; Canvas.FillRect(Rect(0,0,31,31)); // На всякий случай заливаем белым DrawIcon(Canvas.Handle, 0, 0, Icon); // И отрисовываем ее на канве End; End;Как видно, процедура не такая уж и сложная. Для полноты информации можно еще извлечь идентификатор нити, к которой относиться окно. Функция GetWindowThreadProcessId Синтаксис: DWORD GetWindowThreadProcessId(Wnd: HWND; lpdwProcessId : LPDWORD); Описание: Возвращает идентификатор процесса к которому принадлежит данное окно Параметры: Wnd: Идентификатор окна. lpdwProcessId : 32битное значение идентификатора процесса Возвращаемое значение: идентификатор нити
Как использовать данную функцию ??
Var mProcessID,mThreadID : Dword; Begin ... // Получение описателя в WD mThreadID:= GetWindowThreadProcessId(Wd,@mProcessID); // Получения идентификаторов. ... End;Эта функция хороша тем, что для всех окон одного приложения этот идентификатор будут един, если они запущены внутри одной нити (что зачастую и делается). То есть Вы можете разбить окна по нитям и /или по процессам, к которым они относятся. А так же Вы можете определять, какие окна данного приложения в настоящий момент доступны, видны, свернуты и т.д.
Итоги |
Описание функций API
Список текстов