Создание полноценной оконной процедуры в Win32 API (Часть 2)

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

А после компиляции такое окошко:

Screenshot_1До встречи в следующих уроках.

Автор: Marienko L.
Дата: 28.02.2014
Поделиться:

Комментарии

  1. wElenaFax

    Работа в интернете

  2. mpavelFax

    Официальная работа в интернете с обучением.

  3. Дмитрий Зеневич

    MSVS 2013

    у меня компилируется окно, открывается и быстро закрывается, в чём проблема ?

Оставить комментарий

Вы должны войти, чтобы оставить комментарий.