создают диалоговое окно и сразу же показывают его в модальном режиме. Данные функции завершают свое выполнение только тогда, когда модальное окно будет закрыто. Внутри модальных функций организуется собственная петля сообщений. Все прочие окна на время показа модального диалога запрещаются (как если бы для них была вызвана функция
EnableWindow
с параметром
FALSE
), т.е. перестают реагировать на сообщения от мыши и клавиатуры. При этом они сохраняют способность реагировать на другие сообщения, благодаря чему могут, например, обновлять свое содержимое по таймеру (в справке написано, что ничто не мешает программисту вставить в диалоговую процедуру вызов функций, разрешающих запрещенные системой окна, но
при этом теряется смысл модальных диалогов). Если в очереди нет сообщений, модальная петля посылает родительскому окну диалога сообщение
WM_ENTERIDLE
, обработка которого позволяет этому окну выполнять фоновые действия. Разумеется, что обработчик
WM_ENTERIDLE
не должен выполняться слишком долго, иначе модальное окно зависнет. Обычно окно использует оконную процедуру, которая задана при создании соответствующего оконного класса. Однако допускается создание так называемых подклассов — переопределение оконной процедуры после того, как окно создано. Это переопределение касается только заданного окна и не оказывает влияния на остальные окна, принадлежащие данному оконному классу. Осуществляется оно с помощью функции
SetWindowLong
с параметром
GWL_WNDPROC
(другие значения этого параметра позволяют менять другие свойства окна, такие как стиль и расширенный сталь). Изменять оконную процедуру можно только у окон, созданных самим процессом.
Новая оконная процедура, которая устанавливается при создании подкласса, все необработанные сообщения должна передавать не функции
DefWindowProc
, а той оконной процедуре, которая была установлена ранее.
SetWindowLong
при изменении оконной процедуры возвращает дескриптор старой процедуры (этот же дескриптор можно получить, заранее вызвав функцию
GetWindowLong
с аргументом
GWL_WINDOWPROC
). Обычно значение дескриптора численно совпадает с адресом старой оконной процедуры, поэтому в некоторых источниках можно встретить рекомендации использовать этот дескриптор непосредственно как указатель процедурного типа. И это даже будет работать для оконных классов, созданных самой программой. Но безопаснее все же вызов старой оконной процедуры реализовать с помощью системной функции
CallWindowProc
, предоставив ей "разбираться", является ли дескриптор указателем.
В качестве примера рассмотрим создание подкласса для некоторого окна, дескриптор которого содержится в переменной
Wnd
. Пусть нам потребовалось для этого окна нестандартным образом обрабатывать сообщение
WM_KILLFOCUS
.
Тогда код новой оконной процедуры и код ее установки будет выглядеть так, как показано в листинге 1.7.
Листинг 1.7. Создание подкласса для особой обработки сообщения
, совместимые с 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.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