Сообщения windows приложения для пользователя

Основы работы с Windows API

Основы работы с Windows API — Сообщения Windows

Содержание материала

Сообщения Windows

Человеку, знакомому с Delphi, должна быть ясна схема событийного управления. Программист пишет только код реакции на какое-либо событие, а дальше программа ждёт, когда система сочтёт, что настало время передать управление этому участку кода. Простые программы в Delphi состоят исключительно из методов реакции на события вроде OnCreate, onclick, OnCloseQerry и т. д. Причём событием называется не только событие в обычном смысле этого слова, то есть когда происходит что-то внешнее, но и ситуация, когда событие используется просто для передачи управления основной программе в тех случаях, когда VCL не может сама справиться с какой-то задачей. Примером такого события является, например, TListBox.OnDrawItem. Устанавливая стиль списка в lbOwnerDrawFixed или lbOwnerDrawVariable, программист как бы сообщает VCL, что он не доволен теми средствами рисования элементов списка, которыми она располагает, и что он берёт эту часть задачи на себя. И каждый раз, когда возникает необходимость в рисовании элемента, VCL передаёт управление специально написанному коду. На самом деле разница между двумя типами событий весьма условна. Можно так же сказать, что когда пользователь нажимает клавишу, VCL не знает, что делать, и поэтому передаёт управление обработчику OnKeyPress.

Событийное управление не есть изобретение авторов Delphi. Такой подход исповедует сама система Windows. Только здесь события называются сообщениями (message), что, на мой взгляд, даже лучше отражает ситуацию. Windows посылает программе сообщения, связанные либо с тем, что произошло что-то внешнее (мышь, клавиатура. ), либо с тем, что самой системе потребовались от программы какие-то действия. Самым распространённым таким действием является предоставление информации. Например, когда Windows хочет узнать заголовок окна, она посылает этому окну специальное сообщение, в ответ на которое окно должно сообщить системе свой заголовок. Ещё бывают сообщения, которые просто уведомляют программу о начале какого-то действия (например, о начале перетаскивания окна) и предоставляют возможность вмешаться. Но это вмешательство необязательно.

В Delphi для реакции на каждое событие обычно создаётся свой метод. В Windows одна процедура, называемая оконной, обрабатывает все сообщения. В языке Си нет понятия , поэтому при использовании Паскаля может возникнуть путаница. Дело в том, что то, что называется оконной процедурой, на самом деле является функцией. Тем не менее, я буду использовать общепринятый термин . Каждое сообщение имеет свой уникальный номер, а оконная процедура обычно целиком состоит из оператора case, и каждому сообщению соответствует своя альтернатива этого оператора. Номера сообщений учить не надо, потому что можно использовать константы, описанные в модуле Messages.dcu. Эти константы начинаются с префикса, указывающего на принадлежность сообщения к какой-то группе. Например, сообщения общего назначения начинаются с WM_: например, WM_Paint, WM_GetTextLength. Сообщения, специфичные, например, для кнопок, начинаются с префикса BM_. Остальные группы сообщений также связаны либо с теми или иными элементами управления, либо со специальными действиями, например, с динамическим обменом данными (dynamic data exchange, DDE). Обычной программе приходится обрабатывать довольно много сообщений, поэтому оконная процедура бывает, как правило, очень длинной и громоздкой. Оконная процедура описывается программистом как callback функция и указывается при создании оконного класса. Таким образом все окна данного класса имеют одну и ту же оконную процедуру. Впрочем, существует возможность породить так называемый подкласс, то есть новый класс, наследующий все свойства существующего, за исключением оконной процедуры. Несколько подробнее об этом будет сказано далее.

