Что такое сообщение? Это информация о некотором изменении в пользовательском интерфейсе, например перемещение окна или нажатие клавиши на клавиатуре. Сообщения также могут рассылаться другими приложениями. Сообщения используются для операций с совместным использованием данных. Формат сообщений в win32api строго определен и будет объяснен далее.
С точки зрения программиста, сообщение — это целое значение без знака, которому для простоты чтения назначена символьная константа, начинающаяся (как правило) с символов WM_ (Window Message). Например, сообщение WM_LBUTTONDOWN говорит программе, что пользователь нажал левую кнопку мыши, WM_LBUTTONUP говорит, что пользователь отпустил левую кнопку мыши.
Каждая Windows программа занимается лишь тем, что реагирует на получаемые сообщения. В Delphi операции с сообщениями скрыты глубоко в VCL, причем назначая обработчик какого-либо события, вы на самом деле назначаете обработчик соответствующего сообщения. Естественно что не все сообщения реализованы в Delphi как события и с этими сообщениями нужно работать самостоятельно.
В каждой программе, для Windows имеется так называемый цикл обработки сообщений. В самой минимальной программе он может выглядеть так:
while GetMessage(Mesg,0,0,0) do begin TranslateMessage(Mesg); DispatchMessage(Mesg); end; |
Для каждой программы существует так называемая очередь сообщений (message queue). Когда в системе происходит какое-нибудь событие, Windows формирует сообщение и помещает его в очередь. Программа извлекает сообщения из очереди при помощи цикла и функции GetMessage. В приведенном выше примере фигурирует переменная Mesg типа TMsg, который определён так:
TMsg = packed record hwnd: HWND; //описатель окна, которому направлено сообщение message: UINT; //идентификатор сообщения wParam: WPARAM; //параметр сообщения lParam: LPARAM; //параметр сообщения time: DWORD; //время, когда сообщение было послано pt: TPoint; //координаты мыши в момент посылания сообщения end; |
Тип TPoint определён так:
TPoint = record x: Longint; y: Longint; end; |
Цикл работает следующим образом. Функция GetMessage передает Windows запись типа TMsg, эта запись заполняется параметрами ждущего в очереди сообщения. После вызова сообщение извлекается из очереди. Для любого сооющения, кроме WM_QUIT, функция возвращает TRUE. После того, как сообщение получено приложением, оно должно быть обработано. Это делают две функции в теле цикла – TranslateMessage и DispatchMessage. Функция TranslateMessage преобразует низкоуровневые сообщения клавиатуры (о нажатиях клавиш) в более осмысленные WM_CHAR, WM_SYSCHAR, WM_DEADCHAR, WM_SYSDEADCHAR, которые затем посылаются в очередь. Функция DispatchMessage вызывает соответствующую оконную процедуру, для которой предназначено сообщение. Пока сообщение не обработается, выполнение DispatchMessage продолжается.
Существует две главных группы сообщений – системные и частные. Системные сообщения посылаются системоя для взаимодействия системы с программой. Приложения также может само рассылать системые сообщения. Каждое системное сообщение имеет уникальный численный идентификатор и соответствующуюю константу, определенную в файле messages.pas. Для системных сообщений зарезервирован диапазон от $0000 до $03FF. Например, сообщение WM_CREATE посылается при создании окна, причем до того как оно отобразилось на экране. Системные сообщения в свою очередь делятся на группы, которые можно определить, если посмотреть на перфикс константы сообщения.Так, для WM_CREATE префикс – WM. Вот небольшой список префиксов, соответствующих им типов сообщений и примеры сообщений соответствующего типа.
Префикс | Описание типа | Примеры сообщений |
WM | общие оконные сообщения | WM_KEYUP, WM_DESTROY |
LB | сообщения списков (listbox) | LB_ADDFILE, LB_GETCOUNT |
EM | сообщения блоков редактирования и окон класса richedit | EM_CANPASTE, EM_GETLINE, EM_SETSEL |
Частные сообщения нужны, например, для осуществления взаимодействия между окнами вашего приложения, находящихся в разных процессах. К частным сообщениям можно также отнести и так называемые callback-сообщения – это сообщения, которые определяете вы, но посылает вам их система. Для частных сообщений выделен диапазон от $0400. Для задания частных сообщений существует специальная константа – WM_USER, вы можете создавать сообщения, например, так:
const MY_MESSAGE=WM_USER+1234; |
При таком задании частных сообщений возникает проблема уникальности сообщений, действительно, нет никаких препятствий для другой программы определить частное сообщение с таким же номером, как и у вас, в этом случае может возникнуть неприятная ситуация. Для ей предотвращения следует использовать функцию RegisterWindowMessage для получения гарантировано уникального номера сообщения. Вот заголовок этой функции:
function RegisterWindowMessage(lpString: PChar): UINT; |
Параметр lpString определяет некоторую строку, по которой и происходит идентификация сообщения. При первом вызове этой функции Windows находит свободный номер из диапазона $C000–$FFFF, помечает его как занятый и возвращает вызывающей программе. При повторном вызове с той же строкой lpString возвращается уже запомненный номер сообщения, в результате чего все экземпляры программы получают одинаковый идентификатор сообщения.
После того как сообщение покидает цикл сбора, оно попадает в соответствующую оконную процедуру. Оконная процедура – это написанная вами функция, которая принимает и обрабатывает сообщения, посылаемые окну. Каждый оконный класс имеет оконную процедуру и все экземпляры окон одного класса используют одну и ту же процедуру. Оконная процедура выполняет те или иные действия в зависимости от типа сообщения, однако она не может игнорировать ряд системных сообщений, их следует передавать в оконную процедуру по умолчанию (DefWindowProc). Вот пример оконной процедуры из программы winmin (иногда я буду называть оконную процедуру оконной функцией – это одно и то же).
function WindowProc(wnd:HWND; Msg : Integer; wParam: WPARAM; lParam: LPARAM):LRESULT; stdcall; var nCode, ctrlID : word; begin case msg of WM_COMMAND : begin nCode:=HIWORD(wParam); ctrlID:=LOWORD(wParam); case ctrlID of IDCANCEL : begin DestroyWindow(wnd); result := 0; end; else result := 1; end; end; WM_DESTROY : begin postquitmessage(0); Result:=0; end; else Result:=DefWindowProc(wnd,msg,wParam,lParam); end; |
В данной оконной функции происходит обработка двух сообщений WM_COMMAND и WM_DESTROY, все остальные сообщения перенаправляются в оконную процедуру по умолчанию DefWindowProc(wnd,msg,wParam,lParam). Параметры функции WindowProc имеют такое значение:
Вся полезная информация обычно содержится в параметрах wParam и lParam. Типы WPARAM и LPARAM на самом деле одинаковы и равны 32-битному целому. Часто информация содержится в старшем и младшем словах этих параметров, для её извлечения предусмотрены две функции: LoWord и HiWord, которые возвращают соответственно младшее и старшее слово. (Напомню, что 32-битное целое состоит из четырёх байтов, слово состоит из двух байтов, поэтому в 32-битной переменной можно выделить два слова, их называют старшее и младшее. Одно слово можно также разложить на старший и младший байты, для их извлечения существуют функции HiByte и LoByte)
В нашем примере обрабатывается сообщение WM_COMMAND, из параметра wParam выделяются старшее и младшее слова, и записываются в переменные nCode и ctrlID соответственно. Для данного сообщения они имеют следующий смысл – ctrlID содержит идентификатор дочернего элемента управления (или элемента меню, или акселератора), а nCode содержит уведомляющее сообщение от дочернего элемента управления или равен нулю, если сообщение пришло от меню. В нашем случае ловим сообщение от элемента управления либо меню с идентификатором IDCANCEL (это один из системных идентификаторов), и когда поймаем, уничтожим окно. В случае когда мы обрабатываем сообщение WM_COMMAND, возвращаем значение 0, если нет, то 1.
Пиложение может самостоятельно рассылать сообщения. Для этого существуют несколько функций, самые важные из которых – SendMessage и PostMessage. Эти две функии имеют почти одинаковые заголовки:
function SendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; function PostMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): BOOL; |
Параметры имеют следующее значение:
Главное различие между этими двумя функциями заключается в том, что первая ставит сообщение в очередь и ждет, когда оконная процедура обработает его и возвращает значение, переданное оконной функцией. Функция PostMessage просто помещает сообщение в очередь и не дожидается его обработки. Вам следует точно знать, когда какую функцию требуется применить. Например, вы не можете в частном сообщении (из диапазона >WM_USER) передавать указатели через PostMessage, так как когда очередь дойдет до вашего сообщения, поток, передавший указатель, завершится и адресуемая указателем память может освободиться. Используйте PostMessage только тогда, когда вам не важен возвращаемый результат.