Прогулка по окнам Windows
Дата: 10/02/2004
Тема: Формы и окна


Всё или почти всё (хотя я не возьмусь сказать, что именно составляет исключение) в 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







Это статья Delphirus
http://www.delphirus.com.ru

URL этой статьи:
http://www.delphirus.com.ru/modules.php?name=News&file=article&sid=40