Кроме номера, каждое сообщение содержит два параметра — WParam и LParam. Буквы и означают и , то есть первый параметр 16-разрядный, а второй — 32-разрядный. Однако так было только в старых, 16-разрядных версиях Windows. В 32-разрядных версиях оба параметра 32-разрядные, несмотря на их названия. Конкретный смысл каждого параметра зависит от сообщения. В некоторых сообщениях один или оба параметра могут вообще не использоваться, в других — наоборот, двух параметров даже не хватает. В этом случае один из параметров (обычно LParam) содержит указатель на дополнительные данные. После обработки сообщения оконная процедура должна вернуть какое-то значение. Обычно это значение просто сигнализирует, что сообщение не нуждается в дополнительной обработке, но в некоторых случаях оно более осмысленно, например, WM_SetIcon должно вернуть дескриптор иконки, которая была установлена ранее. Если программист не хочет обрабатывать сообщение самостоятельно, он должен вызвать для его обра-ботки функцию DefWindowProc.

Обработка сообщения требует времени, иногда довольно значительного. За это время окну может быть отправлено ещё несколько сообщений. Чтобы они не пропали, Windows организует так называемую очередь сообщений. Очередь сообщений своя для каждой нити. Нить должна сама выбирать сообщения из этой очереди, транслировать их и затем вызывать функцию Dispatch-Message, чтобы направить это сообщение в нужную оконную процедуру. Всё это лучше не писать самому, а оставить на совести VCL, которая прекрасно с этим справляется. При программировании в Delphi обычно требуется либо нестандартная реакция на сообщение, либо отправка сообщения другому окну.

Читайте также:  Не могу установить windows loader

Отправка сообщения другому окну может осуществляться достаточно разнообразными способами. Можно послать сообщение в очередь, а можно заставить Windows вызвать оконную процедуру напрямую, в обход очереди. Можно установить максимальное время ожидания отклика от окна, которому послано сообщение. Все эти функции хорошо описаны в справке (см., например, функцию SendMessage и группу родственных функций).

Кроме параметров WParam и LParam, каждому сообщению приписывается время возникновения и координаты курсора в момент возникновения. Эти параметры можно узнать с помощью функций GetMessagePos и GetMessageTime.

Операционная система Microsoft Windows 3.1 для программиста

1.4. Обработка сообщений

Откуда берутся сообщения?

Большинство сообщений создают драйверы устройств ввода/вывода, таких, как клавиатура, мышь или таймер. Драйверы создают сообщения при поступлении аппаратных прерываний. Например, когда вы нажимаете и затем отпускаете клавишу, драйвер обрабатывает прерывания от клавиатуры и создает несколько сообщений. Аналогично сообщения создаются при перемещении мыши или в том случае, когда вы нажимаете кнопки на корпусе мыши. Можно сказать, что драйверы устройств ввода/вывода транслируют аппаратные прерывания в сообщения.

Куда направляются сообщения, созданные драйверами?

Прежде всего сообщения попадают в системную очередь сообщений Windows. Системная очередь сообщений одна. Далее из нее сообщения распределяются в очереди сообщений приложений. Для каждого приложения создается своя очередь сообщений.

Очередь сообщения приложений может пополняться не только из системной очереди. Любое приложение может послать сообщение любому другому сообщению, в том числе и само себе.

Основная работа, которую должно выполнять приложение, заключается в обслуживании собственной очереди сообщений. Обычно приложение в цикле опрашивает свою очередь сообщений. Обнаружив сообщение, приложение с помощью специальной функции из программного интерфейса Windows распределяет его нужной функции окна, которая и выполняет обработку сообщения.

Когда вы посмотрите на исходный текст нашего первого приложения, обрабатывающего сообщения, вас может удивить тот факт, что функция WinMain не выполняет никакой работы, имеющей отношение к поведению приложения. Внутри этой функции находятся инициализирующий фрагмент и цикл обработки сообщений (Message Loop). Вся основная работа выполняется в функции окна, которой передаются.

Грубо говоря, приложения Windows похожи на загружаемые драйверы MS-DOS. Эти драйверы также состоят из инициализирующего фрагмента и функции, обрабатывающей команды, выдаваемые драйверу. Приложение Windows после инициализации переходит в состояние постоянного опроса собственной очереди сообщений. Как только происходит какое-либо событие, имеющее отношение к приложению, в очереди приложения появляется сообщение и приложение начинает его обрабатывать. После обработки приложение вновь возвращается к опросу собственной очереди сообщений. Иногда функция окна может получать сообщения непосредственно, минуя очередь приложения.

