TCreateParams = record
Caption: PChar;
Style: WORD;
ExStyle: DWORD;
X, Y: Integer;
Width, Height: Integer;
WndParent: HWnd;
Param: Pointer;
WindowClass: TWndClass;
WinClassName: array[0..63] of Char;
end;
В записи Params хранятся параметры как окна, передаваемые в функцию WindowCreateEx, так и оконного класса (поля WindowClass и WndClassName). Все поля инициализируются методом CreateParams на основе значений свойств оконного компонента. Данный метод виртуальный и может быть перекрыт в наследниках, что бывает полезно, когда необходимо изменить стиль создаваемого окна. Например, добавив расширенный стиль WS_EX_CLIENTEDGE (или, как вариант, WS_EX_STATICEDGE), можно получить окно с необычной рамкой (листинг 1.11).
Листинг 1.11. Перекрытие метода CreateParamsprocedure TForm1.CreateParams(var Params: TCreateParams);
begin
// Вызов унаследованного метода заполнения всех полей
// записи Params
inherited CreateParams(Params);
// Добавляем флаг WS_EX_CLIENTEEDGE к расширенному стилю окна
Params.ExStyle := Params.ExStyle or WS_EX_CLIENTEDGE;
end;
ПримечаниеВ разд. 1.1.4 мы говорили, что имя оконного класса, который VCL создает для оконного компонента, совпадает с именем класса этого компонента. Здесь мы видим, что на самом деле имя оконного класса можно сделать и другим, для этого достаточно изменить значение поля Params.WinClassName.
Обратите внимание, что всем без исключения классам метод CreateWnd назначает одну и ту же оконную процедуру — InitWndProc. Это является основой в обработке сообщений с помощью VCL, именно поэтому оконная процедура назначается не в методе CreateParams, а в методе CreateWnd, чтобы в наследниках нельзя было изменить это поведение (метод CreateWnd тоже виртуальный, но при его переопределении имеет смысл только добавлять какие-то действия, а не изменять поведение унаследованного метода).
Чтобы понять, как работает процедура InitWndProc, обратите внимание на еще одну особенность метода CreateWnd: перед вызовом CreateWindowHandle (т.е. непосредственно перед созданием окна) он записывает ссылку на текущий объект в глобальную переменную СreationСontrol. Эта переменная затем используется процедурой InitWndProc (листинг 1.12).
Листинг 1.12. Оконная процедура InitWndProcfunction InitWndProc(HWindow: HWnd; Message, WParam, LParam: LongInt): LongInt;
begin
CreationControl.FHandle := HWindow;
SetWindowLong (HWindow, GWL_WNDPROC, LongInt(CreationControl.FObjectInstance));
if (GetWindowLong(HWindow, GWL_STYLE) and WS_CHILD <> 0) and (GetWindowLong(HWindow, GWL_ID) = 0) then
SetWindowLong(HWindow, GWL_ID, HWindow);
SetProp(HWindow, MakeIntAtom(ControlAtom), THandle(CreationControl));
SetProp(HWindow, MakeIntAtom(WindowAtom), THandle(CreationControl));
asm
PUSH LParam
PUSH WParam
PUSH Message
PUSH HWindow
MOV EAX, CreationControl
MOV CreationControl, 0
CALL [EAX].TWinControl.FObjectInstance
MOV Result, EAX
end;
end;
ПримечаниеКод функции InitWndProc в листинге 1.12 взят из Delphi 7. В более поздних версиях код включает в себя поддержку окон, работающих с кодировкой Unicode, поэтому там предусмотрен выбор между ANSI- и Unicode-вариантами функций API (подробнее об ANSI- и Unicode-вариантах см разд. 1.1.12). Такой код сложнее понять из-за этих дополнений. Кроме того, из листинга 1.12 убрано все, что относится к компиляции под LINUX, чтобы не засорять листинг.
Из листинга 1.12 видно, что оконная процедура InitWndProc не обрабатывает сама никаких сообщений, а просто переназначает оконную процедуру у окна. Таким образом, InitWndProc для каждого окна вызывается только один раз, чтобы переназначить оконную процедуру. Обработка того сообщения, которое привело к вызову InitWndProc, тоже передается в эту новую процедуру (ассемблерная вставка в конце InitWndProc делает именно это). При просмотре этого кода возникают два вопроса. Первый — зачем писать такую оконную процедуру, почему бы не назначить нужную процедуру обычным образом? Здесь все дело в том. что стандартными средствами оконная процедура назначается одна на весь оконный класс, в то время как по внутренней логике VCL каждый экземпляр компонента должен иметь свою собственную оконную процедуру. Добиться этого можно только порождением подкласса уже после создания окна. Указатель на свою уникальную оконную процедуру (откуда эта процедура берется и почему она должна быть уникальной, мы поговорим в следующем разделе) каждый экземпляр хранит в поле FObjectInstance. Значение глобальной переменной CreationControl присваивается, как мы помним, непосредственно перед созданием окна, а первое свое сообщение окно получает буквально в момент создания. Так как VCL — принципиально однонитевая библиотека, ситуация, когда другой код вклинивается между присваиванием значения переменной CreationControl и вызовом InitWndProc, невозможна, так что в InitWndProc попадает правильная ссылка на создаваемый объект.
Второй вопрос — зачем так сложно? Почему в методе CreateWnd сразу после создания окна нельзя было вызвать SetWindowLong и установить нужную оконную процедуру там, вместо того чтобы поручать это процедуре InitWndProc? Здесь ответ такой: это сделано потому, что свои первые несколько сообщений (например, сообщения WM_CREATE и WM_NCCREATE) окно получает до того, как функция CreateWindowEx завершит свою работу. Чтобы завершить создание окна, CreateWindowEx отправляет несколько сообщений окну, и только после того как окно обработает их должным образом, процесс создания окна считается завершенным. Так что назначать уникальную оконную процедуру после завершения CreateWindowEx — это слишком поздно. Именно поэтому уникальная оконная процедура назначается таким неочевидным и несколько неуклюжим способом.