Delphi - объектно-ориентированный язык программирования, разработанный компанией Borland в 1995 году. Он основан на языке программирования Pascal, но имеет более расширенные возможности и добавлены новые функции.
Delphi является интегрированной средой разработки (IDE), которая позволяет разрабатывать программное обеспечение для различных платформ, включая Windows, macOS, Android и iOS. Delphi достигает многоплатформенности с помощью...
Всё или почти всё (хотя я не возьмусь сказать, что именно составляет исключение) в Windows имеет свой хэндл (Handle). Интересен перевод системой Сократ термина «Handle» — «Ручка», на нормальном техническом русском это будет дескриптор (определитель, идентификатор, описатель). Таким образом, handle — некий уникальный идентификатор любого ресурса Windows. Каждое окно имеет свой собственный дескриптор.
Иерархия окон в системе представлена таким образом:
Мы имеем древовидную структуру с возможностью навигации по дереву, как вверх и вниз по уровню вложенности, так и горизонтально. Горизонтальная навигация возможна исключительно по окнам, имеющим то же окно-владелец. Рассмотрим простенькую схемку:
Для «Окна 3» — владелец «Окно 1», окна своего уровня — «Окно 3 …Окно N», и соответсвующие по рисунку, дочерние окна — «Окно N+1 … N+M». При этом, непосредственно получить идентификаторы других окон, используя идетификатор исходного окна невозможно.
Перейдём к иструментарию Windows API, позволяющему реализовать сказаное выше.
Оконные функции WinAPI, используемые в данном проекте. |
function GetWindow(hWnd: HWND; uCmd: UINT): HWND;
Функция возвращает дескриптор окна, с заданным положением в иерархии окон относительно заданного окна.
Значения переменной uCmd:
О Z-упорядочивании: самое «верхнее» окно на экране имеет нулевую позицию, следующее, перекрываемое им окно - первую позицию, и так далее до самого «нижней» части экрана. Таким образом реализуется понятие трёхмерности, хотя в одной умной книжке я читал, что многооконная среда условно имеет 2.5-мерность (2.5D).
С помощью этой функции мы можем получить список всех окон системы несложной рекурсивной прогулкой по дереву (задача 1-го курса ВУЗа «Обход дерева»).
Нам понадобятся ещё несколько функций WinAPI:
function GetWindowText(hWnd: HWND; lpString: PChar; nMaxCount: Integer): Integer;
Функция получает текст окна по его идентификатору и возвращает считанную длину строки текста окна.
function GetClassName(hWnd: HWND; lpClassName: PChar; nMaxCount: Integer): Integer;
function GetMenu(hWnd: HWND): HMENU; Функция возвращает идентификатор меню по переданному идентификатору окна.
Перейдём к непосредственному построению программы.
Построение программы. |
Запустим Delphi и создадим новое приложение в меню File->New Application.
Не будем отвлекаться на навороченный интерфейс, не сомневаюсь, что большинство делает это легко, как «два байта переслать» ;). Построим что-нибудь а-ля "Проводник". Обзовём главную форму fmMain, а главный модуль - Main.pas. Разместим на форме компоненты как показано на рисунке:
Слева на форме находится компонент Tree:TTreeView, а клиентскую часть занимает компонент List:TListView.
Добавим в форму fmMain процедуру заполнения дерева FillTree следующего содержания:
// процедура заполнения дереваprocedure TfmMain.FillTree;var h,h1:THandle; Buffer:PChar; // рекурсивная процедура заполнения дереваprocedure RegisterWindow(hWnd:THandle;ParentTreeItem:TTreeNode);var Node:PNode; // данные окна TreeNode:TTreeNode; // узел дерева h:THandle; // хэндл окна, промежуточная переменнаяbegin // получение и сохранение информации об окне // создание переменной информации обокне New(Node); // присвоение дескриптора Node^.Handle:=hWnd; // получение текста окна GetWindowText(hWnd,Buffer,256); Node^.Text:=Trim(StrPas(Buffer)); // получение класса окна GetClassName(hWnd,Buffer,255); Node^.CName:=Trim(StrPas(Buffer)); // получение дескриптора меню окна Node^.Menu:=GetMenu(hWnd); // размещение в дереве TreeNode:=Tree.Items.AddChild(ParentTreeItem,''); // присвоение отображаемого текстового значения if Node^.Text<>'' then TreeNode.Text:=Node^.Text else if Node^.CName<>'' then TreeNode.Text:=Node^.CName else TreeNode.Text:=IntToStr(Node^.Handle); // сохранение ссылки на информацию об окне в соответствующем узле дерева TreeNode.Data:=Node; // Рекурсивные вызовы // получение поддчинённых элементов h:=GetWindow(hWnd,gw_Child); if h<>0 then RegisterWindow(h,TreeNode); // получение элеметов того же уровня h:=GetWindow(hWnd,gw_hWndNext); if h<>0 then RegisterWindow(h,ParentTreeItem);end;begin // сообщение в строке статуса Status.SimpleText:='Обновление информации об окнах ...'; Application.ProcessMessages; // выключение отображения изменений дерева Tree.Items.BeginUpdate; // очистка дерева Tree.Items.Clear; // создание буфера для работы с PChar Buffer:=StrAlloc(256); // Получаем хэндл текущего окна h1:=Handle; repeat // получаем хэндл окна-владельца h:=GetWindow(h1,GW_OWNER); // если не окно верхнего уровня, то ищем дальше if h<>0 then begin h1:=h; end; until h=0; // находим первое верхнее окно h:=GetWindow(h1,GW_HWNDFIRST); // запуск рекурсии RegisterWindow(h,nil); // задание выделенного элемента дерева Tree.Selected:=Tree.Items[0]; // включение отображения изменений дерева Tree.Items.EndUpdate; // удаление буфера для работы с PChar StrDispose(Buffer); // сообщение в строке статуса Status.SimpleText:='Готово.';end;
Теперь попробуем в ней разобраться (смотри по тексту процедуры). Status — класс TStatus Bar. Сообщим в строке статуса, что мы обновляем информацию. Выключим отображение обновления дерева для того, чтобы ничего в процессе обновления у нас на экране не дёргалось. Очистим дерево и создадим переменную Buffer, в которую будем записывать строковые результаты выполнения функций WinAPI.
Следующий этап - добраться до самого верхнего и самого первого окна системы. Мы вызываем функцию GetWindow с параметром GW_OWNER — получение окна-владельца, начиная с главного окна программы. Получив окно, не имеющее владельца, находим для этого окна окно с нулевым Z-порядком, вызвав функцию GetWindow с параметром GW_HWNDFIRST — получение первого окна в данном уровне вложенности. Теперь мы можем начать обход дерева окон, начиная с самого первого по порядку возрастания вложенности и Z-порядка, вызвав рекурсивную процедуру RegisterWindow.
Как известно, у класса TTreeNode — узла дерева — есть поле Data:Pointer. В это поле можно записывать любые ссылки на данные, которые требуется ассоциировать с узлом дерева. Создадим структуру данных для хранения информации об окне.
// тип для хранения информации об окнеTNode=record Handle:THandle; // хэндл окна Text:string[255]; // текст окна CName:string[255]; // имя класса Menu:HMenu; // хендл менюend;PNode=^TNode; // указатель на структуру
При нахождении очередного окна будем создавать динамическую переменную типа PNode и её значение записывать в поле TTreeNode.Data. Итак, мы знаем «первое верхнее» окно. Передадим его в процедуру RegisterWindow. Также в эту процедуру передаётся ссылка на узел дерева, к которому добавляются узлы подчинёных окон. В первом случае вызова рекурсии, эта ссылка равна nil.
Следующие действия просты до безобразия (процедура RegisterWindow):
Если мы вызовем процедуру FillTree в событии FormCreate, то дерево автоматически заполнится при запуске программы. Создадим действие acRefresh:TAction и свяжем его с кнопкой обновления, расположенной на форме. В обработке запуска действия напишем такой код:
// действие "обновить"procedure TfmMain.acRefreshExecute(Sender: TObject);begin // заполнение дерева FillTree;end;
Теперь мы можем принудительно обновлять список. Перейдём к отображению всей собранной информации об окне в компоненте List:TListView. Обработаем событие TTreeView.OnChange так:
// событие изменения выделенного элемента дереваprocedure TfmMain.TreeChange(Sender: TObject; Node: TTreeNode);// добавление в List строкиprocedure AddItem(Name,Value:String);var Item:TListItem;begin Item:=List.Items.Add(); Item.Caption:=Name; Item.SubItems.Add(Value);end;begin // выключение обновления List.Items.BeginUpdate; // очистка List.Items.Clear; // добавление элементов if (Node<>nil) and (Node.Data<>nil) then begin AddItem('Текст',TNode(Node.Data^).Text); AddItem('Класс',TNode(Node.Data^).CName); AddItem('Дескриптор окна',IntToStr(TNode(Node.Data^).Handle)); AddItem('Дескриптор меню',IntToStr(TNode(Node.Data^).Menu)); end; // включение обновления List.Items.EndUpdate;end;
На выделение соответсвующего элемента дерева (Node:TTreeNode), добавляем в компонент List:TListView строки со значениями всех полей записи PNode^.