В качестве примера рассмотрим создание подкласса для некоторого окна, дескриптор которого содержится в переменной Wnd. Пусть нам потребовалось для этого окна нестандартным образом обрабатывать сообщение WM_KILLFOCUS.
Тогда код новой оконной процедуры и код ее установки будет выглядеть так, как показано в листинге 1.7.
Листинг 1.7. Создание подкласса для особой обработки сообщения WM_KILLPFOCUSvar
OldWndProc: TFNWndProc;
function NewWindowProc(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM) : LRESULT; stdcall;
begin
if Msg = WM_KILLFOCUS then
// Обработка события
else
Result := CallWindowProc(OldWndProc, hWnd, Msg, wParam, lParam);
end;
...
// Установка новой оконной процедуры окну Wnd
OldWndProc := TFNWndProc(SetWindowLong(Wnd, GWL_WNDPROC, Longint(@NewWindowProc)));
...
ПримечаниеMSDN называет функции GetWindowLong и SetWindowLong устаревшими и рекомендует использовать вместо них GetWindowLongPtr и SetWindowLongPtr, совместимые с 64-разрядными версиями Windows. Однако до 2007-й версии Delphi включительно эти функции отсутствуют в модуле Windows, и при необходимости их следует импортировать самостоятельно.
Переопределять оконную процедуру с помощью SetWindowLong можно и у тех окон, оконная процедура которых была переопределена ранее. Таким образом создаются цепочки оконных процедур, каждая из которых вызывает предыдущую.
1.1.7. Создание окон средствами VCL
Теперь поговорим о том, как в VCL создаются окна. Речь здесь будет идти не о написании кода для создания окна с помощью VCL (предполагается, что читатель это и так знает), а о том, какие функции API и в какой момент вызывает VCL при создании окна.
Если смотреть код методов класса TWinControl, которые вызываются при создании и отображении окна, то найти там то место, когда окно создается, удается не сразу. На первый взгляд все выглядит так, будто этот код вообще не имеет отношения к созданию окна, как будто оно создается где-то совсем в другом месте, а TWinControl получает уже готовый дескриптор. На самом деле окно создает, конечно же, сам TWinControl, а спрятано его создание в свойстве Handle. Метод GetHandle, который возвращает значение свойства Handle, выглядит следующим образом (листинг 1.8).
Листинг 1.8. Реализация метода TWinControl.GetHandleprocedure TWinControl.HandleNeeded;
begin
if FHandle = 0 then
begin
if Parent <> nil then Parent.HandleNeeded;
CreateHandle;
end;
end;
function TWinControl.GetHandle: HWnd;
begin
HandleNeeded;
Result := FHandle;
end;
При каждом обращении к свойству Handle вызывается метод HandleNeeded, который проверяет, создано ли уже окно, и если нет, создает его, попутно создавая, при необходимости, родительское окно. Таким образом, окно создается при первом обращении к свойству Handle.
Метод CreateHandle, который вызывается из HandleNeeded, выполняет непосредственно лишь несколько вспомогательных операций, а для создания окна вызывает еще один метод — CreateWnd (листинг 1.9).
Листинг 1.9. Реализация метода CreateWndprocedure TWndControl.CreateWnd;
var
Params: TCreateParams;
TempClass: TWndClass;
ClassRegistered: Boolean;
begin
CreateParams(Params);
with Params do
begin
if (WndParent = 0) end (Style and WS_CHILD <> 0) then
if (Owner <> nil) end (csReading in Owner.ComponentState) and (Owner is TWinControl) then
WndParent TWinControl(Owner).Handle
else
raise EInvalidOperation.CreateFmt(SParentRequired, [Name]);
FDefWndProc := WindowClass.lpfnWndProc;
ClassRegistered := GetClassInfo(WindowClass.hInstance, WinClassName, TempClass);
if not ClassRegistered or (TempClass.lpfnWndProc <> @InitWndProc) then
begin
if (ClassRegistered then
Windows.UnregisterClass(WinClassName, WindowClass.hInstance);
WindowClass.lpfnWndProc := InitWndProc;
WindowClass.lpszClassName := WinClassName;
if Windows.RegisterClass(WindowClass) = 0 then RaiseLastOSError;
end;
CreationControl := Self;
CreateWindowHandle(Params);
if FHandle = 0 then RaiseLastOSError;
if (GetWindowLong(FHandle, GWL_STYLE) and WS_CHILD <> 0) and (GetWindowLong(FHandle, GWL_ID) = 0) then
SetWindowLong(FHandle, GWL_ID, FHandle);
end;
StrDispose(FText);
FText := nil;
UpdateBounds;
Perform(WM_SETFONT, FFont.Handle, 1);
if AutoSize then AdjustSize;
end;
Собственно создание окна опять происходит не здесь, а в методе CreateWindowHandle, который очень прост: он состоит из одного только вызова API-функции CreateWindowEx с параметрами, значения которых берутся из полей записи Params типа TCreateParams (листинг 1.10)
Листинг 1.10. Запись TCreateParams