В предыдущей статье мы на половину закончили разработку полноценной оконной процедуры. В этой статье она будет закончена. Итак, мы остановились на функции WinMain(). В её последних строках содержались вызовы двух функций, взаимодействующих с нашей функцией WndProc() (о ней здесь и будет идти речь) и оператор возврата значения функции. Конструкция функции WndProc() должна быть условно такая:
LRESULT CALLBACK WndProc(HWND hWnd, // дескриптор окошка
UINT uMsg, // сообщение, посылаемое ОС
WPARAM wParam, // параметры
LPARAM lParam) // сообщений, для последующего обращения
{
// 1) создаём нужные переменные
// 2) расписываем условия, при которых нужно выполнить нужное действие (switch)
// 3) Возвращаем значение функции
}
LRESULT – это возвращаемое значение. CALLBACK нужно писать, так как это функция обратного вызова. Сначала создаём необходимые переменные. Для начала создадим экземпляр контекста устройства HDC для нужной ориентации текста в окне. Кроме того, для обработки области окна нам нужны ещё две переменные RECT (прямоугольная область окна) и PAINTSTRUCT (в структуре информация об окне для прорисовки) соответственно:
// создаём нужные переменные: HDC hDC; PAINTSTRUCT ps; RECT rect; // и т.д.
Чтобы поменять цвет текста (и не только) нужно создать переменную типа COLORREF и присвоить ей возвращаемое значение функции RGB() (от англ. Red Green Blue) с тремя параметрами:
// создавали нужные переменные COLORREF colorText = RGB(255, 0, 0); // параметры int // и т.д.
Эта функция преобразовывает целочисленный тип в интенсивность цвета и возвращает значение смешивания трёх цветов (интенс. красн. + интенс. зел.+ интенс. син.). Как видно с помощью градаций этих цветов, можно создать 255*255*255= 16’581’375 цветов. ОС Windows и различные функции наших программ ежесекундно отправляют тысячи сообщений каждому отдельному приложению. Эти сообщения могут быть отправлены из-за нажатия клавиши или нажатия кнопки на мыши и т.д. Для этих случаев у нас существует структура MSG, описанная в WinMain(), которая хранит информацию об этих сообщениях. В WndProc() есть условия выбора для этих сообщений. Если ОС отправляет сообщение WM_PAINT, например, то в окне должно что-то рисоваться. Эти сообщения обрабатываются условием switch()(оператор множественного выбора), параметрами которого являются uMsg. uMsg мы создали при описании нашей функции WndProc(). Основной значение в этом операторе, без которых окно не будет обновляться после сворачивания и не закроется: WM_DESTROY. Также есть WM_PAINT, который нужен для прорисовки в клиентской области. WM – от слов Window Message. То есть:
// создавали переменные
switch(uMsg){
case WM_PAINT:
// что то рисуем
case WM_DESTROY:
// обязательно делаем условие закрытия окошка
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam); // об этом далее
}
И что же мы должны делать в этих «кейсах»? При посылке сообщения WM_PAINT – вызываем функции рисования BeginPaint(), GetClientRect(), SetTextColor(), DrawText(), EndPaint(). По названию видно, что эти функции делают. Функция BeginPaint() в прямом смысле начинает рисовать. Только для этого ей нужно иметь дескриптор окна и объект PAINTSTRUCT (у нас это ps). Она возвращает значение типа HDC, поэтому нам нужно присвоить ей hDc. GetClientRect() выбирает область. Параметры у неё аналогичны предыдущей функции: дескриптор окна и указатель на объект класса RECT (у нас это rect). Функция SetTextColor() возвращает цвет текста. Её параметры: возвращаемое значение функции BeginPaint() – hDC и указатель на объект класса COLORREF. Мы могли и не задавать отдельно цвет текста, создавая при этом переменную colorText, а могли сделать это прямо в ней. Но с точки зрения читаемости кода и его понятливости – это в корне не правильно. Старайтесь всегда объявлять переменные отдельно и писать в комментариях, зачем они нужны и тогда не будет вопросов, какие параметры имеет функция, спустя год как вы последний раз закрыли проект по WinAPI. Также соблюдайте венгерскую нотацию по программированию, суть которой: имена переменных должны нести смысл их существования и показывать тип данных. Объявление функции DrawText():
int DrawText(
HDC hDC,// дескриптор контекста устр-ва
LPCTSTR lpchText, // указатель на нашу строку
int nCount, // длина текста (если равно -1, то определяет сам)
LPRECT lpRect, // указатель на объект RECT
UINT uFormat // формат отображения текста
);
На счёт первых 4х параметров всё ясно. Четвёртый uFormat – имеет несколько видов. Обычно используются DT_SINGLELINE, DT_CENTER и DT_VCENTER для отображения текста в центре области, в одну линию. Но Вы можете задать другие параметры (о чём поговорим в следующих уроках). Функция EndPaint() имеет два параметра: дескриптор окна и объект ps. Заметили аналогию с BeginPaint()? Что делать при вызове WM_PAINT, мы знаем (не забываем в конце дописать break). WM_DESTROY посылается окошку функцией DestroyWindow(), которая вызывается в случае, если мы его закрыли. А это происходит в операторе default. При этом происходит вызов функции DefWindowProc(), параметры которой те же, что и у WndProc(). В своём теле WM_DESTROY должен иметь функцию PostQuitMessage(), которая посылает WinMain() сообщение WM_QUIT. Её параметр обычно NULL, интерпретирующийся для главной функции WinMain(), как WM_QUIT.
VOID WINAPI PostQuitMessage(int nExitCode);
По завершению switch() мы возвращаем нулевое значение функции WndProc(). А теперь вернёмся к WinMain(), в частности к обработчику сообщений:
while(GetMessage(&msg, NULL, NULL, NULL)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
Функция GetMessage() имеет такое описание:
BOOL WINAPI GetMessage(LPMSG lpMsg, // указатель на структуру MSG
HWND hWnd, // дескриптор окошка
UINT wMsgFilterMin, // фильтры
UINT wMsgFilterMax // фильтры для выборки сообщений
);
Она обрабатывает сообщения, посылаемые ОС. Первый параметр – это адрес структуры MSG, в которую помещается очередное сообщение. Второй параметр — дескриптор окна. Третий и четвёртый параметры указывают порядок отбора сообщений. Обычно это нулевые значения и функция отбирает любые значения из очереди. Цикл прекращается, если она получает сообщение WM_QUIT. В таком случае она возвращает FALSE и мы выходим из программы. Функции TranslateMessage() и DispatchMessage() в цикле нужны для интерпретации самих сообщений. Обычно это используется при обработке нажатых кнопок на клавиатуре. В следующих уроках мы познакомимся с этими хитростями. По окончанию цикла мы возвращаем ОС код возврата msg.wParam. В итоге у нас должен получиться такой код:
#include <windows.h> // заголовочный файл, содержащий WINAPI
// Прототип функции обработки сообщений с пользовательским названием:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
TCHAR mainMessage[] = L"Какой то-текст!"; // строка с сообщением
// Управляющая функция:
int WINAPI WinMain(HINSTANCE hInst, // дескриптор экземпляра приложения
HINSTANCE hPrevInst, // не используем
LPSTR lpCmdLine, // не используем
int nCmdShow) // режим отображения окошка
{
TCHAR szClassName[] = L"Мой класс"; // строка с именем класса
HWND hMainWnd; // создаём дескриптор будущего окошка
MSG msg; // создём экземпляр структуры MSG для обработки сообщений
WNDCLASSEX wc; // создаём экземпляр, для обращения к членам класса WNDCLASSEX
wc.cbSize = sizeof(wc); // размер структуры (в байтах)
wc.style = CS_HREDRAW | CS_VREDRAW; // стиль класса окошка
wc.lpfnWndProc = WndProc; // указатель на пользовательскую функцию
wc.lpszMenuName = NULL; // указатель на имя меню (у нас его нет)
wc.lpszClassName = szClassName; // указатель на имя класса
wc.cbWndExtra = NULL; // число освобождаемых байтов в конце структуры
wc.cbClsExtra = NULL; // число освобождаемых байтов при создании экземпляра приложения
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // декриптор пиктограммы
wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // дескриптор маленькой пиктограммы (в трэе)
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // дескриптор курсора
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // дескриптор кисти для закраски фона окна
wc.hInstance = hInst; // указатель на строку, содержащую имя меню, применяемого для класса
if(!RegisterClassEx(&wc)){
// в случае отсутствия регистрации класса:
MessageBox(NULL, L"Не получилось зарегистрировать класс!", L"Ошибка", MB_OK);
return NULL; // возвращаем, следовательно, выходим из WinMain
}
// Функция, создающая окошко:
hMainWnd = CreateWindow(
szClassName, // имя класса
L"Полноценная оконная процедура", // имя окошка (то что сверху)
WS_OVERLAPPEDWINDOW | WS_VSCROLL, // режимы отображения окошка
CW_USEDEFAULT, // позиция окошка по оси х
NULL, // позиция окошка по оси у (раз дефолт в х, то писать не нужно)
CW_USEDEFAULT, // ширина окошка
NULL, // высота окошка (раз дефолт в ширине, то писать не нужно)
(HWND)NULL, // дескриптор родительского окна
NULL, // дескриптор меню
HINSTANCE(hInst), // дескриптор экземпляра приложения
NULL); // ничего не передаём из WndProc
if(!hMainWnd){
// в случае некорректного создания окошка (неверные параметры и тп):
MessageBox(NULL, L"Не получилось создать окно!", L"Ошибка", MB_OK);
return NULL;
}
ShowWindow(hMainWnd, nCmdShow); // отображаем окошко
UpdateWindow(hMainWnd); // обновляем окошко
while(GetMessage(&msg, NULL, NULL, NULL)){ // извлекаем сообщения из очереди, посылаемые фу-циями, ОС
TranslateMessage(&msg); // интерпретируем сообщения
DispatchMessage(&msg); // передаём сообщения обратно ОС
}
return msg.wParam; // возвращаем код выхода из приложения
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
HDC hDC; // создаём дескриптор ориентации текста на экране
PAINTSTRUCT ps; // структура, сод-щая информацию о клиентской области (размеры, цвет и тп)
RECT rect; // стр-ра, определяющая размер клиентской области
COLORREF colorText = RGB(255, 0, 0); // задаём цвет текста
switch(uMsg){
case WM_PAINT: // если нужно нарисовать, то:
hDC = BeginPaint(hWnd, &ps); // инициализируем контекст устройства
GetClientRect(hWnd, &rect); // получаем ширину и высоту области для рисования
SetTextColor(hDC, colorText); // устанавливаем цвет контекстного устройства
DrawText(hDC, mainMessage, -1, &rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER); // рисуем текст
EndPaint(hWnd, &ps); // заканчиваем рисовать
break;
case WM_DESTROY: // если окошко закрылось, то:
PostQuitMessage(NULL); // отправляем WinMain() сообщение WM_QUIT
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam); // если закрыли окошко
}
return NULL; // возвращаем значение
}
А после компиляции такое окошко:

Комментарии
wElenaFax
Работа в интернете
mpavelFax
Официальная работа в интернете с обучением.
Дмитрий Зеневич
MSVS 2013
у меня компилируется окно, открывается и быстро закрывается, в чём проблема ?
Дмитрий Зеневич
Разобрался «Что делать при вызове
WM_PAINT, мы знаем (не забываем в конце дописатьbreak).»забыл дописать