Структура оконного приложения : обработка сообщений окна

Структура оконного приложения

Оконные приложения строятся по принципам событийно-управляемого программирования (event-driven programming) — стиля программирования, при котором поведение компонента системы определяется набором возможных внешних событий и ответных реакций компонента на них. Такими компонентами в Windows являются окна.

С каждым окном в Windows связана определенная функция обработки событий – оконная функция. События для окон называются сообщениями. Сообщение относится к тому или иному типу, идентифицируемому определенным кодом (32-битным целым числом), и сопровождается парой 32-битных параметров (WPARAM и LPARAM), интерпретация которых зависит от типа сообщения.

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

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

Классическое оконное приложение, как правило, состоит по крайней мере из двух функций:

Стартовая функция WinMain

В консольной программе на С точкой входа является функция main(). С этого места программа начинает выполняться. Точкой входа программы для Windows является функция WinMain().

 
 
 
 
 
int WINAPI WinMain(
      HINSTANCE hInstance,
      HINSTANCE hPrevInstance,
      PSTR szCmdLine,
      int iCmdShow) {…}

Эта функция использует последовательность вызовов API и при завершении возвращает операционной системе целое число.

Аргументы функции:
  • hInstance – дескриптор процесса (instance handle) – число, идентифицирующее программу, когда она работает под Windows. Если одновременно работают несколько копий одной программы, каждая копия имеет свое значение hInstance.
  • hPrevInstance — предыдущий дескриптор процесса (previous instance) — в настоящее время устарел, всегда равен NULL.
  • szCmdLine — указатель на оканчивающуюся нулем строку, в которой содержатся параметры, переданные в программу из командной строки. Можно запустить программу с параметром командной строки, вставив этот параметр после имени программы в командной строке.
  • iCmdShow — целое константное значение, показывающее, каким должно быть выведено на экран окно в начальный момент. Задается при запуске программы другой программой.  В большинстве случаев число равно 1 (SW_SHOWNRMAL).
Имя Значение Описание
SW_HIDE 0 Скрывает окно и делает активным другое окно
SW_SHOWNORMAL 1 Отображает и делает активным окно в его первоначальном размере и положении.
SW_SHOWMINIMIZED 2 Активизирует окно и отображает его в свернутом виде
SW_SHOWMAXIMIZED 3 Активизирует окно и отображает его в полноэкранном виде
SW_SHOWNOACTIVATE 4 Отображает окно аналогично SW_SHOWNORMAL, но не активизирует его
SW_SHOW 5 Отображает и делает активным окно с текущим размером и положением.
SW_MINIMIZE 6 Сворачивает текущее окно и делает активным следующее окно в порядке очереди.
SW_SHOWMINNOACTIVE 7 Сворачивает окно аналогично SW_SHOWMINIMIZED, но не активизирует его.
SW_SHOWNA 8 Отображает окно в текущей позиции аналогично SW_SHOW, но не активизирует его.
SW_RESTORE 9 Отображает и активизирует окно. Если окно было свернуто или развернуто во весь экран, оно отображается в своем первоначальном положении и размере.
SW_SHOWDEFAULT 10 Отображает окно способом, заданным по умолчанию.
SW_FORCEMINIMIZE 11 Применяется для минимизации окон, связанных с различными потоками.

В структуре стартовой функции Windows можно выделить следующие операции, образующие «скелет» программы:

Регистрация класса окна

Регистрация класса окна осуществляется функцией

 
ATOM WINAPI RegisterClass(_In_ const WNDCLASS *lpWndClass);

Прототип функции находится в файле библиотеки user32.dll. Единственным аргументом функции является указатель на структуру

 
 
 
 
 
 
 
 
 
 
 
typedef struct _WNDCLASS {
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCTSTR   lpszMenuName;
  LPCTSTR   lpszClassName; } WNDCLASS;

Члены структуры style — устанавливает стиль(и) класса. Этот член структуры может быть любой комбинацией стилей класса.

Имя Значение Описание
CS_VREDRAW 0x01 Вертикальная перерисовка: осуществлять перерисовку окна при перемещении или изменении высоты окна.
CS_HREDRAW 0x02 Горизонтальная перерисовка: осуществлять перерисовку окна при перемещении или изменении ширины окна.
CS_KEYCVTWINDOW 0x04 В окне будет выполняться преобразование виртуальных клавиш.
CS_DBLCLKS 0x08 Окну будут посылаться сообщения о двойном щелчке кнопки мыши.
CS_OWNDC 0x20 Каждому экземпляру окна присваивается собственный контекст изображения.
CS_CLASSDC 0x40 Классу окна присваивается собственный контекст изображения, который можно разделить между копиями.
CS_PARENTDC 0x80 Классу окна передается контекст изображения родительского окна.
CS_NOKEYCVT 0x100 Отключается преобразование виртуальных клавиш.
CS_NOCLOSE 0x200 Не закрываемое окно: в системном меню блокируется выбор пункта закрытия окна.
CS_SAVEBITS 0x800 Часть изображения на экране, закрытая окном, сохраняется.
CS_BYTEALIGNCLIENT 0x1000 Выравнивание клиентской области окна: использование границы по байту по оси x.
CS_BYTEALIGNWINDOW 0x2000 Выравнивание окна: bспользование границы по байту по оси x.
CS_PUBLICCLASS CS_GLOBALCLASS 0x4000 Определяется глобальный класс окон.

