В предыдущей статье мы на половину закончили разработку полноценной оконной процедуры. В этой статье она будет закончена. Итак, мы остановились на функции 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
).»забыл дописать