II. Создание оконного приложения.
В этой статье будет разобрана программа, которая создает стандартное окно, то есть у него
можно будет менять размер, расположение на экране, его можно будет максимизмровать, минимизировать
и закрывать путем нажатия кнопки на полосе заголовка.
Вот
код программы:
#include <Windows.h>
// ПЕРЕМЕННЫЕ
HWND hMainWnd; // дескриптор окна приложения
// ОКОННАЯ ПРОЦЕДУРА
LRESULT CALLBACK WindowProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
switch(iMsg)
{
// если получено сообщение WM_DESTROY
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, iMsg, wParam, lParam);
}
// ТОЧКА ВХОДА В ПРОГРАММУ
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASSEX wcex; // структура для описания класса окна
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW|CS_VREDRAW;
wcex.lpfnWndProc = WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "Класс окна";
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// регистрируем класс окна
RegisterClassEx(&wcex);
// создаем окно
hMainWnd = CreateWindowEx(0,
"Класс окна",
"Окно",
WS_OVERLAPPEDWINDOW,
0, 0,
400, 400,
NULL,
NULL,
hInstance,
0);
// отображаем окно
ShowWindow(hMainWnd, nShowCmd);
MSG msg; // структура для хранения информации о сообщениях
// цикл обработки сообщений
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
} |
Перед тем как разобраться с кодом рассмотрим общий принцип функционирования программ в
операционной системе Windows.
Надо отметить, что работа всех оконных приложений основана на обмене
сообщениями с операционной системой. Для каждого окна в программе определяется специальная
функция, называемая оконной процедурой. Всякий раз, когда с каким-либо окном происходит некоторое
событие (например, меняется размер окна, на клавиатуре нажимается какая-то клавиша, окно
закрывается и т.п.), Windows посылает ему соответствующее сообщение. Выражается это в том, что
вызывается оконная процедура этого окна, в качестве параметров которой передается определенный код
сообщения (числовое значение типа UINT = unsigned int), а также некоторые параметры,
характеризующие произошедшее событие (например, при изменении размеров окна в качестве таких
параметров Windows передает программе его новую ширину и высоту).
Для каждой программы, Windows создает очередь сообщений. В ней содержатся сообщения,
предназначенные сразу для всех окон программы. В процессе выполнения программы сообщения
извлекаются из очереди и посылаются на обработку оконной процедуре того окна, для которого
сообщение было послано. Для извлечения сообщений из очереди обычно пользуются функцией GetMessage:
BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax)
|
где- lpMsg – указатель на структуру типа MSG (см. ниже), в которую будет занесена
информация о полученном сообщении;
- hWnd – дескриптор окна, для которого необходимо извлечь сообщение (если этот параметр
равен NULL, то будут извлекаться сообщения для всех окон). Десриптор окна – уникальное число,
определяющее любое окно в Windows;
- wMsgFilterMin – код минимального принимаемого сообщения;
- wMsgFilterMax – код максимального принимаемого сообщения.
Если функция получает сообщение с кодом WM_QUIT – она возвращает 0. В случае ошибки –
-1. Во всех остальных случаях – положительное значение. WM_QUIT – это константа,
определенная в заголовочныx файлах и равная 0x0012. Подобные константы с префиксом
«WM_» определены для всех допустимых сообщений.
Структура MSG, в которую GetMessage помещает информацию о полученном сообщении,
определена в заголовочных файлах из <Windows.h> следующим образом:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG |
здесь
- hwnd – дескриптор окна, для которого сообщение предназначено;
- message – код сообщения;
- wParam – параметр сообщения. Значение этого параметра зависит от кода сообщения message.
WPARAM определен в заголовочных файлах как UINT (то есть этот параметр – 32-разрядное
беззнаковое целое);
- lParam – еще один параметр сообщения. Значение этого параметра также зависит от кода
сообщения message. LPARAM определен в заголовочных файлах как LONG (то есть этот параметр –
32-разрядное знаковое целое);
- time – время, когда сообщение было отправлено. DWORD определен как unigned long;
- pt – позиция мыши в тот момент, когда послали сообщение. POINT – структура,
включающая в себя лишь два члена:x и y (оба типа LONG).
Для того чтобы передать полученное сообщение соответствующей оконной процедуре применяют
функцию DispatchMessage:
LONG DispatchMessage(CONST MSG *lpmsg)
|
где lpmsg – указатель на структуру типа MSG, содержащую информацию о сообщении.
Сама оконная процедура – это, как уже говорилось, специальная функция, которая
обрабатывает посылаемые ей сообщения. Любая оконная процедура должна иметь следующий вид:
LRESULT CALLBACK SomeWndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
// здесь происходит обработка
// всех событий...
return DefWindowProc(hWnd, iMsg, wParam, lParam);
} |
Когда оконная процедура вызывается, в качестве первого параметра hWnd ей передается дескриптор
окна, для которого надо обработать сообщение; в качестве второго – код сообщения; в качестве
третьего и четвертого – 32-разрядные целые параметры, значения которых зависят от сообщения
iMsg. В случае если в теле функции сообщение было обработано, оконная процедура должна вернуть
значение 0. Если же какое-то сообщение не обрабатывается, то функция должна вернуть значение,
полученное после выполнения функции DefWindowProc – специальной функции, которая по
умолчанию обрабатывает все сообщения, не обработанные в оконной процедуре ранее.
Но для того, чтобы сообщения стали поступать, в программе должно быть хотя бы одно окно.
Создание окна всегда состоит из двух этапов:
- Регистрация класса создаваемого окна;
- Создание окна на основе зарегистрированного ранее класса;
Прежде, чем создать в Windows какое либо окно, надо зарегистрировать класс окна. В классе окна
определяются параметры для всех окон, созданных на его основе (в частности оконная процедура для
окна определяется именно на этом этапе). При регистрации класса вы должны дать ему некоторое имя,
которое будет потом его определять.
Регистрация класса окна производится с помощью функции RegisterClassEx, имеющей единственный
параметр – указатель на структуру 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 |
где
- cbSize – размер структуры WNDCLASSEX;
- style – стиль класса окна (некоторая константа или комбинация нескольких констант,
определенных в заголовочных файлах);
- lpfnWndProc – указатель на оконную процедуру всех окон, созданных на основе данного
класса;
- cbClsExtra – число дополнительных байтов, выделяемых операционной системой для данного
класса;
- cbWndExtra – число дополнительных байтов, выделяемых операционной системой для каждого
окна, созданного на основе данного класса;
- hInstance – дескриптор экземпляра приложения, в котором данный класс будет
регистрироваться;
- hIcon – дескриптор значка, который будет отображаться на окнах данного класса (HICON
– дескриптор значка, то есть небольшого изображения);
- hCursor – дескриптор курсора, который будет устанавливаться при наведении указателя мыши
на окна данного класса (HCURSOR – дескриптор курсора);
- hbrBackground – дескриптор кисти, которой будут закрашиваться окна данного класса (кисть
– специальный объект, применяемый для закраски некоторых областей. HBRUSH – описатель
кисти);
- lpszMenuName – указатель на главное меню для данного класса;
- lpszClassName – строка-название класса окна;
- hIconSm – дескриптор маленького значка, который будет отображаться в панели задач
рядом с заголовком программы.
При непосредственном создании окна, указываются название класса, на основе которого оно будет
создаваться, а также некоторые дополнительные характеристики, которые окно будет иметь при
создании (расположение на экране, размер, внешний вид и др.). Чтобы создать окно на базе
некоторого класса можно использовать функцию CreateWindowEx:
HWND CreateWindowEx(
DWORD dwExStyle, // дополнительный стиль окна
LPCTSTR lpClassName, // указатель на название зарегистрированного класса окна
LPCTSTR lpWindowName, // указатель на название окна
DWORD dwStyle, // стиль окна
int x, // положени окна по горизонтали
int y, // положени окна по вертикали
int nWidth, // ширина окна
int nHeight, // высота окна
HWND hWndParent, // дескриптор родительского окна (для главного = NULL)
HMENU hMenu, // дескриптор меню (NULL, ели меню нет)
HINSTANCE hInstance, // дескриптор экземпляра приложения
LPVOID lpParam // указатель на некоторый параметр (обычно = NULL)
); |
Возвращает функция дескриптор созданного окна.
Разберем теперь нашу программу. Начнем с функции WinMain. Первое, что мы делаем –
заполняем структуру wcex, определяя параметры оконного класса, который мы будем регистрировать.
В поле style мы используем константы CS_HREDRAW и CS_VREDRAW, комбинируя их с помощью операции
«|». Эти значения показывают, что если происходит изменение размеров окна (созданного
на базе данного класса), по горизонтали (флаг CS_HREDRAW) или по вертикали (флаг CS_VREDRAW)или
окно перемещается, то необходимо произвести его перерисовку. В поля cbClsExtra и cbWndExtra
заносим нулевые значения, так как хранить дополнительную информацию для окна в программе не нужно.
В поля hIcon и hIconSm помещаем значения дескрипторов для стандартных значков, получаемые при
помощи функции LoadIcon (в случае, если ее первый параметр равен NULL, она возвращает дескриптор
стандартного значка, код которого, в нашем случае IDI_APPLICATION, передается в качестве второго
параметра). При помощи аналогичной функции LoadCursor (в качестве второго параметра мы берем
IDC_ARROW – код стандартного курсора-стрелки) устанвливаем курсор. Чтобы установить кисть,
которой будет закрашиваться окно, используем функцию GetStockObject, которой в качестве
единственного параметра передаем константу GRAY_BRUSH (она соответствует стандартной серой кисти).
После заполнения структуры wcex вызываем функцию RegisterClassEx и регистрируем класс окна.
Далее идет функция CreateWindowEx, возвращающая дескриптор создаваемого ей окна. В нашей
программе окно имеет стиль WS_OVERLAPPEDWINDOW
(это константа, определенная в заголовочных файлах из <Windows.h>). Этот стиль (фактически,
некотое число) является комбинацией пяти стилей, полученной в результате побитового ИЛИ:
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|
WS_MINIMIZEBOX|WS_MAXIMIZEBOX) |
WS_OVERLAPPED – стиль стандартного окна с полосой заголовка; WS_CAPTION – стиль окна с
заголовком; WS_SYSMENU – добавляет к окну системное меню; WS_THICKFRAME – добавляет к
окну рамку, дающую возможность менять его размеры; WS_MINIMIZEBOX и WS_MAXIMIZEBOX –
добавляет к окну кнопки минимизации и максимизации соответственно.
Наше окно не имеет родительских, так как является главным в приложении; в нем отсутствует меню.
После того как окно создано, его надо отобразить на экране. Для этого существует функция
ShowWindow. Первый ее параметр – дескриптор окна, которое надо показать, второй –
константа, определяющая то как окно будет отображаться. Вот некоторые из констант, которые
можно использовать:
- SW_SHOWNORMAL – с этой константой в качестве второго параметра ShowWindow функция
отобразит окно и сделает его активным;
- SW_SHOWMAXIMIZED – окно становится активный и отображется максимизированным;
- SW_SHOWMINIMIZED – окно становится активный и отображется минимизированным;
- SW_HIDE – окно перестает отображаться на экране.
В нашей программе в качестве второго параметра функции ShowWindow берем nShowCmd –
последний параметр главной функции WinMain. В нем программе передается желаемый вид главного
окна (обычно SW_SHOWNORMAL).
После того как окно отображено, начинается работа цикла обработки сообщений. Пока не получено
сообщение WM_QUIT (которое посылается в случае, если программа должна завершить свое выполнение)
из очереди извлекаются сообщения и передаются оконной процедуре (в нашей программе она имеет
название WindowProc и определена перед функцией WinMain):
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
|
Функция TranslateMessage служит для обработки сообщений, поступающих от клавиатуры. Подробнее
о ней будет рассказано в следующих статьях.
Рассмотрим теперь функцию WindowProc нашей программы:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
switch(iMsg)
{
case WM_DESTROY:
// посылаем сообщение WM_QUIT в очередь сообщений
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, iMsg, wParam, lParam);
} |
В этой оконной процедуре происходит обработка лишь единственного сообщения – WM_DESTROY.
WM_DESTROY посылается в случае, если происходит разрушение окна (например, если была нажата кнопка
закрытия окна). На это сообщение оконная процедура отвечает вызовом функции PostQuitMessage,
которая помещает в очередь сообщение WM_QUIT (в результате чего «разрывается» цикл
обработки сообщений, программа завершает свою работу). Все остальные сообщения
«игнорируются» и обрабатываются по умолчанию функцией DefWindowProc.
Исходный код программы (3K)
|