lpfnWndProc — указатель на оконную процедуру.

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

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

hInstance — дескриптор экземпляра, который содержит оконную процедуру для класса.

hIcon — дескриптор значка класса, дескриптор ресурса значка. Если этот член структуры — NULL, система предоставляет заданный по умолчанию значок.

hCursor — дескриптор курсора класса, дескриптор ресурса курсора. Если этот член структуры — NULL, приложение устанавливает форму курсора всякий раз, когда мышь перемещается в окно прикладной программы.

hbrBackground — дескриптор кисти фона класса, дескриптор физической кисти, которая используется, чтобы красить цветом фона, или код цвета, преобразованный к типу HBRUSH.

lpszMenuName — указатель на символьную строку с символом конца строки ('\0'), которая устанавливает имя ресурса меню класса. Можно использовать целое число, чтобы идентифицировать меню с помощью макроса MAKEINTRESOURCE(int). Если этот член структуры — NULL, окна, принадлежащие этому классу,  не имеют заданного по умолчанию меню.

lpszClassName — указатель на символьную строку с именем класса, оканчивающуюся '\0'.

Создание окна

Создание окна осуществляется функцией

 
 
 
 
 
 
 
 
 
 
 
 
HWND WINAPI CreateWindow(
_In_opt_  LPCTSTR lpClassName,
_In_opt_  LPCTSTR lpWindowName,
_In_      DWORD dwStyle,
_In_      int x,
_In_      int y,
_In_      int nWidth,
_In_      int nHeight,
_In_opt_  HWND hWndParent,
_In_opt_  HMENU hMenu,
_In_opt_  HINSTANCE hInstance,
_In_opt_  LPVOID lpParam );

Прототип функции находится в файле библиотеки user32.dll. Возвращаемое значение – дескриптор создаваемого окна. В случае невозможности создать окно возвращается  NULL.

Аргументы функции:

lpClassName – указывает на строку с ‘\0’ в конце, которая определяет имя класса окна. Имя класса может быть зарегистрированным функцией RegisterClass или любым из предопределенных имен класса элементов управления.

lpWindowName — указывает на строку с ‘\0’ в конце, которая определяет имя окна.

dwStyle — определяет стиль создаваемого окна.

Имя Значение Описание
WS_BORDER 0x00800000 Окно имеет тонкую границу в виде линии.
WS_CAPTION 0x00C00000 Окно имеет строку заголовка.
WS_CHILD 0x40000000 Окно является дочерним.
WS_DISABLED 0x08000000 Окно является изначально неактивным.
WS_GROUP 0x00020000 Окно группирует другие управляющие элементы.
WS_HSCROLL 0x00100000 Окно содержит горизонтальную полосу прокрутки.
WS_MAXIMIZE 0x01000000 Исходный размер окна – во весь экран.
WS_MINIMIZE 0x20000000 Исходно окно свернуто.
WS_OVERLAPPED 0x00000000 Окно может быть перекрыто другими окнами.
WS_POPUP 0x80000000 Всплывающее окно.
WS_SYSMENU 0x00080000 Окно имеет системное меню в строке заголовка.
WS_VISIBLE 0x10000000 Окно изначально видимое.
WS_VSCROLL 0x00200000 Окно имеет вертикальную полосу прокрутки.

x — определяет координату левой стороны окна относительно левой стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату левой стороны относительно начальной координаты родительского окна. Если установлен как CW_USEDEFAULT, Windows выбирает заданную по умолчанию позицию окна.

у – определяет координату верхней стороны окна относительно верхней стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату верхней стороны относительно начальной координаты родительского окна.

nWidth – определяет ширину окна в единицах измерения устройства. Если параметр соответствует CW_USEDEFAULT, Windows выбирает заданную по умолчанию ширину и высоту для окна.

nHeight – определяет высоту окна в единицах измерения устройства.

hWndParent – дескриптор родительского окна.

hMenu – идентифицирует меню, которое будет использоваться окном. Этот параметр может быть NULL, если меню класса будет использовано.

hInstance — идентифицирует экземпляр модуля, который будет связан с окном.

lpParam — указывает на значение, переданное окну при создании.

Отображение и перерисовка окна

Отображение окна осуществляется функцией

 
 
 
BOOL WINAPI ShowWindow(
   _In_  HWND hWnd,
   _In_  int nCmdShow);

Прототип функции находится в файле библиотеки user32.dll. Возвращаемое значение: 1 – успешное отображение окна, 0 – ошибка.

Аргументы функции:

hWnd – дескриптор отображаемого окна.

