Добавлен: 29.10.2018
Просмотров: 48165
Скачиваний: 190
456
Глава 5. Ввод и вывод информации
Рис. 5.28. Простое окно, расположенное в позиции (200, 100) на XGA-дисплее
Поэтому Windows-программы сориентированы на сообщения. Действия пользователя,
работающего с клавиатурой или мышью, перехватываются системой Windows и пре-
вращаются в сообщения в адрес той программы, которой принадлежит окно. У каждой
программы имеется очередь сообщений, куда посылаются все сообщения, имеющие
отношение ко всем ее окнам. Основной цикл программы состоит из отлавливания оче-
редного сообщения и его обработки путем вызова внутренней процедуры для данного
типа сообщений. В некоторых случаях сама система Windows может непосредственно
вызвать эти процедуры, минуя очередь сообщений. Эта модель существенно отлича-
ется от UNIX-модели процедурного кода, которая заставляет использовать системные
вызовы для взаимодействия с операционной системой. Тем не менее X-система сори-
ентирована на события.
Чтобы сделать модель программирования более понятной, рассмотрим пример, при-
веденный в листинге 5.2. Здесь представлена структура основной программы для
Windows. Она не носит завершенного характера и не имеет проверки на возникновение
ошибок, но имеет достаточную для наших целей детализацию. Программа начинается
с включения заголовочного файла
windows.h
, в котором содержатся многие макросы,
типы данных, константы, прототипы функций и другая информация, необходимая
Windows-программам.
5.6. Пользовательский интерфейс: клавиатура, мышь, монитор
457
Листинг 5.3. Структура основной Windows-программы
#include <windows.h>
int WINAPI WinMain(HINSTANCE h, HINSTANCE hprev, char *szCmd, int iCmdShow)
{
WNDCLASS wndclass; /* объект класса для этого окна */
MSG msg; /* место хранения входящих сообщений */
HWND hwnd; /* дескриптор объекта окна */
/* Инициализация wndclass */
wndclass.lpfnWndProc = WndProc; /* процедура, которую следует вызывать для
обработки сообщений, адресованных окну данного класса*/
wndclass.lpszClassName = "Имя программы"; /* Текст для заголовка */
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); /* загрузка значка */
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); /* загрузка курсора
мыши */
RegisterClass(&wndclass); /* сообщение Windows о wndclass */
hwnd = CreateWindow ( ... ) /* создание окна */
ShowWindow(hwnd, iCmdShow); /* отображение окна на экране */
UpdateWindow(hwnd); /* указание окну на перерисовку */
while (GetMessage(&msg, NULL, 0, 0)) { /* получение сообщения из
очереди */
TranslateMessage(&msg); /* преобразование сообщения */
DispatchMessage(&msg); /* отправка msg соответствующей
процедуре */
}
return(msg.wParam);
}
long CALLBACK WndProc(HWND hwnd, UINT message, UINT wParam, long lParam)
{
/* Сюда помещаются объявления. */
switch (message) {
case WM_CREATE: ... ; return ... ; /* создание окна */
case WM_PAINT: ... ; return ... ; /* перерисовка содержимого
окна */
case WM_DESTROY: ... ; return ... ; /* удаление окна */
}
return(DefWindowProc(hwnd, message, wParam, lParam)); /* по умолчанию */
}
Основная программа начинается с описания, дающего ее имя и параметры. Макрос
WINAPI является инструкцией компилятору на использование определенного со-
глашения о передаче параметров, которая в дальнейшем нас интересовать не будет.
Первый параметр, h, является описателем экземпляра и используется для идентифи-
кации программы во всей остальной системе. В некоторой степени Win32 является
объектно-ориентированной системой, что означает наличие в ней объектов (например,
программ, файлов и окон), имеющих определенное состояние, и связанного с ними
кода, называемого методами, который оперирует этим состоянием. При обращении
к объектам используются дескрипторы, в данном случае программу идентифицирует
дескриптор h. Присутствие второго параметра обусловлено соображениями обеспе-
458
Глава 5. Ввод и вывод информации
чения обратной совместимости и больше не используется. Третий параметр, szCmd,
является строкой, заканчивающейся нулевым байтом, в которой содержится команд-
ная строка, запустившая программу, даже если эта программа не была запущена из
командной строки. Четвертый параметр, iCmdShow, сообщает, должно ли исходное
окно программы быть развернуто на весь экран, на часть экрана или отсутствовать на
экране (находиться только на панели задач).
Это описание иллюстрирует широко используемое компанией Microsoft соглашение
под названием венгерская нотация. Название дано по аналогии с польской нотацией,
изобретенной польским логиком Й. Лукашевичем (J. Lukasiewicz) для представления ал-
гебраических формул без использования старшинства действия или скобок. Венгерская
нотация была изобретена венгерским программистом, работающим в компании Microsoft,
Чарльзом Симони (Charles Simonyi). В ней несколько первых символов идентификатора
используются для указания типа. Разрешенные буквы и типы включают: c (символ —
character), w (слово — word, которое сейчас означает беззнаковое 16-разрядное целое
число), i (32-разрядное целое число со знаком — integer), l (длинное целое число — long,
также 32-разрядное целое число со знаком), s (строка — string), sz (строка, заканчиваю-
щаяся нулевым байтом — zero), p (указатель — pointer), fn (функция — function) и h (опи-
сатель — handle). Таким образом, к примеру, szCmd является строкой, заканчивающейся
нулевым байтом, а iCmdShow — целым числом. Многие программисты считают, что
подобная кодировка типа в именах переменных особой пользы не приносит, а только
затрудняет чтение кода Windows. В системе UNIX аналогичные соглашения отсутствуют.
У каждого окна должен быть связанный с ним объект класса, определяющий его
свойства. В листинге 5.2 таким объектом класса является wndclass. У объекта типа
WNDCLASS имеется 10 полей, четыре из которых инициализируются в приведенном
листинге. В настоящей программе должны быть инициализированы и остальные
шесть. Наиболее важным полем является lpfnWndProc, которое представляет собой
длинное целое (то есть 32-разрядное) число — указатель на функцию, обрабатывающую
сообщения, направляемые этому окну. Другие инициализируемые в листинге поля
указывают, какие имя и значок использовать в заголовке и какое обозначение — для
указателя мыши.
После инициализации wndclass вызывается процедура RegisterClass для передачи этого
объекта системе Windows. В частности, после этого вызова Windows знает, какую про-
цедуру вызывать при возникновении различных событий, которые не проходят через
очередь сообщений. Вызов следующей процедуры, CreateWindow, приводит к выделе-
нию памяти для структуры данных окна и возвращению дескриптора для последующих
обращений к окну. Затем программа осуществляет еще два вызова подряд — для вывода
очертаний окна на экран и его окончательного заполнения.
А теперь мы подошли к главному циклу программы, который состоит из получения
сообщения, осуществления определенных преобразований этого сообщения, а затем
передачи его обратно системе Windows, чтобы заставить ее вызвать процедуру WndProc
для его обработки. А нельзя ли весь этот механизм упростить? Конечно, можно, но
его конструкция обусловлена историческими причинами, которых мы теперь и при-
держиваемся.
За основной программой следует процедура WndProc, обрабатывающая различные со-
общения, которые могут быть посланы в адрес окна. Использование здесь ключевого
слова CALLBACK, как и использование чуть раньше ключевого слова WINAPI, опреде-
ляет способ вызова и передачи параметров. Первый параметр является дескриптором
5.6. Пользовательский интерфейс: клавиатура, мышь, монитор
459
используемого окна. Второй параметр — это тип сообщения. Третий и четвертый
параметры могут использоваться для предоставления дополнительной информации,
если таковая потребуется.
Сообщения типа WM_CREATE и WM_DESTROY посылаются в начале и в конце про-
граммы соответственно
1
. Они дают программе возможность, к примеру, выделить
память для структур данных, а затем вернуть эту память.
Сообщение третьего типа, WM_PAINT, является инструкцией программе на заполне-
ние окна. Оно вызывается не только при первой прорисовке окна, но нередко и при
выполнении программы. В отличие от систем, основанных на использовании текста,
в Windows программа не может предположить, что все, что она нарисовала на экране,
будет находиться на нем, пока она не удалит это изображение. Поверх этого изображе-
ния можно перетащить другие окна, поверх него могут быть раскрыты меню, диалого-
вые окна и всплывающие подсказки, закрывая часть содержимого окна, и т. д. После
удаления этих элементов окно должно быть перерисовано. Способ, которым Windows
предписывает перерисовку окна, заключается в отправке сообщения WM_PAINT. В ка-
честве дружеского жеста система также предоставляет информацию о том, какая часть
окна была затерта, в случае если проще или быстрее регенерировать эту часть окна, а не
перерисовывать его целиком с самого начала.
У системы Windows есть два способа заставить программу что-нибудь сделать. Во-
первых, она может поместить сообщение в очередь сообщений программы. Этот спо-
соб используется для ввода с помощью клавиатуры, мыши и для получения реакции
на истекшее время таймера. Во-вторых, она может послать сообщение окну, для чего
Windows сама напрямую вызывает процедуру WndProc. Этот способ используется
для всех остальных событий. Поскольку система Windows уведомляется о том, что
сообщение полностью обработано, она может воздержаться от нового вызова до тех
пор, пока не будет закончена обработка предыдущего. Таким образом удается избежать
соревновательного состояния.
Существует множество других типов сообщений. Чтобы избежать непредсказуемого
поведения при получении неожиданного сообщения, программа должна вызвать про-
цедуру DefWindowProc в конце процедуры WndProc, чтобы дать возможность обработ-
чику по умолчанию позаботиться обо всех остальных сообщениях.
В итоге программа Windows создает, как правило, одно или несколько окон с объектом
класса для каждого из них. С каждой программой связаны очередь сообщений и набор
процедур обработки. В конечном счете, поведением программы управляют входящие
события, обрабатываемые соответствующими процедурами. Это совсем другая модель
мира по сравнению с более процедурным представлением, принятым в UNIX. Самим
выводом графики на экран занимается пакет, состоящий из сотен процедур, которые
собраны воедино в форме интерфейса графических устройств — GDI (Graphics Device
Interface). Он может обрабатывать текст и графику и сконструирован таким образом,
чтобы быть независимым от платформ и устройств. Перед тем как программа сможет
вывести графику в окно, ей нужно получить контекст устройства (device context),
представляющий собой внутреннюю структуру данных, в которой содержатся свойства
1
Точнее, при создании и уничтожении конкретного окна. Если у программы только одно
окно, то действительно, скорее всего, оно будет создано практически сразу после ее запуска
и уничтожено незадолго до ее завершения. — Примеч. ред.
460
Глава 5. Ввод и вывод информации
окна, среди которых текущий шрифт, цвет текста, цвет фона и т. д. Во многих вызовах
GDI используется контекст устройства либо для вывода графики, либо для получения
или установки свойств.
Для получения контекста устройства существует несколько способов. Рассмотрим
простой пример его получения и использования:
hdc = GetDC(hwnd);
TextOut(hdc, x, y, psText, iLength);
ReleaseDC(hwnd, hdc);
Первый оператор осуществляет получение дескриптора контекста устройства — hdc.
Во втором операторе контекст устройства используется для вывода строки текста на
экран. В нем указываются координаты (x, y) начала вывода строки, дается указатель
на саму строку и сообщается ее длина. Третий вызов процедуры приводит к высво-
бождению контекста устройства, чтобы показать, что программа в данный момент
прекратила вывод графики. Заметьте, что hdc используется аналогично дескриптору
файла в UNIX. Также заметьте, что ReleaseDC содержит избыточную информацию
(использование дескриптора hdc, однозначно определяющего окно). Использование
избыточной информации, не имеющей практического значения, встречается в Windows
довольно часто.
Другое интересное известие состоит в том, что при получении hdc подобным способом
программа может выводить информацию только в имеющуюся в окне область клиента,
но не в заголовок и не в другие части окна. Внутри структуры данных контекста устрой-
ства поддерживается обособленная область. Любой графический вывод за ее пределы
игнорируется. Но для получения контекста устройства есть и другой способ — вызов
процедуры GetWindowDC, при котором обособленная область устанавливается на все
пространство окна. Другие вызовы устанавливают границы обособленной области
по-своему. Системе Windows свойственно иметь несколько вызовов, приводящих
практически к одинаковому результату.
Полное рассмотрение GDI не входит в круг освещаемых здесь вопросов. Заинтересо-
вавшиеся работой интерфейса графических устройств могут обратиться за дополни-
тельной информацией по ранее приведенным ссылкам. Но учитывая важность GDI,
пожалуй, стоит сказать о нем еще несколько слов. Для получения и высвобождения
контекстов устройств в GDI имеется несколько процедур, предоставляющих инфор-
мацию об этих контекстах, извлекающих и устанавливающих атрибуты контекстов
(к примеру, цвета фона), работающих с такими GDI-объектами, как перья, кисти
и шрифты, каждый из которых имеет собственные атрибуты. И наконец, в нем конеч-
но же имеется огромное количество GDI-вызовов, используемых для вывода графи-
ческой информации на экран.
Процедуры вывода графической информации сведены в четыре категории: рисования
прямых и кривых линий, рисования закрашенных областей, управления растровыми
изображениями, отображения текста. Ранее нам уже встречался пример отображения
текста, поэтому давайте кратко взглянем на один из прочих примеров. Вызов
Rectangle(hdc, xleft, ytop, xright, ybottom);
приводит к выводу на экран закрашенного прямоугольника, имеющего углы с коор-
динатами в левой верхней (xleft, ytop) и правой нижней (xright, ybottom) областях.
К примеру, вызов
Rectangle(hdc, 2, 1, 6, 4);