Node.Data:= Pointer(Wnd);
// Вызываем EnumChildWindows, передавая функцию
// EnumWindowsProc в качестве параметра, а указатель на
// созданный узел — в качестве параметра этой функции.
// При этом EnumWindowsProc будет вызываться из
// EnumChildWindows, т. е. получается рекурсия.
EnumChildWindows(Wnd, @EnumWindowsProc, LParam(Mode));
end;
Как мы помним, первый параметр функции обратного вызова для EnumWindows
содержит дескриптор найденного окна, а второй параметр может быть произвольным 4-байтным значением, которое система игнорирует, просто копируя сюда то значение, которое было передано при вызове EnumWindows
или EnumChildWindows
. Мы задействуем этот параметр для передачи ссылки на узел дерева, соответствующий родительскому окну. Также договоримся, что в свойство Data каждого узла будем записывать дескриптор связанного с ним окна. Для окон верхнего уровня ссылка будет иметь значение nil
— это обеспечивается тем, что при вызове EnumWindows второй параметр равен нулю (см. листинг 1.21).
Работа функции начинается с проверки того, что родительским окном для данного окна действительно является то окно, чей дескриптор связан с узлом родительского окна. Эта проверка нужна потому, что функция EnumChildWindows
перечисляет не только дочерние, но и "внучатые", "правнучатые" и т. д. окна. Нам здесь это не нужно, на каждом шаге нас интересуют только непосредственные "дети" окна, а до "внуков" мы доберемся, когда вызовем EnumChildWindows
для дочерних окон, поэтому и отсеиваем лишнее.
Следующий шаг — получение заготовка окна. Для этого мы используем сообщение WM_GETTEXT
(разница между этим сообщением и функцией GetWindowText
обсуждается в Text
типа string
. Сначала с помощью сообщения WM_GETTEXTLENGTH
мы узнаем длину заголовка окна, а затем выделяем под строку Text
требуемое количество памяти с помощью SetLength
. После этого можно получить строку с помощью сообщения WM_GETTEXT
. Второй параметр этого сообщения — адрес буфера, в который будет помещена строка. Так как переменная типа string
и есть указатель на буфер строки (это детально обсуждается в Text
к типу LParam
и передать получившееся значение.
Строго говоря, у нас здесь нигде нет параметра типа LPTSTR
, однако при работе с параметрами этого типа можно действовать точно так же: выделить для строки типа string нужное количество памяти и передать эту переменную, приведенную к типу LPTSTR
, в качестве параметра.
Далее получаем название класса окна. Для этого мы используем статический массив ClassName
, т. е. размер буфера определяется на этапе компиляции. С одной стороны, это неправильно, потому что ограничений на длину имени класса не существует (по крайней мере, в документации они не упомянуты), а мы уже говорили, что такой метод следует применять только тогда, когда существует известное на этапе компиляции ограничение длины. По с другой стороны, когда речь идет об имени класса, не существует ничего подобного сообщению WM_SETTEXTLENGTH
, т. е. API не дает возможности получить длину имени класса, что делает бессмысленными все манипуляции с размером буфера во время работы программы. Поэтому мы определяем размер буфера еще на этапе компиляции, исходя из того, что слишком уж длинные имена классов встречаются редко. При вызове функции с параметром типа LPTSTR
можно просто передавать массив без приведения типа, т. к. LPTSTR
— это PChar
, а массивы символов Char
, индексирующиеся с нуля, компилятор полагает совместимыми с этим типом и все необходимые преобразования делает неявно.