nCmdShow – константа, определяющая, как будет отображаться окно согласно таблице.

Перерисовка окна осуществляется функцией

 
BOOL UpdateWindow(_In_  HWND hWnd);

Прототип функции находится в файле библиотеки user32.dll. Возвращаемое значение: 1 – успешная перерисовка окна, 0 – ошибка. Аргумент функции hWnd – дескриптор окна.

Цикл обработки сообщений

После вызова функции UpdateWindow, окно окончательно выведено на экран. Теперь программа должна подготовить себя для получения информации от пользователя через клавиатуру и мышь.

Windows поддерживает «очередь сообщений» (message queue) для каждой программы, работающей в данный момент в системе Windows.

Когда происходит ввод информации, Windows преобразует ее в «сообщение», которое помещается в очередь сообщений программы.

Программа извлекает сообщения из очереди сообщений, выполняя блок команд, известный как «цикл обработки сообщений» (message loop):

 
 
 
 
 
while(GetMessage(&msg,NULL,0,0))
{
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}

Для получения сообщения из очереди используется функция:

 
 
 
 
 
BOOL WINAPI GetMessage(
  _Out_     LPMSG lpMsg,
  _In_opt_  HWND hWnd,
  _In_      UINT wMsgFilterMin,
  _In_      UINT wMsgFilterMax);

Прототип функции находится в файле библиотеки user32.dll. В случае получения из очереди сообщения, отличного от WM_QUIT, возвращает ненулевое значение.

Аргументы функции:

lpMsg — указатель на структуру сообщения.

 
 
 
 
 
 
 
 
 
typedef struct MSG 
{
  HWND hwnd; // дескриптор окна, очередь сообщений которого просматривается
  UINT message; // идентификатор сообщения
  WPARAM wParam; // дополнительная информация о сообщении,
  LPARAM lParam; // зависит от идентификатора сообщения
  DWORD time; // время помещения сообщения в очередь
  POINT pt; // структура, содержащая координаты курсора в момент помещения сообщения в очередь
MSG;

Структура POINT имеет вид

 
 
 
 
 
typedef struct POINT
{
  LONG x; // координата x
  LONG y; // координата y
POINT;

hWnd — дескриптор окна, очередь для которого просматривается.

wMsgFilterMin — нижняя граница фильтра идентификаторов сообщений.

wMsgFilterMax — верхняя граница фильтра идентификаторов сообщений.

Функция

 
BOOL WINAPI TranslateMessage(_In_ const MSG *lpMsg);

передает аргумент — структуру msg обратно в Windows для преобразования какого-либо сообщения с клавиатуры. Возвращает ненулевое значение в случае успешной расшифровки сообщения, 0 – ошибка.

Функция

 
LRESULT WINAPI DispatchMessage(_In_ const MSG *lpmsg );

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

После того, как оконная функция обработает сообщение, оно возвращается в Windows, которая все еще обслуживает вызов функции DispatchMessage. Когда Windows возвращает управление в стартовую функцию WinMain() к следующему за вызовом DispatchMessage коду, цикл обработки сообщений в очередной раз возобновляет работу, вызывая GetMessage. Возвращает значение, определяемое оконной функцией, которое чаще всего игнорируется. Прототипы функций находятся в файле библиотеки user32.dll.

Пример стартовой функции, создающей и выводящей окно размером 500х300 точек:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <windows.h>
#include <tchar.h>
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd; // дескриптор окна
    MSG msg; // структура сообщения
    WNDCLASS w; // структура класса окна
    // Регистрация класса окна
    memset(&w, 0, sizeof(WNDCLASS));
    w.style = CS_HREDRAW | CS_VREDRAW;
    w.lpfnWndProc = WndProc; // имя оконной функции
    w.hInstance = hInstance;
    w.hbrBackground = (HBRUSH)(WHITE_BRUSH);
    w.lpszClassName = _T("My Class");
    RegisterClass(&w);
    // Создание окна
    hwnd = CreateWindow(_T("My Class"), _T("Окно пользователя"),
        WS_OVERLAPPEDWINDOW, 500, 300, 500, 380, NULLNULL, hInstance, NULL);
    ShowWindow(hwnd, nCmdShow); // отображение
    UpdateWindow(hwnd);          // перерисовка
    // Цикл обработки сообщений
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

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

Оконная функция - обработка сообщений окна

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

1
2
3
4
5
6
7
8
9
10
11
12
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
{
  switch (Message)
  {
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return DefWindowProc(hwnd, Message, wparam, lparam);
  }
  return 0;
}

4 аргумента оконной функции идентичны первым четырем полям структуры сообщения MSG.

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

Вызов функции DefWindowProc() обрабатывает по умолчанию все сообщения, которые не обрабатывает оконная процедура.

Функция PostQuitMessage() сообщает Windows, что данный поток запрашивает завершение. Аргументом является целочисленное значение, которое функция вернет операционной системе.

Результат выполнения программы, выводящей окно:

Окно пользователя
Прокрутить вверх