1.1.7. Создание окон средствами VCL
1.1.7. Создание окон средствами VCL
Теперь поговорим о том, как в VCL создаются окна. Речь здесь будет идти не о написании кода для создания окна с помощью VCL (предполагается, что читатель это и так знает), а о том, какие функции API и в какой момент вызывает VCL при создании окна.
Если смотреть код методов класса TWinControl, которые вызываются при создании и отображении окна, то найти там то место, когда окно создается, удается не сразу. На первый взгляд все выглядит так, будто этот код вообще не имеет отношения к созданию окна, как будто оно создается где-то совсем в другом месте, а TWinControl получает уже готовый дескриптор. На самом деле окно создает, конечно же, сам TWinControl, а спрятано его создание в свойстве Handle. Метод GetHandle, который возвращает значение свойства Handle, выглядит следующим образом (листинг 1.8).
Листинг 1.8. Реализация метода TWinControl.GetHandle
procedure 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. Реализация метода CreateWnd
procedure 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
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. Перекрытие метода CreateParams
procedure 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. Оконная процедура InitWndProc
function 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 — это слишком поздно. Именно поэтому уникальная оконная процедура назначается таким неочевидным и несколько неуклюжим способом.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКДанный текст является ознакомительным фрагментом.
Читайте также
2.4. Интеграция со средствами разработки ПО
2.4. Интеграция со средствами разработки ПО Обычно, программный продукт проходит стадии разработки, представленные на рис. 3. Рис. 3. Стадии разработки ПОВ [24] описан способ, позволяющий уменьшить общее время разработки программного продукта за счет объединения средств
3.4. Интеграция со средствами разработки ПО
3.4. Интеграция со средствами разработки ПО При мониторинге особое внимание уделяется работе псевдоагентов, которые закладываются в код программы на этапе компиляции. Для их успешной работы по сбору необходимой информации требуется наличие на целевой машине ряда
Создание брандмауэра средствами iptables
Создание брандмауэра средствами iptables Утилита iptables может решать различные задачи. Одной из таких задач является создание брандмауэров. Брандмауэр можно реализовать как на компьютере, выполняющем функции маршрутизатора, так и на рабочих станциях или серверах. При
Глава 2. Создание диалоговых окон
Глава 2. Создание диалоговых окон В данной главе вы научитесь создавать диалоговые окна с использованием средств разработки Qt. Диалоговые окна предоставляют пользователю возможность задавать необходимые значения параметров и выбирать определенные режимы работы. Они
Глава 3. Создание главных окон
Глава 3. Создание главных окон В данной главе вы научитесь создавать главные окна при помощи средств разработки Qt. К концу главы вы будете способны построить законченный графический пользовательский интерфейс приложения, который имеет меню, панели инструментов и
ГЛАВА 19. Создание окон с помощью System.Windows.Forms
ГЛАВА 19. Создание окон с помощью System.Windows.Forms Если вы прочитали предыдущие 18 глав, вы должны иметь солидную базу дли использования языка программирования C# и архитектуры .NET. Вы, конечно же, можете применить полученные знания для построения консольных приложений следующего
Создание дочерних окон
Создание дочерних окон Заключительным шагом должно быть создание подходящей реализации обработчика событий Файл?Создать родительской формы. Теперь, когда дочерняя форма определена, соответствующая программная логика оказывается очень простой: нужно создать и
Создание файла *.resx программными средствами
Создание файла *.resx программными средствами Как было отмечено выше, файл *.resx содержит XML-данные, представляющие пары имен и значений для каждого ресурса приложения. Класс ResXResourceWriter предлагает набор членов, с помощью которых вы можете создать файл *.resx, добавить в него
Создание пользовательских диалоговых окон
Создание пользовательских диалоговых окон Теперь, когда вы понимаете роль базовых элементов управления Windows Forms и суть процесса построения пользовательских элементов управления, давайте рассмотрим вопрос создания пользовательских диалоговых окон. Здесь хорошей
Создание собственных диалоговых окон
Создание собственных диалоговых окон Сложные приложения часто используют несколько форм. Например, во многих программах имеется диалоговое окно О программе, в котором отображаются информация о программе, номер версии, сведения об авторе и логотип компании.Для создания
Создание стен, дверей и окон в режиме конструирования
Создание стен, дверей и окон в режиме конструирования Строительный план имеет трехмерное представление. Оно является неотъемлемой частью плана, реализующей дизайнерскую составляющую проекта. В режиме просмотра 3D-модели можно добавлять, удалять и редактировать
Создание окон
Создание окон Чтобы создать окно, необходимо выполнить приведенную ниже последовательность действий.1. Активизировать этаж с построенным в предыдущем примере контуром стены.2. Нажать кнопку палитры инструментов.3. Открыть окно настройки параметров окна, нажав
Создание приложения средствами MFC AppWizard
Создание приложения средствами MFC AppWizard Во второй главе книги мы рассматривали приложение MFDialog, которое не имеет главного окна. Вместо окна это приложение использует обыкновенную диалоговую панель. Сейчас мы расскажем вам как создать приложение, подобное MFDialog, не набрав
Создание окон
Создание окон Рассмотрим пример создания окна.1. Активизируйте первый этаж вашего проекта.2. Выберите инструмент Window (Окно) щелчком на соответствующей кнопке палитры инструментов.3. Вызовите окно настройки параметров окна щелчком на кнопке информационной палитры.4.
Архивация средствами Windows
Архивация средствами Windows Сегодня встроенные средства архивации имеет практически каждая операционная система. В операционных системах семейства Unix широко распространены производные от ZIP форматы: TGZ, BZip2, а также сам ZIP. В Windows поддерживается только ZIP, чего, впрочем,
Запись средствами Windows
Запись средствами Windows Если на компьютере не установлена специальная программа для записи, то остается пользоваться средствами вашей операционной системы. Сделать это очень просто. Прежде всего просмотрите жесткий диск и выберите файлы (либо папки), которые необходимо