Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

Глобальное сообщение обязано иметь имя (именно поэтому такие сообщения называются также строковыми), под которым оно регистрируется в системе с помощью функции в 

RegisterWindowMessage
. Эта функция возвращает уникальный номер регистрируемого сообщения. Если сообщение с таким именем регистрируется впервые, номер выбирается из числа ещё не занятых. Если же сообщение с таким именем уже было зарегистрировано, то возвращается тот же самый номер, который был присвоен ему при первой регистрации. Таким образом, разные программы, регистрирующие сообщения с одинаковыми именами, получат одинаковые номера и смогут понимать друг друга. Для прочих же окон это сообщение не будет иметь никакого смысла. Создание и использование оконных сообщений демонстрируется примером NumBroadcast, содержащимся на прилагаемом компакт-диске. Разумеется, существует вероятность, что два разных приложения выберут для своих глобальных сообщений одинаковые имена, и это приведет к проблемам при широковещательной рассылке этих сообщений. Но, если давать своим сообщениям осмысленные имена, а не что-то вроде
WM_MYMESSAGE1
, вероятность такого совпадения будет очень мала. В особо критических ситуациях можно в качестве имени сообщения использовать GUID, уникальность которого
гарантируется.

Номера глобальных сообщений становятся известными только на этапе выполнения программы. Это означает, что для их обработки нельзя использовать методы с директивой message, вместо этого следует перекрывать методы

WndProc
или
DefaultHandler
.

1.1.10. Особые сообщения

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

Сообщение

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

Сообщение

WM_PAINT
предназначено для перерисовки клиентской облаcти окна. Если изображение сложное, этот процесс занимает много времени, поэтому в Windows предусмотрены механизмы, минимизирующие количество перерисовок. Перерисовывать свое содержимое окно должно при получении сообщения
WM_PAINT
. С каждым таким сообщением связан регион, нуждающийся в обновлении. Этот регион может совпадать с клиентской областью окна или быть ее частью. В последнем случае программа может ускорить перерисовку, рисуя не все окно, а только нуждающуюся в этом часть (VCL игнорирует возможность перерисовки только части окна, поэтому при работе с этой библиотекой окно всегда перерисовывается полностью). Послать сообщение
WM_PAINT
с помощью
PostMessage
окну нельзя, т.к. оно не ставится в очередь. Вместо этого можно пометить регион как нуждающийся в обновлении с помощью функций
InvalidateRect
и
InvalidateRgn
. Если на момент вызова этих функций регион, который необходимо обновить, не был пуст, новый регион объединяется со старым. Функции
GetMessage
и
PeekMessage
, если очередь сообщений пуста, а регион, требующий обновления, не пуст, возвращают сообщение
WM_PAINT
. Таким образом, перерисовка окна откладывается до того момента, когда все остальные сообщения будут обработаны. Отправить
WM_PAINT
с помощью
SendMessage
тоже нельзя. Если требуется немедленная перерисовка окна, следует вызвать функции
UpdateWindow
или
RedrawWindow
, которые не только отправляют сообщение окну, но и выполняют сопутствующие действия, связанные с регионом обновления. Обработка сообщения
WM_PAINT
также имеет некоторые особенности. Обработчик должен получить контекст устройства окна (см. разд. 1.1.11 данной главы) с помощью функции
BeginPaint
и по окончании работы освободить его с помощью
EndPaint
. Эти функции должны вызываться только один раз при обработке сообщения. Соответственно, если сообщение обрабатывается поэтапно несколькими обработчиками, как это бывает при перехвате сообщений, получать и освобождать контекст устройства должен только первый из них, а остальные должны пользоваться тем контекстом, который он получил. Система не накладывает обязательных требований, которые могли бы решить проблему, но предлагает решение, которое используют все предопределенные системные классы. Когда сообщение
WM_PAINT
извлекается из очереди, его параметр
wParam
равен нулю. Если же обработчик получает сообщение с
wParam <> 0
, то он рассматривает значение этого параметра как дескриптор контекста устройства и использует его, вместо того чтобы получать дескриптор через
BeginPaint
. Первый в цепочке обработчиков должен передать вниз по цепочке сообщение с измененным параметром
wParam
. Компоненты VCL также пользуются этим решением. При перехвате сообщения
WM_PAINT
это нужно учитывать.

Примеры PanelMsg и Line, имеющиеся на прилагаемом компакт-диске, демонстрируют, как правильно перехватывать сообщение

WM_PAINT
.

Простые таймеры, создаваемые системой с помощью функции

SetTimer
, сообщают об истечении интервала посредством сообщения
WM_TIMER
. Проверка того, истек ли интервал, осуществляется внутри функций
GetMessage
,
PeekMessage
. Таким образом, если эти функции долго не вызываются, сообщение
WM_TIMER
не ставится в очередь, даже если положенный срок истек. Если за время обработки других сообщений срок истек несколько раз, в очередь ставится только одно сообщение
WM_TIMER
. Если в очереди уже есть сообщение
WM_TIMER
, новое в очередь не добавляется, даже если срок истек. Таким образом, часть сообщений
WM_TIMER
теряется, т.е., например, если интервал таймера установить равным одной секунде, то за час будет получено не 3600 сообщений
WM_TIMER
, а меньшее
число, и разница будет тем больше, чем интенсивнее программа загружает процессор.

