");
// -->
Всё или почти всё (хотя я не возьмусь сказать, что именно составляет исключение) в Windows имеет свой хэндл (Handle). Интересен перевод системой Сократ термина «Handle» — «Ручка», на нормальном техническом русском это будет дескриптор (определитель, идентификатор, описатель). Таким образом, handle — некий уникальный идентификатор любого ресурса Windows. Каждое окно имеет свой собственный дескриптор.
Иерархия окон в системе представлена таким образом:
- каждое окно имеет список подчинённых окон. Список может быть пустым, в случае, если стиль окна не предусматривает хранения подчинённых элементов.
- каждое окно имеет окно-владельца. Дескриптор окна-владельца будет нулевым (пустым), если окно имеет верхний уровень вложенности, например, главное окно программы.
- для каждого окна можно получить слудующее и предыдущее, в его уровне вложенности, окно.
Мы имеем древовидную структуру с возможностью навигации по дереву, как вверх и вниз по уровню вложенности, так и горизонтально. Горизонтальная навигация возможна исключительно по окнам, имеющим то же окно-владелец. Рассмотрим простенькую схемку:
Для «Окна 3» — владелец «Окно 1», окна своего уровня — «Окно 3 …Окно N», и соответсвующие по рисунку, дочерние окна — «Окно N+1 … N+M». При этом, непосредственно получить идентификаторы других окон, используя идетификатор исходного окна невозможно.
Перейдём к иструментарию Windows API, позволяющему реализовать сказаное выше.
Оконные функции WinAPI, используемые в данном проекте. |
function GetWindow(hWnd: HWND; uCmd: UINT): HWND;
Функция возвращает дескриптор окна, с заданным положением в иерархии окон относительно заданного окна.
- hWnd — дескриптор исходного окна.
- uCmd — направление связи, т.е. вверх, вниз или по горизонтали.
Значения переменной uCmd:
- GW_CHILD — Возвращает дескриптор дочернего (подчинённого) окна, находящегося в верхней позиции Z-упорядочивания. В случае, если окно не имеет дочерних окон, возвращается 0.
- GW_HWNDFIRST — Возвращает дескриптор окна, находящегося в верхней позиции Z-упорядочивания того же уровня, что и исходное окно.
- GW_HWNDLAST — Возвращает дескриптор окна, находящегося в нижней позиции Z-упорядочивания того же уровня, что и исходное окно.
- GW_HWNDNEXT — Возвращает дескриптор окна, находящегося в следующей позиции Z-упорядочивания того же уровня, что и исходное окно.
- GW_HWNDPREV — Возвращает дескриптор окна, находящегося в предыдущей позиции Z-упорядочивания того же уровня, что и исходное окно.
- GW_OWNER — Возвращает дескриптор окна владельца исходного окна. Если окно имеет нулевой уровень вложенности, возвращается 0.
О Z-упорядочивании: самое «верхнее» окно на экране имеет нулевую позицию, следующее, перекрываемое им окно - первую позицию, и так далее до самого «нижней» части экрана. Таким образом реализуется понятие трёхмерности, хотя в одной умной книжке я читал, что многооконная среда условно имеет 2.5-мерность (2.5D).
С помощью этой функции мы можем получить список всех окон системы несложной рекурсивной прогулкой по дереву (задача 1-го курса ВУЗа «Обход дерева»).
Нам понадобятся ещё несколько функций WinAPI:
function GetWindowText(hWnd: HWND; lpString: PChar; nMaxCount: Integer): Integer;
Функция получает текст окна по его идентификатору и возвращает считанную длину строки текста окна.
- hWnd - идентификатор окна.
- lpString - текст окна в переменной PChar. Переменную необходимо создать заранее.
- nMaxCount - длина строки lpString.
function GetClassName(hWnd: HWND; lpClassName: PChar; nMaxCount: Integer): Integer;
- hWnd - идентификатор окна.
- lpClassName - имя класса окна в переменной PChar. Переменную необходимо создать заранее.
- nMaxCount - длина строки lpClassName.
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):
- создать переменную описания окна;
- получить по заданному идентификатору описанными ранее функциями WinAPI текст, имя класса и идентификатор меню, и записать эти значения в переменную окна;
- создать узел дерева и присвоить ему перменную окна, задав владельца узла и присвоив ему текстовое значение;
- получить идентификатор дочернего окна, и если идентификатор не равен 0, рекурсивно его обработать, передав в RegisterWindow полученные параметры. При этом, в процедуру передаётся идентификатор полученного дочернего окна и только что созданный узел дерева;
- получить идентификатор следующего за текущим окна того же уровня вложенности. И если он не равен 0, передать в RegisterWindow полученный идентификатор и внешний параметр ParentTreeItem. При этом узлы дерева будут добавляться на том же уровне, что и текущий узел.
Если мы вызовем процедуру 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^.
Всё!
Мы имеем информацию о всех окнах и используя идентификатор окна можем получить доступ к ОЧЕНЬ БОЛЬШОМУ КОЛИЧЕСТВУ параметров окна через WinAPI
Проиллюстрируем четырьмя примерами:
- Спрятать ненужное (читаем, надоевшее) окно. Например, окно системы баннерных показов. Делается так:
ShowWindow(TNode(Tree.Selected.Data^).Handle,sw_Hide). Правда эффективного скрытия этого окна нужно работать с собщениями cерии ABM_*, но это другая история.
- Показать упрятанное окно. Обратное действие:
ShowWindow(TNode(Tree.Selected.Data^).Handle,sw_Show). Например, слетела панель с кнопкой "Пуск". Все значки в системном трее пропали. Показать окно не представляет проблемы.
- Разрешить запрещенный элемент управления:
EnableWindow(TNode(Tree.Selected.Data^).Handle,True). Для чего это нужно, не будем даже и говорить.
- Убить окно:
PostMessage(TNode(Tree.Selected.Data^).Handle,wm_Close,0,0). Объяснение — см. пункт 3.
Источник www.delphi.aiq.ru
|