Так как Windows — мультизадачная операционная система, ее разработчики должны были предусмотреть механизм совместного использования несколькими параллельно работающими приложениями таких ресурсов, как мышь и клавиатура. Так как все сообщения, создаваемые драйверами мыши и клавиатуры, попадают в системную очередь сообщений, должен существовать способ распределения этих сообщений между различными приложениями.

В Windows существует понятие фокуса ввода (input focus), помогающее в распределении сообщений. Фокус ввода — это атрибут, который в любой момент времени может относиться только к одному окну. Если окно имеет фокус ввода, все сообщения от клавиатуры распределяются сначала в очередь сообщений приложения, создавшего окно, а затем — функции окна, владеющего фокусом ввода. Нажимая определенные клавиши, вы можете перемещать фокус ввода от одного окна к другому. Например, если вы работаете с диалоговой панелью, содержащей несколько окон, предназначенных для ввода текста, с помощью клавиши вы можете переключать фокус с одного такого окна на другое и вводить текст в различные окна диалоговой панели. Существуют также комбинации клавиш, с помощью которых вы можете переключиться на другое приложение. При этом фокус ввода будет отдан другому окну, принадлежащему другому приложению.

Сообщения от драйвера мыши всегда передаются функции того окна, над которым находится курсор мыши. При необходимости приложение может выполнить операцию захвата (capturing) мыши. В этом случае все сообщения от мыши будут поступать в очередь приложения, захватившего мышь, вне зависимости от положения курсора мыши.

Простейший цикл обработки сообщений состоит из вызовов двух функций — GetMessage и DispatchMessage.

Функция GetMessage предназначена для выборки сообщения из очереди приложения. Сообщение выбирается из очереди и записывается в область данных, принадлежащую приложению.

Читайте также:  Пингвин талисман linux 4 буквы

Функция DispatchMessage предназначена для распределения выбранного из очереди сообщения нужной функции окна. Так как приложение обычно создает много окон и эти окна используют различные функции окна, необходимо распределить сообщение именно тому окну, для которого оно предназначено. Поэтому приложение должно распределить сообщение после его выборки из очереди приложения, в котором находятся сообщения для всех окон. Windows оказывает приложению существенную помощь в решении этой задачи — для распределения приложению достаточно вызвать функцию DispatchMessage.

Вот как выглядит простейший вариант цикла обработки сообщений:

Завершение цикла обработки сообщений происходит при выборке из очереди специального сообщения, в ответ на которое функция GetMessage возвращает нулевое значение. Тип MSG описан в файле windows.h.

Процесс обработки сообщений схематически показан на рис. 1.9.

Рис. 1.9. Процесс обработки сообщений

На этом рисунке слева показаны ресурсы и подсистемы Windows, справа — приложения. Сообщения поступают от драйверов таких устройств, как клавиатура, мышь, таймер, и попадают в системную очередь сообщений. Из системной очереди сообщений Windows выбирает сообщения, предназначенные для приложения, и помещает их в очередь сообщения приложения.

Функция WinMain в цикле обработки сообщений с помощью функции GetMessage выбирает сообщения из очереди сообщений приложения и распределяет их функциям окон, вызывая функцию DispatchMessage.

На нашем рисунке для простоты показано только одно приложение, в котором определена только одна функция окна. Реально же существует много приложений, и каждое приложение имеет несколько функций окна. Каждое приложение вызывает в цикле обработки сообщений функцию GetMessage, выбирая сообщения из своей очереди. Затем каждое приложение распределяет выбранное сообщение одной из своих функций окна, для чего используется функция DispatchMessage.

Функция окна получает сообщения при создании окна, в процессе работы приложения, а также при разрушении окна.

Сообщение с кодом WM_CREATE передается функции окна в момент создания окна. Функция окна при обработке этого сообщения выполняет инициализирующие действия (аналогично конструктору класса в языке C++). Коды сообщений определены в файле windows.h, включаемом в исходные тексты любых приложений Windows.

В процессе работы приложения функция окна может получать сообщения с различными кодами как через очередь сообщений приложения, так и непосредственно, в обход очереди сообщений. Обработчики этих сообщений, определенные в функции окна, являются методами для работы с окном как с объектом.

При разрушении структуры данных окна (при уничтожении окна) функция окна получает сообщение с кодом WM_DESTROY. Обработчик этого сообщения действует как деструктор. Если ваша функция окна во время обработки сообщения WM_CREATE создала какие-либо структуры данных, эти структуры должны быть разрушены (а заказанная для них память возвращена операционной системе) во время обработки сообщения WM_DESTROY.

Образовательный блог — всё для учебы

Приложения Windows содержат различные типы объектов, которые взаимодействуют друг с другом посредством сообщений, посылаемых в ответ на события. За создание и передачу сообщений отвечает среда Windows.

Существует четыре основных источника сообщений. Приложение может получать сообщения от пользователя, от Windows, от самой себя и от других приложений.

Сообщения поименованы и, как правило, все идентификаторы сообщений Windows начинаются на WM_ (Windows Message).
Таким образом, все управляющие элементы и приложения Windows сообщаются посредством системы отправки сообщений. По форме сообщение представляет собой запись, конкретный вид которой зависит от типа сообщения. В отличие от событий Delphi, обработка сообщений обязательна.

Важно не только уметь обрабатывать поступающие сообщения, но и уметь создавать собственные.

Принцип работы системы сообщений Windows

Приложению посылается сообщение всякий раз, когда должно произойти какое-то изменение. Работающая в среде Windows программа фактически ничего не делает сама с экраном, принтером, прерываниями и т.п. Вместо этого она в ответ на сообщение вызывает соответствующие функции Windows или ждет нужного сообщения.

В Windows существует единственная система распределения сообщений — системная очередь сообщений (Message queue). При этом приложения имеют и свои собственные очереди. Каждое сообщение рано или поздно из системной очереди пересылается модулем User в очередь сообщений конкретной программы. Когда приложение получает сообщение, то оно фактически получает данные, упакованные в структуру вида:

PMsg=^TMsg; // Определена в модуле Windows Delphi
TMsg=Record
HWnd: HWND; //Дескриптор окна — получателя сообщения
Message: UINT; // Содержит константу номера сообщения
wParam: WPARAM; //Дополнительные данные сообщения
lParam: LPARAM; // Дополнительные данные сообщения
Time: DWORD; // Время обработки сообщения Windows
Pt: TPoint; // Координаты мыши при выдаче сообщения
End;

Таким образом, структура TMsg содержит сведения о том, какому окну какое сообщение адресовано, а также ряд дополнительных параметров.

После запуска программы каждое окно (формы, кнопки, поля ввода и т.п. — все это окна) регистрируется в Windows, получая значение дескриптора (Handle: THandle) — фактически число, которое в дальнейшем и используется для обращения к этому окну, в том числе и для обработки сообщения. Каждое окно имеет специальную процедуру для обработки сообщений.

Читайте также:  Эмулятор flash player для windows

Основная составляющая работы любого приложения, работающего под Windows, это цикл обработки сообщений (Message loop). Цикл сообщений приложения получает сообщение из очереди, передает его соответствующему окну приложения для обработки, затем получает из очереди следующее сообщение, вновь передает его, и так до окончания работы приложения.

Фактически сообщение посылается не приложению, а конкретному окну, принадлежащему тому или иному приложению. Далее сообщение обрабатывается. Подобные сообщения называются синхронными (synchronous messages). Существуют и асинхронные сообщения (asynchronous messages). Это сообщения, которые непосредственно передаются оконной процедуре, минуя очередь сообщений. К асинхронным относятся такие сообщения, как перерисовка, завершение работы программы, срабатывание таймера и ряд других.

В приложениях Delphi цикл обработки сообщений реализован в модуле Forms. В процессе работы приложения Delphi метод TApplication.Run периодически вызывает метод TApplication.HandleMessage, который просматривает очередь сообщений.