Примечание

Класс

TTimer
инкапсулирует таймер, работающий через
WM_TIMER
. Сообщения получает невидимое окно, создающееся специально для этого. Поэтому событие
OnTimer
за час при секундном интервале также возникнет меньше, чем 3600 раз.

Некоторую специфику имеют и сообщения от клавиатуры. При обработке таких сообщений можно использовать функцию

GetKeуState
, которая возвращает состояние любой клавиши (нажата-отпущена) в момент возникновения данного события. Именно в момент возникновения, а не в момент вызова функции. Если функцию
GetKeyState
использовать при обработке не клавиатурного сообщения, оно вернет состояние клавиши на момент последнего извлеченного из очереди клавиатурного сообщения.

1.1.11. Графика в Windows API

Та часть Windows API, которая служит для работы с графикой, обычно называется GDI (Graphic Device Interface). Ключевое понятие в GDI — контекст устройства (Device Context, DC). Контекст устройства — это специфический объект, хранящий информацию о возможностях устройства, о способе работы с ним и о разрешенной для изменения области. В Delphi контекст устройства представлен классом

TCanvas
, свойство
Handle
которого содержит дескриптор контекста устройства.
TCanvas
универсален в том смысле, что с его помощью рисование в окне, на принтере или в метафайле выглядит одинаково. То же самое справедливо и для контекста устройства. Разница заключается только в том, как получить в разных случаях дескриптор контекста.

Большинство методов класса

TCanvas
являются "калькой" с соответствующих (в большинстве случаев одноименных) функций GDI. Но в некоторых случаях (прежде всего в методах вывода текста и рисования многоугольников) параметры методов
TCanvas
имеют более удобный тип, чем функции GDI. Например, метод
TCanvas.Polygon
требует в качестве параметра открытый массив элементов типа
TPoint
, а соответствующая функция GDI — указатель на область памяти, содержащую координаты точек, и число точек. Это означает, что до вызова функции следует выделить память, а потом — освободить ее. Еще нужен код, который заполнит эту область памяти требуемыми значениями. И ни в коем случае нельзя ошибаться в количестве элементов массива. Если зарезервировать память для одного числа точек, а при вызове функции указать другое, программа будет работать неправильно. Но для простых функций работа через GDI ничуть не сложнее, чем через
TCanvas
. Для получения дескриптора контекста устройства существует много функций. Только для того, чтобы получить дескриптор контекста обычного окна, существуют четыре функции:
BeginPaint
,
GetDC
,
GetWindowDC
и
GetDCEx
. Первая из них возвращает контекст клиентской области окна при обработке сообщения
WM_PAINT
. Вторая дает контекст клиентской области окна, который можно использовать в любой момент времени, а не только при обработке
WM_PAINT
. Третья позволяет получить контекст всего окна, вместе с неклиентской частью. Последняя же дает возможность получить контекст определенной области клиентской части окна.

После того как дескриптор контекста получен, можно воспользоваться преимуществами класса

TCanvas
. Для этого необходимо создать экземпляр такого класса и присвоить его свойству
Handle
полученный дескриптор. Освобождение ресурсов нужно проводить в следующем порядке сначала свойству Handle присваивается нулевое значение, затем уничтожается экземпляр класса
TCanvas
, после этого с помощью подходящей функции GDI освобождается контекст устройства. Пример такого использования класса
TCanvas
демонстрируется листингом 1.17.

Листинг 1.17. Использование класса
TCanvas
для работы с произвольным контекстом устройства

var

 DC: HDC;

 Canvas: TCanvas;

begin

 DC := GetDC(...); // Здесь возможны другие способы получения DC

 Canvas := TCanvas.Create;

 try

Canvas.Handle := DC; // Здесь рисуем, используя Canvas

 finally

Canvas.Free;

 end;

 // Освобождение объекта Canvas не означает освобождения контекста DC

 // DC необходимо удалить вручную

 ReleaseDC(DC);

end;

Использование класса

TCanvas
для рисования на контексте устройства, для которого имеется дескриптор, показано в примере PanelMsg на прилагающемся компакт-диске.

Разумеется, можно вызывать функции GDI при работе через

TCanvas
. Для этого им просто нужно передать в качестве дескриптора контекста значение свойства
Canvas.Handle
. Коротко перечислим те возможности GDI, которые разработчики VCL почему-то не сочли нужным включать в
TCanvas
: работа с регионами и траекториями; выравнивание текста по любому углу или по центру; установка собственной координатной системы; получение детальной информации об устройстве; использование геометрических перьев; вывод текста под углом к горизонтали; расширенные возможности вывода текста; ряд возможностей по рисованию нескольких кривых и многоугольников одной функцией; поддержка режимов заливки. Доступ ко всем этим возможностям может быть осуществлен только через API. Отметим также, что Windows NT/2000/XP поддерживает больше графических функций, чем 9x/МЕ. Функции, которые не поддерживаются в 9x/ME, также не имеют аналогов среди методов TCanvas, иначе программы, написанные с использованием данного класса, нельзя было бы запустить в этих версиях Windows.

Поделиться с друзьями: