Для начала немного теории об устройстве Windows.
Приложение Windows состоят из множества объектов, которые обмениваются между собой сообщениями (messages). Источниками этих сообщений могут быть пользователь, оперирующий клавиатурой и мышью сама среда Windows, посылающая сообщения приложениям, другие приложение, обменивающиеся информацией с вашим приложением и, наконец, ваше приложение, посылающее сообщения компонентам.
Что же такое сообщение? Говоря просто, сообщение - это извещение о некотором событии.
Нетрудно догадаться, что в процессе работы Windows происходит множество событий, а значит, передается множество сообщений. А поскольку Windows – среда многозадачная, а процессор работает в режиме разделения времени между процессами, то сообщения не могут передаваться в один момент, и поэтому вынуждены выстраиваться в очередь.
Windows хранит очередь сообщений в коллекции записей (размером 28 байт каждая), содержащих следующие поля:
tagMSG = packed record hwnd: HWND; // Дескриптор окна-получателя message: UINT; // Идентификатор сообщения WParam: WPARAM; // Дополнительная информация (Longint) LParam: LPARAM; // Дополнительная информации (Longint) time: DWORD; // Время отправления сообщения pt: TPoint; // Положение указателя мыши в координатах // экрана в момент отправки сообщения end; ... TMsg = tagMSG;
В поле hwnd храниться дескриптор окна-получателя, которому адресовано сообщение.
Наиболее важное и всегда используемое в обработчиках сообщений поле message содержит целое значение, идентифицирующее данное сообщение. Не следует думать, что если поле message представляет собой беззнаковое целое типа LongWord, то сообщений действительно больше 2 миллиардов. В действительности зарезервированных сообщений Windows около тысячи, что тоже довольно много, чтобы даже попытаться запомнить все целочисленные значения сообщений.
Дополнительная, не менее важная, но используемая не во всех сообщениях информация содержится в полях сообщения WParam и LParam.
В поле time содержится информация о времени отправки сообщения (она нужна для установления очередности сообщений), а в поле pt – координаты указателя мыши координатах экрана в момент отправки сообщения.
Когда доходит очередь до определенного сообщения из очереди, то оно передается окну-адресату и это окно получает данные о том, какое именно сообщение и с какими параметрами ему передано. При этом окну-адресату не важен момент отправки сообщения и координаты указателя мыши – это внутренне дело Windows. Кроме того, не нужно окну и поле дескриптора, так как раз оно его приняло, то ему и адресовалось.
Поэтому окно-адресат из очереди сообщений получает только необходимую ему информацию, содержащуюся в переменной структурного типа TMessage общим размером 16 байт. Эта структура объявлена в модуле Windows следующим образом:
TMessage = packed record Msg: Cardinal; case Integer of 0: ( WParam: Longint; LParam: Longint; Result: Longint); 1: ( WParamLo: Word; WParamHi: Word; LParamLo: Word; LParamHi: Word; ResultLo: Word; ResultHi: Word); end;
В принципе этой структуры достаточно чтобы принимать и обрабатывать сообщения. Однако для каждого вида сообщения параметры WParam и LParam имеют свою смысловую трактовку и работать с ними весьма неудобно. В этой связи в модуле Messages объявлены специфические для каждого вида сообщений типы параметров. Они все одинакового размера в 16 байт и принципиально ничего не меняют в обработке сообщения, однако поля типов имеют осмысленные идентификаторы, упрощающие работу с сообщением.
Например, для событий мыши введен тип следующий тип TWMMove:
TWMMove = packed record Msg: Cardinal; Unused: Integer; case Integer of 0: ( XPos: Smallint; YPos: Smallint); 1: ( Pos: TSmallPoint; Result: Longint); end;
Из описания типа видно, что размер его не изменился и остался равен 16 байтам, но названия полей и их последовательность в записи позволяют легко работать со специфическими сообщениями.
В Delphi в модуле messages для каждого вида сообщений введен свой тип, определяющий тип переменной, передаваемой обработчику события.
Кроме того, для упрощения работы с сообщениями в модуле messages.pas содержаться объявления идентификаторов целочисленных констант, соответствующих сообщениям. Этот модуль является по сути дела частичным переводом на язык Delphi необходимых для работы с функциями API структур из заголовочного файла WINDEF.H, поставляемого Microsoft для программирования на языках C/C++. Наличие такого модуля позволяет оперировать мнемоническими именами сообщений вместо малопонятных целочисленных значений.
Общая последовательность обработки сообщений Windows следующая:
При создании оконного компонента – потомка TWinControl
, он
регистрируется в Windows и получает уникальный идентификатор – дескриптор (handle). Получить
доступ только для чтения к нему можно через свойство компонента Handle.
При регистрации окно передает Windows указатель на процедуру, которая будет вызываться при
получении им сообщений от Windows. Это метод MainWndProc
. Его
назначение – вызов метода обработки сообщений через свойство
WindowsProc
. По умолчанию в этом свойстве храниться ссылка на
процедуру WndProc
, но пользователь может изменить ее на сою во время
выполнения приложения.
Метод WndProc
представляет собой оператор case, анализирующий
идентификатор сообщения и производящий соответствующие действия. В конечном счете, все
сообщения передаются методу Dispatch, который просматривает таблицу методов класса объекта и
извлекает из нее тот, который имеет требуемый идентификатор сообщения. Если требуемый метод не
найден, то вызывается обработчик сообщения по умолчанию.
Таким образом, обработка сообщения Windows присходит по следующей цепочке:
событие > MainWndProc
>
WndProc
> Dispatch
>
обработчик события
Существует несколько типов сообщений, характерных для окон различного класса. Их идентификаторы начинаются с определенного префикса, что облегчает понимание того, какие события может принять окно определенного класса.
Префикс | Расшифровка | Для чего используются |
---|---|---|
BM | Button mode | Управление кнопками |
BN | Button notifications | Уведомление кнопок |
CB | Combobox | Управление выпадающими списками |
CBN | Combobox notifications | Уведомление выпадающих списков |
EM | Edit message | Управление редакторами Rich Edit |
FM | File manager | Управление файловым менеджером |
LB | Listbox | Управление списками строк |
LBN | Listbox notifications | Уведомление списков строк |
SB | Scrollbar | Управление полосами прокрутки |
WM | Window message | Управление окнами |
Аргументами для передачи и результатами приема сообщения являются целочисленные константы, которые для удобства работы с ними имею свои идентификаторы. При работе с сообщениями важно знать какого вида константа может использоваться в данном контексте. В таблице приведен перечень видов констант, наиболее часто используемых при работе с сообщениями.
Префикс | Расшифровка | Смысловое содержание |
---|---|---|
BS | ButtonStyle | Стиль кнопки |
CBS | ComboBoxStyle | Стиль выпадающего списка |
ES | EditStyle | Стиль редактора |
LBS | ListBoxStyle | Стиль списка строк |
PS | PenStyle | Стиль пера рисования |
SBS | ScrollBarStyle | Стиль полосы прокрутки |
SM | SystemMetrics | Системные метрики |
SPI | SystemParametersInfo | Системные параметры |
WS | WindowStyle | Стили окон |
Обработка сообщений означает, что приложение будет тем или иным образом реагировать на полученные от операционной системы сообщения. В стандартном приложении Windows обработка сообщений сосредотачивается в процедурах окна. Delphi, частично обрабатывая сообщения, упрощает работу программиста, позволяя вместо одной процедуры для обработки всех типов сообщений создавать независимые процедуры для обработки сообщений различных типов.
Все процедуры обработки сообщений должны отвечать следующим требованиям:
Процедура должна быть методом объекта;
Процедуре должен передаваться один передаваемый по ссылке параметр (т.е. с помощью описания var). Тип параметра должен быть TMessage или другой, зависящий от типа специализированного сообщения;
Описание процедуры должно включать ключевое слово message, за которым должна следовать константа, задающая обрабатываемое сообщение.
Пример 6.1. Пример объявления процедуры, обрабатывающей сообщение WM_SYSCOMMAND
procedure OnMinMaximize (var Msg:TWMSysCommand); message WM_SYSCOMMAND;
Теперь в разделе implementation модуля добавляем определение процедуры (в этом случае указание ключевого слова message не требуется):
Пример 6.2.
procedure TForm1.OnMinMaximize(var Msg: TWMSysCommand); begin case Msg.CmdType of SC_MINIMIZE : if MessageDlg('Ну что? Типа сворачиваемся?',mtConfirmation,[mbYes, mbNo],0)=mrYes then inherited; SC_MAXIMIZE : inherited; else inherited; end; end;
Обратите внимание на ключевое слово inherited, которое позволяет передать сообщение обработчику этого сообщения, принадлежащему классу-предку, т.е. если бы мы в нашем случае не указали бы это слово, окно не смогло бы должным образом реагировать на другие системные команды, а выполнялось бы только то, что было описано в области реализации нашего приложения.