Основной чертой всех Windows-приложений является то, что они поддерживают оконный интерфейс, используя при этом множество стандартных элементов управления (кнопки, переключатели, линейки, окна редактирования, списки и т. Д.). Эти элементы поддерживаются с помощью динамических библиотек (DLL), которые являются частью операционной системы (ОС). Именно поэтому элементы доступны любым приложениям, и ваше первое приложение имеет почти такой же облик, как и любое другое. Принципиально важным отличием Windows-приложений от приложений DOS является то, что все они — программы, управляемые событиями (event-driven applications). Приложения DOS — программы с фиксированной последовательностью выполнения. Разработчик программы последовательность выполнения операторов, и система строго ее соблюдает. В случае программ, управляемых событиями, разработчик не может заранее предсказать последовательность вызовов функций, и даже выполнения операторов своего приложения, так как эта последовательность определяется на этапе выполнения кода.
Программы, управляемые событиями, обладают большей гибкостью в смысле выбора пользователем порядка выполнения операций. Характерно то, что последовательность действий часто определяется операционной системой и зависит от потока сообщений о событиях в системе. Большую часть времени приложение, управляемое событиями, находится в состоянии ожидания событий, точнее сообщений о них. Сообщения могут поступать от различных источников, но все они попадают в одну очередь системных сообщений. Только некоторые из них система передаст в очередь сообщений вашего приложения. В случае многопотокового приложения сообщение приходит активному потоку (thread) приложения. Приложение постоянно выполняет цикл ожидания сообщений. Как только придет адресованное ему сообщение, управление будет передано его оконной процедуре.
|
Наступление события обозначается поступлением сообщения. Все сообщения Windows имеют стандартные имена, многие из которых начинаются с префикса WM_ (Windows Message). Например, WM_PAINT именует сообщение о том, что необходимо перерисовать содержимое окна того приложения, которое получило это сообщение. Идентификатор сообщения WM_PAINT — это символьная константа, обозначающая некое число. Другой пример: при создании окна система посылает сообщение WM_CREATE. Вы можете ввести в оконную процедуру реакцию на это сообщение для того, чтобы произвести какие-то однократные действия.
Программист может создать и определить какие-то свои собственные сообщения, действующие в пределах зарегистрированного оконного класса. В этом случае каждое новое сообщение должно иметь идентификатор, превышающий зарезервированное системой значение WM_USER (0x400). Допустим, вы хотите создать сообщение о том, что пользователь нажал определенную клавишу в тот момент, когда клавиатурный фокус находится в особом окне редактирования с уже зарегистрированным классом. В этом случае новое сообщение можно идентифицировать так:
#define WM_MYEDIT_PRESSED WM_USER + 1
Каждое новое сообщение должно увеличивать значение идентификатора по сравнению с WM_MYEDIT_PRESSED. Максимально-допустимым значением для идентификаторов такого типа является число 0x7FFF. Если вы хотите создать сообщение, действующее в пределах всего приложения и не конфликтующее с системными сообщениями, то вместо константы WM_USER следует использовать другую константу WM_APP (0x8000). В этом случае можно наращивать идентификатор вплоть до 0xBFFF.
|
Рассмотренная модель выработки и прохождения сообщений поможет понять структуру, принятую для всех Windows-приложений. Простейшее из них должно состоять как минимум из двух функций:
1. функции WinMain, с которой начинается выполнение программы и которая «закручивает» цикл ожидания сообщений;
2. оконной процедуры, которую вызывает система, направляя ей соответствующие сообщения.
Каждое приложение в системе, основанной на сообщениях, должно уметь получать и обрабатывать сообщения из своей очереди. Основу такого приложения в системе Windows представляет функция WinMain, которая содержит стандартную последовательность действий. Однако обрабатывается большинство сообщений окном — объектом операционной системы Windows.
C точки зрения пользователя, окно — это прямоугольная область экрана, соответствующая какому-то приложению или его части. Вы знаете, что приложение может управлять несколькими окнами, среди которых обычно выделяют одно главное окно-рамку. С точки зрения операционной системы, окно — это в большинстве случаев конечный пункт, которому направляются сообщения. С точки зрения программиста, окно — это объект, атрибуты которого (тип, размер, положение на экране, вид курсора, меню, значок, заголовок) должны быть сначала сформированы, а затем зарегистрированы системой. Манипуляция окном осуществляется посредством специальной оконной функции, которая имеет вполне определенную, устоявшуюся структуру.
|
Функция WinMain выполняется первой в любом приложении. Ее имя зарезервировано операционной системой. Она в этом смысле является аналогом функции main, с которой начинается выполнение С-программы для DOS-платформы. Имя оконной процедуры произвольно и выбирается разработчиком. Система Windows регистрирует это имя, связывая его с приложением. Главной целью функции WinMain является регистрация оконного класса, создание окна и запуск цикла ожидания сообщений.
Рассмотрим более подробно структуру традиционного Windows-приложения.
// Стандартный включаемый файл Windows
#include <windows.h>
// Прототип функции обратного вызова для обработки сообщений
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// Функция вызывается автоматически, когда программа запускается
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
// Настройка класса окна
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = “Window Class”; // Имя класса
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// Регистрация класса окна
if(RegisterClassEx(&wndclass) == 0)
{
// Сбой программы, выход
return 0;
}
// Создание окна
hWnd = CreateWindowEx(
WS_EX_OVERLAPPEDWINDOW,
«Window Class», // Имя класса
«Приложение Windows», // Текст заголовка
WS_OVERLAPPEDWINDOW,
0,
0,
320,
200,
NULL,
NULL,
hInstance,
NULL);
// Отображение окна
ShowWindow(hWnd, iCmdShow);
// Обработка сообщений, пока программа не будет прервана
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// Функция обратного вызова для обработки сообщений
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
switch(iMsg)
{
// Вызывается, когда пользователь отпускает левую кнопку мыши
case WM_LBUTTONUP:
MessageBox(hWnd, TEXT(«Вы кликнули!»), TEXT(«событие»), MB_OK);
return(0);
// Вызывается, когда окно обновляется
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
Ellipse(hDC, 50, 20, 200, 100);
EndPaint(hWnd, &ps);
return(0);
// Вызывается, когда пользователь закрывает окно
case WM_DESTROY:
PostQuitMessage(0);
return(0);
default:
return DefWindowProc(hWnd, iMsg, wParam, lParam);
}
}
В двух первых строках кода указываются включаемые заголовочные файлы и приводятся прототипы функций. В программировании для Windows необходим только один заголовочный файл с именем windows.h.
Для нашего примера требуется единственный прототип функции для обработчика сообщений. Любая программа для Windows должна содержать функцию, которая будет вызываться для обработки сообщений и должна соответствовать следующему прототипу:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
Имя функции может отличаться, но параметры должны оставаться неизменными. Это вызвано тем, что Windows автоматически вызывает данную функцию и не сможет работать правильно, если вы измените параметры.
При вызове функции WinMain система передает ей параметры:
hInstance — описатель экземпляра приложения. Это адрес приложения, загруженного в память. В Windows NT/2000 этот адрес для всех приложений имеет одно и то же значение 0x00400000 (4 Мбайт);
hPrevlnstance — описатель предыдущего экземпляра приложения. Этот параметр устарел и теперь не используется в приложениях Win32;
szCmdLine — указатель на командную строку. Мы не будем использовать этот параметр;
iCmdShow — состояние окна при начальной демонстрации.
Ранее в Win16 второй параметр использовался в целях экономии ресурсов, но в Win32 — это NULL, так как каждый экземпляр приложения теперь выполняется в своем собственном виртуальном адресном пространстве процесса емкостью 4 Гбайт. Все экземпляры процесса загружаются, начиная с одного и того же адреса в этом пространстве.
Первый элемент, упоминаемый в функции WinMain() – это объект, используемый для создания окна. Объект является структурой типа WNDCLASSEX и очень важен для создания окна Вашей программы.
Ниже приведен прототип структуры WNDCLASSEX:
typedef struct _WNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;
Первая переменная типа UINT называется cbSize. Она используется для указания размера структуры данных. Обычно для инициализации этого элемента структуры используется выражение sizeof(WNDCLASSEX). Инициализацию этого элемента данных вы можете увидеть, взглянув на приведенный выше листинг.
Второй элемент структуры также имеет тип UINT и называется style. Как указывает имя, элемент style используется для задания стиля создаваемого окна. Удобство данного элемента данных в том, что вы можете указывать комбинации одних стилей с другими, используя несколько флагов, объединенных поразрядной операцией ИЛИ (|). Некоторые стили приведены в таблице.
Таблица 2.1.1 – Некоторые стили окон
Значение | Действие |
CS_DBLCLKS | Простой стиль. Когда он указан, Windows будет посылать окну сообщение о двойном щелчке каждый раз, когда пользователь выполняет двойной щелчок кнопкой мыши в пределах области окна. Это может показаться странным, но многие приложения самостоятельно запоминают время каждого щелчка кнопки мыши, чтобы определить был ли выполнен двойной щелчок. |
CS_HREDRAW | Этот стиль заставляет перерисовывать все окно в случае изменения его ширины. |
CS_NOCLOSE | Запрещает выполнение закрытия окна через системное меню. |
CS_VREDRAW | Заставляет перерисовывать все содержимое окна в случае изменения высоты окна. |
Третий элемент структуры имеет тип WNDPROC, является указателем на функцию и называется lpfnWndProc. Помещаемый сюда указатель должен указывать на функцию обработки сообщений Windows, которую окно использует, чтобы принимать сообщения. Это очень важно, и функция, на которую ссылаются здесь, должна полностью соответствовать прототипу, приведенному в коде.
Четвертый элемент структуры относится к типу int и называется cbClsExtra. Это целое число задает количество байт, которые будут выделены сразу за структурой данных класса окна. Практически всегда это значение устанавливается равным нулю.
Пятый элемент также относится к типу int и называется cbWndExtra. Это целое число задает количество байтов, которые будут выделены сразу за экземпляром окна. Работа с ним аналогична обращению с предыдущим элементом структуры.
Шестой элемент структуры имеет тип HANDLE и называется hInstance. Дескриптор, который вы задаете здесь, является дескриптором экземпляра, к которому относится окнонная процедура класса. В большинстве случаев можно задать значение дескриптора hInstance, получаемого функцией WinMain() в одном из параметров.
Седьмой параметр называется hIcon и имеет тип HICON.
Вы, конечно, обратили внимание на обилие новых типов данных, которые используются в приложениях Win32. Многие из них имеют префикс Н, который является сокращением слова Handle — дескриптор, описатель. Описатели разных типов (HWND, HPEN, HBITMAP и т. Д.) являются посредниками, которые помогают найти нужную структуру данных в виртуальном мире Windows. Объекты Windows или ее ресурсы, такие как окна, файлы, потоки, перья, кисти, области, представлены в системе структурами языка С, и адреса этих структур могут изменяться. В случае нехватки реальной памяти Windows выгружает из памяти ненужные в данный момент времени объекты и загружает на их место объекты, требуемые приложением. В системной области оперативной памяти Windows поддерживает таблицу, в которой хранятся физические адреса объектов. Для поиска объекта и управления им сначала следует получить у системы его дескриптор (место в таблице, индекс). Важно иметь в виду, что физический адрес объекта — понятие для Windows, а не для программиста. Описатель типа HANDLE можно уподобить номеру мобильного телефона, с помощью которого вы отыскиваете объект, перемещающийся в виртуальном мире Windows.
Тип HICON это ни что иное, как тип HANDLE. Данный дескриптор указывает на класс значка, используемого окном. Класс значка в действительности является ресурсом значка (иконкой). Функция LoadIcon() загружает ресурс значка из исполняемой программы. Хотя ресурсы компилируются внутрь исполняемого файла программы (с расширением exe) для Windows, все равно необходимо загружать их, поэтому и требуется вызов данной функции. Вот как выглядит ее прототип:
HICON LoadIcon(
HINSTANCE hInstance,
LPCTSTR lpIconName
);
У функции всего два параметра — HINSTANCE и LPCTSTR. Первый параметр с именем hInstance, содержит дескриптор экземпляра модуля, чей исполняемый файл содержит значок, который вы желаете использовать. Если параметру присвоить значение NULL, то поиск ресурса, в данном случае иконки, будет осуществляться среди стандартных. Второй параметр является указателем на строку, содержащую имя загружаемого значка. Взглянув на разбираемый пример, можно увидеть, что для инициализации данного параметра используется константа IDI_APPLICATION. Ее значение соответствует значку, используемому по умолчанию для приложений, который можно видеть во многих программах для Windows. Некоторые другие иконки перечислены в таблице.
Таблица 2.1.2 – Некоторые стандартные иконки
Значение | Описание |
IDI_APPLICATION | Значок, используемый по умолчанию для приложений. Он применяется и в рассматриваемом примере. В большинстве случаев его можно использовать, если только не требуется нестандартный значок для приложения. |
IDI_ASTERISK | Значок в виде небольшого овала с буквой «i» внутри. |
IDI_ERROR | Красный круг с крестом внутри. |
IDI_EXCLAMATION | Желтый треугольник с восклицательным знаком внутри. |
IDI_QUESTION | Значок с вопросительным знаком. |
IDI_WINLOGO | Небольшой логотип Windows. |
Восьмой элемент структуры данных WNDCLASSEX очень похож на седьмой, за исключением того, что он задает используемый окном курсор. Его тип HCURSOR, а имя — hCursor. Тип HCURSOR это еще один замаскированный дескриптор. Обычно здесь указывается значение дескриптора класса курсора, который будет использован в программе для ее собственного курсора. Функция LoadCursor() похожа на функцию LoadIcon() за исключением того, что она загружает ресурсы курсора, а не ресурсы значка. Вот ее прототип:
HCURSOR LoadCursor(
HINSTANCE hInstance,
LPCTSTR lpCursorName
);
Первый параметр называется hInstance, и содержит дескриптор экземпляра модуля, чей исполняемый файл содержит курсор, который вы собираетесь использовать. Второй параметр — это указатель на строку, содержащую имя загружаемого курсора. Как можно видеть, в рассматриваемом примере я параметр имеет значение IDC_ARROW. Оно соответствует стандартному курсору Windows в виде стрелки. Некоторые другие курсоры перечислены в таблице.
Таблица 2.1.3 – Стандартные курсоры
Значение | Описание |
IDC_APPSTRING | Курсор в форме стандартной стрелки с присоединенными к ней песочными часами. Обычно, данный курсор устанавливается, когда программа занята. |
IDC_ARROW | Стандартный курсор Windows. |
IDC_CROSS | Создает курсов, выглядящий как перекрестье прицела. |
IDC_HELP | Этот курсор выглядит как стандартная стрелка с присоединенным к ней вопросительным знаком. Его хорошо использовать, когда пользователю предоставляется возможность задать вопрос. |
IDC_IBEAM | Курсор в форме буквы «I». Обычно используется в режиме ввода и редактирования текста. |
IDC_NO | Курсор в виде перечеркнутого круга. Его можно использовать, когда пользователь наводит курсор на область, которая не реагирует на щелчки кнопок мыши. |
IDC_SIZEALL | Курсор с перекрещенными стрелками. Применяется, когда пользователь изменяет размер окна или графического элемента. |
IDC_SIZENESW | Еще один курсор для изменения размера. В отличие от предыдущего курсора, у которого стрелки направлены во все четыре стороны, здесь стрелки направлены только на северо-восток и юго-запад. |
IDC_SIZENS | То же, что и предыдущий курсор, но стрелки направлены на север и на юг. |
IDC_SIZENWSE | То же, что и предыдущие два курсора, но стрелки направлены на северо-запад и юго-восток. |
IDC_SIZEWE | Еще один курсор со стрелками. В данном случае они направлены на запад и на восток. |
IDC_UPARROW | Курсор в виде стрелки, направленной вверх. |
IDC_WAIT | Курсор в виде песочных часов. |
Девятый элемент структуры hbrBackground имеет тип HBRUSH и определяет цвет фона окна. Возможно, задать непосредственно цвет, использую константы вида:
wndclass.hbrBackground = (HBRUSH)COLOR_GRAYTEXT;
И другие начинающиеся с COLOR_.
Или, как показано в примере, использовать дескриптор стандартной кисти, полученный с помощью функции GetStockObject().Функция GetStockObject() часто используется для того, чтобы получить дескриптор одной из встроенных кистей, шрифтов, палитр или перьев. Дело в том, что в Windows есть несколько предопределенных типов, которые могут быть использованы. Прототип функции следующий:
HGDIOBJ GetStockObject(int fnObject);
Ее единственный параметр представляет собой целое число, идентифицирующее предопределенный объект операционной системы. В примере используется встроенный объект DKGRAY_BRUSH. Он окрашивает фон окна в темно-серый цвет.
Десятый элемент структуры данных WNDCLASSEX — это завершающаяся нулевым символом строка с именем lpszMenuName. Она содержит имя ресурса меню, используемого окном. NULL указывает на отсутствие меню в программе.
Одиннадцатый элемент структуры данных также является строкой, которая должна завершаться нулевым символом. Его имя — lpszClassName. Как сказано в имени, эта строка используется для задания имени класса окна. Имя класса является уникальным идентификатором типа класса. Поэтому очень важно, чтобы заданное здесь имя не использовалось для других классов окон программы.
Двенадцатый, и последний, элемент структуры WNDCLASSEX — это переменная с именем hIconSm. Она аналогична элементу данных hIcon, за исключением того, что здесь задается используемый программой маленький значок.
Класс окна должен быть зарегистрирован. Ниже приведен фрагмент кода, выполняющий это действие.
If(RegisterClassEx(&wndclass) == 0)
{
// Сбой программы, выход
return 0;
}
Вызов функции RegisterClassEx() необходим, чтобы потом мы смогли создать окно. Эта функция регистрирует класс в системе Windows. Если класс не зарегистрирован, то его невозможно использовать его для создания окна.
Прототип функции следующий:
ATOM RegisterClassEx(
CONST WNDCLASSEX *lpwcx
);
Первый и единственный необходимый для нее параметр является указателем на структуру данных WNDCLASSEX. Функция возвращает значение типа ATOM, которое можно сравнить с NULL. Если возвращаемое функцией RegisterClassEx() значение не равно нулю, ее выполнение завершилось успешно.
Итак, класс окна зарегистрирован, и программа переходит к действительному созданию окна.
Для создания окна используется функция CreateWindowEx(). Ее можно применять для создания дочерних, всплывающих или перекрывающихся окон. При создании окна указывается используемый класс, имя приложения и некоторые другие параметры. Прототип функции выглядит следующим образом:
HWND CreateWindowEx(
DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
Первый параметр имеет тип DWORD и называется dwExStyle. Он похож на определяющий стиль элемент структуры WNDCLASSEX, но задает дополнительные стили окна.
WS_EX_OVERLAPPEDWINDOW является достаточно распространенным стилем, благодаря чему скомпилированная и запущенная программа выглядит как большинство приложений Windows.
LPCTSTR lpClassName – имя класса для создаваемого окна (это имя использовалось при регистрации класса).
LPCTSTR lpWindowName – имя окна.
DWORD dwStyle – стиль окна.
Int x – позиция по горизонтали верхнего левого угла окна.
Int y – позиция по вертикали.
Int nWidth – ширина окна.
Int nHeight – высота окна.
HWND hWndParent – используется для создания «дочернего окна» («child window»). Сюда передается дескриптор «родительского окна» («parent window»).
HMENU hMenu – дескриптор меню (если hMenu равно нулю, используется меню класса, указанного в lpClassName).
HINSTANCE hInstance – экземпляр приложения.
LPVOID lpParam – указатель на пользовательский параметр окна. Этот указатель со всеми остальными параметрами функции CreateWindow будет занесен в структуру CREATESTRUCT. В сообщениях WM_CREATE или WM_NCCREATE параметр lParam будет содержать указатель на эту структуру.
Функция CreateWindow возвращает уникальный дескриптор окна HWND. Если функция вернула ноль, значит, во время создания окна произошла ошибка.
Создание окна не приводит к его отображению. Чтобы окно было действительно выведено на экран необходимо вызвать функцию ShowWindow().Ее прототип:
BOOL ShowWindow(
HWND hWnd,
int nCmdShow
);
Первый параметр задает дескриптор отображаемого окна. Это действительно просто, поскольку дескриптор уже подготовлен функцией, создавшей окно. Второй параметр — это целое число, определяющее как будет отображаться окно. Можно использовать константы вида SW_MINIMIZE (запуск в свернутом состоянии) или отобразить, как рекомендуется операционной системой, передав в функцию параметр, полученный функцией WinMain от Windows.
Чтобы приложения Windows знали когда их окна закрываются, перемещаются или изменяют размеры, они должны принимать сообщения Windows. В общем случае приложения для Windows должны всегда иметь цикл сообщений. Чтобы проверить наличие ожидающих обработки сообщений вызывается функция GetMessage():
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
В первом параметре функция ожидает указатель на объект MSG. Структура данных MSG содержит всю информацию о любом обнаруженном сообщении.
Второй параметр указывает для какого окна проводится проверка наличия сообщений. Он необходим потому, что ваша программа может управлять несколькими окнами. В рассматриваемом примере есть только одно окно, поэтому просто передается дескриптор окна, созданного функцией CreateWindow(). Можно указать NULL, что будет означать обработку сообщений всех окон программы.
Третий параметр задает нижнюю границу кодов получаемых сообщений. Мы хотим получать все сообщения, поэтому присваиваем данному параметру значение 0.
Четвертый параметр позволяет задать верхнюю границу кодов получаемых сообщений. Поскольку мы хотим получать все сообщения, значение этого параметра также равно 0.
Перед тем, как отправить сообщение в вашу очередь сообщений, его необходимо транслировать в символьные данные. Это делает функция TranslateMessage(). Для нее требуется единственный параметр — указатель на транслируемое сообщение.
После того, как сообщение транслировано в символьные данные, его нужно поместить его в очередь сообщений с помощью функции DispatchMessage().
Подобно функции TranslateMessage(), функция DispatchMessage() требует единственный параметр. Цель этой функции — отправить прошедшее трансляцию сообщение в очередь сообщений программы. После вызова этой функции сообщение попадает в функцию обработки сообщений. Последняя строка кода функции WinMain() возвращает значение wParam последнего сообщения Windows извлеченного функцией получения сообщений. Как видно главная функция, программы завершается циклом while, который будет обрабатываться до тех пор пока GetMessage() не вернет ложное значение. Это происходить при приеме сообщения WM_QUIT, генерация которого осуществляется функцией PostQuitMessage().
Стартовая заготовка иллюстрирует стандартную последовательность действий при создании Windows-приложения на базе API-функций. Обратите внимание на то, что функция WndProc нигде явно не вызывается, хотя именно она выполняет всю полезную работу. Для проверки усвоения прочитанного ответьте на вопрос: «Когда и чем она вызывается?»
Теперь рассмотрим, как устроена оконная процедура WndProc. Ее имя уже дважды появлялось в тексте программы. Сначала был объявлен ее прототип, затем оно было присвоено одному из полей структуры типа WNDCLASSEX. Поле имеет тип указателя на функцию с особым прототипом оконной функции. Здесь полезно вспомнить, что имя функции трактуется компилятором C++ как ее адрес.
Оконная процедура должна «просеивать» все посылаемые ей сообщения и обрабатывать те из них, которые были выбраны программистом для обеспечения желаемой функциональности. Типичной структурой оконной процедуры является switch-блок, каждая ветвь которого содержит обработку одного сообщения. В первом примере оконная процедура реагирует только на три сообщения:
WM_LBUTTONUP – о щелчке отпускании пользователем левой кнопки мыши;
WM_PAINT — о необходимости перерисовать клиентскую область окна;
WM_DESTROY — о необходимости закрыть окно.
Сообщение WM_DESTROY (окно “уничтожено”) посылается системой уже после того, как окно исчезло с экрана. Мы реагируем на него вызовом функции PostQuitMessage, которая указывает системе, что поток приложения требует своего завершения, путем посылки сообщения WM_QUIT. Его параметром является код завершения, который мы указываем при вызове PostQuitMessage.
Рассмотренная структура приложения Win32 позволяет сделать вывод, что в подавляющем числе случаев развитие приложения сосредоточено внутри оконной процедуры, а не в функции WinMain. Развитие приложения заключается в том, что в число обрабатываемых сообщений (messages) включаются новые. Для этого программист должен вставлять новые case-ветви в оператор switch (msg).
Если оконная процедура не обрабатывает какое-либо сообщение, то управление передается в ветвь default. Вы видите, что в этой ветви мы вызываем функцию DefWindowProc, которая носит название оконной процедуры по умолчанию. Эта функция гарантирует, что все сообщения будут обработаны, то есть, удалены из очереди. Возвращаемое значение зависит от посланного сообщения.
Результат компиляции и запуска примера показан на рисунке 2.1.1.
Рисунок 2.1.1 – Результат выполнения примера
Контрольные вопросы
- Чем отличается последовательность выполнения команд традиционной DOS – программы и современного Windows – приложения?
- Что такое событие и сообщение в рамках программирования в ОС Windows? Какие бывают сообщения?
- Какая простейшая структура событийно-управляемой программы на Си?
- Что такое оконная процедура, какое ее назначение?
- Почему такие параметры как адрес оконной процедуры или иконка курсора задаются при регистрации класса окна, а название окна в аргументах функции для его создания?
- Что такое цикл сообщений?
- Требуется ли приложению обрабатывать все возможные виды сообщений, существующих в ОС?
- Можно ли просто вывести что-то на экран в произвольном месте кода программы? Почему?
- Как происходит завершение программы? Какое назначение функции PostQuitMessage()?
- Оконная процедура нигде не вызывается явно из главной функции программы, хотя все основные команды сосредоточены именно в ней? Когда и чем она вызывается?