Таким образом, если очередь не пуста, то управление передается методу TApplication.ProcessMessage. Этот метод, если посмотреть на его код в блоке Forms, проверяет содержимое поля FOnMessage (фактически проверяет наличие обработчика у события OnMessage) и, если оно не пусто, то вызывает обработчик этого события, а если поле пусто (равно Nil), то вызывает функцию API DispatchMessage(Msg). Эта функция ищет нужное окно для поступившего сообщения и посылает ему сообщение в виде структуры TMsg.

Если же очередь сообщений пуста, то управление передается методу TApplication.Idle. Этот метод, в свою очередь, если посмотреть на его код в блоке Forms, проверяет содержимое поля FOnldle (фактически проверяет наличие обработчика у события Onldle) и, если оно не пусто, вызывается обработчик этого события — обработчик отсутствия сообщений, называемый также в литературе обработчиком фонового режима. Если же содержимое поля FOnldle равно Nil, то в программе ничего не происходит, и она находится в режиме ожидания поступления сообщений.

Помимо просмотра очереди сообщений приложение Delphi из структуры типа TMsg часть информации сообщения обрабатывает самостоятельно, а другую часть информации сообщения всегда преобразует в запись типа TMessage, которая содержит поле Result, позволяющее вернуть значение из процедуры обработки сообщения. Запись TMessage, включающая вариантную часть, определена в модуле Messages следующим образом:

PMessage=^TMessage;
TMessage=Record
Msg: Cardinal; // Идентификатор номера сообщения
Case Integer Of
0: (wParam, lParam, Result: Longlnt);
1: (wParamLo, wParamHi, lParamLo, lParamHi, ResultLo, ResultHi: Word); End;

Таким образом, обработка сообщений производится той или иной процедурой окна (Window procedure). При этом процедура каждого окна приложения объединяет в себе функции обработки всех типов сообщений для данного окна. Эта процедура представляет собой функцию обратного вызова, которая выполняет определенные действия в ответ на сообщения и обычно возвращает Windows некоторое значение после их обработки.

Сообщения, определяемые пользователем
Windows позволяет определять имена (идентификаторы) для номеров (индексов) новых сообщений. Справочная система Delphi рекомендует использовать для объявления пользовательских сообщений константу — WM_APP=$8000, которая также объявлена в модуле Messages.

Для объявления нового идентификатора надо в секции Const записать:

Const
WM_USER1=WM_APP; // Номер первого сообщения
WM_USER2=WM_APP+1; // Номер второго сообщения

Объявление обработчиков сообщений
Delphi предоставляет возможность объявлять методы обработчиками сообщений Windows (методами сообщений). Такие методы составляют подмножество динамических методов и объявляются директивой Message, за которой следует индекс (номер), указанный с помощью идентификатора сообщения.

Синтаксис объявления метода сообщений в Delphi:

Примечания:
• Методы сообщений обязательно должны быть процедурами.
• Они принимают единственный Var-параметр произвольного типа или
нетипизированный.
• Они принимают целочисленную константу (в диапазоне значений 1…49151) в качестве динамического индекса (номера сообщения) в соответствие с директивой Message.
• Имя метода не играет роли при обработке сообщений, поскольку при обращении используется только индекс сообщения, закрепленный за указанным идентификатором сообщения — WM**.
• Для перекрытия метода сообщений директива Override не используется. В этом случае нужно сохранить в описании нового метода директиву Message с тем же индексом метода, что и у перекрываемого метода.
• В обработчиках сообщений целесообразно вызывать метод-предок, указав Inherited, но без указания его имени — в этом особенность использования Inherited в сообщениях. Чтобы выполнить всю работу по обработке сообщений, за редким исключением, настоятельно рекомендуется вызывать предыдущий обработчик. Таким образом, собственный обработчик исполнит тот код, который заложен в логику работы приложения, а наследуемый обработчик выполнит все действия, которые требуется системе Windows.

Рекомендуемые правила для объявления собственных сообщений:
• именовать тип записи, начиная с буквы Т;
• объявлять метод Message в разделе Protected класса (т.е. эти методы
могут быть доступны только в потомках);
• объявлять метод Message после объявления идентификатора сообщения (WM_**), которое он обрабатывает.

Оцените статью