// Это функция обратного вызова, которая будет
// использоваться при вызове EnumWindows и EnumChildWindows.
// Тип второго параметра не совпадает с типом, который
// указан MSDN. Однако TTreeNode, как и любой класс,
// является указателем, поэтому может использоваться везде,
// где требуется нетипизированный указатель - на двоичном
// уровне между ними нет разницы. Указатель на функцию
// обратного вызова в EnumWindows и EnumChildWindows в
// модуле Windows.dcu объявлен как нетипизированный
// указатель, поэтому компилятор не контролирует
// соответствие реального прототипа заявленному.
function EnumWindowsProc(Wnd: HWND; ParentNode: TTreeNode): Bool; stdcall;
// Система не предусматривает возможности узнать, какова
// длина имени класса, поэтому при получении этого имени
// приходится выделять буфер большой длины в надежде, что
// имя класса не окажется еще длиннее. В данном примере
// размер этого буфера определяется константой ClassNameLen.
// Крайне маловероятно, что имя класса скажется длиннее,
// чем 511 символов (512-й зарезервирован для завершающего
// нулевого символа).
const
ClassNameLen = 512;
var
// Здесь будет храниться заголовок окна
Text: string;
TextLen: Integer;
// Это - буфер для имени класса
ClassName: array[0..ClassNameLen - 1] of Char;
Node: TTreeNode;
NodeName: string;
begin
Result := True;
// Функция EnumChildWindows перечисляет не только
// непосредственно дочерние окна данного окна, но и
// дочерние окна его дочерних окон и т.п. Но при
// построении дерева на каждом шаге нам нужны только
// прямые потомки, поэтому все окна, не являющиеся прямыми
// потомками, мы здесь игнорируем.
if Assigned(ParentNode) and (GetParent(Wnd) <> HWND(ParentNode.Data)) then Exit;
// Получаем длину заголовка окна. Вместо функций
// GetWindowText и GetWindowTextLength мы здесь
// используем сообщения WM_GETTEXT и WM_GETTEXTLENGTH,
// потому что функции, в отличие от сообщений, не
// умеют работать с элементами управления,
// принадлежащими окнам чужих процессов.
TextLen := SendMessage(Wnd, WM_GETTEXTLENGTH, 0, 0);
// Устанавливаем длину строковой переменной, которая
// будет служить буфером для заголовка окна.
// Использование SetLength гарантирует, что будет
// выделена специальная область памяти, на которую не
// будет других ссылок.
SetLength(Text, TextLen);
// Если заголовок окна - пустая строка, TextLen будет
// иметь значение 0, и указатель Text при выполнении
// Set Length получит значение nil. Но при обработке
// сообщения WM_GETTEXT оконная процедура в любом случае
// попытается записать строку по переданному адресу,
// даже если заголовок окна пустой - в этом случае в
// переданный буфер будет записан один символ -
// завершающий ноль. Но если будет передан nil, то
// попытка записать что-то в такой буфер приведет к
// Access violation, поэтому отправлять окну WM_GETTEXT
// можно только в том случае, если TextLen > 0.
if TextLen > 0 then
SendMessage(Wnd, WM_GETTEXT, TextLen + 1, LParam (Text));
// Заголовок окна может быть очень длинным - например, в
// Memo заголовком считается весь текст, который там
// есть. Практика показывает, что существуют проблемы
// при добавлении в TTreeView узлов с очень длинным
// названиями: при попытке открыть такой узел программа,
// запущенная из Delphi, вылетает в отладчик (при
// запуске вне среды Delphi проблем не замечено). Чтобы
// этого не происходило, слишком длинные строки
// обрезаются.
if TextLen > 100 then
Text := Copy(Text, 1, 100) + '...';
GetClassName(Wnd, ClassName, ClassNameLen);
ClassName[ClassNameLen - 1] := #0;
if Text = '' then NodeName := 'Без названия (' + ClassName + ') '
else NodeName := Text + ' (' + ClassName + ')';
Node := FormWindows.TreeWindows.Items.AddChild(ParentNode, NodeName);
// Записываем в данные узла дескриптор соответствующего
// ему окна, чтобы иметь возможность отбросить непрямые
// потомки.
Node.Data := Pointer(Wnd);
// Вызываем EnumChildWindows, передавая функцию
// EnumWindowsProc в качестве параметра, а указатель на
// созданный узел - в качестве параметра этой функции.
// При этом EnumWindowsProc будет вызываться из
// EnumChildWindows, т.е. получается рекурсия.
EnumChildWindows(Wnd, @EnumWindowsProc, LParam(Mode));
end;
Как мы помним, первый параметр функции обратного вызова для EnumWindows содержит дескриптор найденного окна, а второй параметр может быть произвольным 4-байтным значением, которое система игнорирует, просто копируя сюда то значение, которое было передано при вызове EnumWindows или EnumChildWindows. Мы задействуем этот параметр для передачи ссылки на узел дерева, соответствующий родительскому окну. Также договоримся, что в свойство Data каждого узла будем записывать дескриптор связанного с ним окна. Для окон верхнего уровня ссылка будет иметь значение nil — это обеспечивается тем, что при вызове EnumWindows второй параметр равен нулю (см. листинг 1.21).