суббота, 5 января 2013 г.

C++: WinApi, сообщения окна и их обработка

Приложения с графическим интерфейсом пользователя (GUI) должны реагировать на действия (события) пользователя и ОС.
События пользователя это любое взаимодействие пользователя с программой, например перемещения курсора мыши, нажатие клавиш на клавиатуре и т.д.
События ОС включают в себя любое действие за пределами программы, которые могут повлиять на поведение программы. Например подключение нового устройства или переход в режим пониженного энергопотребления (а так же выключение или гибернация).
Все эти события могут происходить в любое время и в неизвестном порядке. Чтобы структурировать поток выполнения программы, Windows использует модель передачи сообщений. Она взаимодействует с окнами приложения передавая ему сообщения. По сути, сообщение это цифровой код, обозначающий конкретное событие. Например, при нажатии пользователем левой кнопки мыши окно получает сообщение со следующим кодом
#define WM_LBUTTONDOWN    0x0201
Некоторые сообщения имеют связанные данные. Например, WM_LBUTTONDOWN содержит в качестве данных координаты x и y курсора мыши.
Для передачи этих сообщений ОС использует вызов оконной процедуры (WindowProc) зарегистрированной для окна.

Цикл Message Loop

Так как приложение будет получать тысячи сообщений во время своей работы, а так же иметь несколько окон со своей собственной оконной процедурой, программе необходимо будет каким-то образом получать все эти сообщения и передать их в нужную процедуру окна. Для этого в приложении запускается цикл Message Loop, который будет получать сообщение и отправлять их в нужную оконную процедуру.
Для каждого потока, который создает окно ОС создает очередь оконных сообщений, которая содержит сообщения для всех окон этого потока. Очередь сообщений скрыта от выполняемой программы и нельзя управлять очередью напрямую. Но можно вытащить сообщение из очереди с помощью функции GetMessage.
MSG msg;
GetMessage(&msg, NULL, 0, 0);
Эта функция удаляет первое сообщение из головы очереди. Если она пуста, то функция блокируется до следующего сообщения в очереди. Блокирование GetMessage не будет означать остановку программы. Если сообщений нет, то программа не будет ничего выполнять. Т.е. иными словами GetMessage будет просто ожидать приход нового сообщения.
Функция GetMessage принимает 4 параметра. Первый параметр это адрес структуры MSG. Если функция завершается успешно, то она заполнит структуру информацией о сообщении. Остальные три параметра дают возможность фильтровать сообщения, получаемые из очереди. В большинстве случаев их устанавливают в ноль.
Несмотря на то, что MSG структура содержит информацию о сообщении, изучать ее не имеет никакого смысла. Вместо этого, эту структуру необходимо передать двум другим функциям
TranslateMessage(&msg); 
DispatchMessage(&msg);
Функция TranslateMessage связанна с клавиатурой. Она переводит нажатия клавиш в код символа. Разработчику в большинстве случаем не нужно знать как она работает, главное не забывать вызывать ее перед DispatchMessage.
Функция DispatchMessage вызывает оконную процедуру, того окна с которым связанно данное событие.
Рассмотрим пример, пользователь нажимает левую кнопку мыши. Вот что происходит:
  • ОС помещает сообщение WM_LBUTTONDOWN в очередь сообщений
  • Программа вызывает функцию GetMessage
  • GetMessage выталкивает сообщение WM_LBUTTONDOWN из очереди и заполняет структуру MSG
  • Программа вызывает TranslateMessage и DispatchMessage
  • Внутри DispatchMessage ОС вызывает оконную процедуру
  • Оконная процедура может отвечать на сообщение или игнорировать его
Когда оконная процедура завершается, выполнение программы передается в DispatchMessage, которая в свою очередь передает выполнение в цикл обработки сообщений Message Loop для следующего сообщения. Таким образом, пока программа работает, сообщения продолжают поступать в очередь. Вот пример цикла.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
Вызов функции GetMessage должен быть расположен в условии цикла while. Так обеспечивается завершение цикла, когда GetMessage вернет нулевое значение. Всякий раз когда необходимо выйти из цикла Message Loop, необходимо вызвать функцию PostQuitMessage.
PostQuitMessage(0);

Функция PostQuitMessage ставит сообщение WM_QUIT в очередь сообщений. Сообщение WM_QUIT является специальным сообщением, оно заставляет GetMessage вернуть нулевое значение и таким образом завершить цикл Message Loop.

четверг, 3 января 2013 г.

C++: WinApi Регистрация и создание окна

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

Во-первых, сразу стоит сказать, что класс окна это не класс C++, а скорее структура данных используемая внутри ОС Windows. В Windows-приложении, каждое создаваемое окно должно быть связано с классом окна, даже если программа создает всего один экземпляр окна. Окна регистрируются во время выполнения программы. Для этого нужно создать и заполнить структуру WNDCLASS.
// Register the window class.
const wchar_t CLASS_NAME[]  = L"Sample Window Class";
    
WNDCLASS wc = { };

wc.lpfnWndProc   = WindowProc;
wc.hInstance     = hInstance;
wc.lpszClassName = CLASS_NAME;
Необходимо установить значения следующим членам структуры:
  • lpfnWndProc - указатель на определенную приложением функцию, называемой процедурой окна или оконной процедурой.
  • hInstance - дескриптор экземпляра приложения, полученный из функции WinMain
  • lpszClassName - строка, которая определяет имя класса окна.

Далее необходимо передать созданную структуру WNDCLASS функции RegisterClass. Она зарегистрирует окно в ОС.

RegisterClass(&wc);

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

Для создания окна используется функция CreateWindowEx.

 HWND hwnd = CreateWindowEx(
        0,                              // Optional window styles.
        CLASS_NAME,                     // Window class
        L"Learn to Program Windows",    // Window text
        WS_OVERLAPPEDWINDOW,            // Window style

        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

        NULL,       // Parent window    
        NULL,       // Menu
        hInstance,  // Instance handle
        NULL        // Additional application data
        );

    if (hwnd == NULL)
    {
        return 0;
    }

Как мы видим она принимает много параметров. Рассмотрим их.

  • Необязательные дополнительные параметры (например, прозрачность), установка 0 означает параметр по умолчанию.
  • CLASS_NAME имя класса окна, зарегистрированного выше
  • Текст заголовка окна.
  • Стиль окна, определяемый набором флагов. Флаг WS_OVERLAPPENDWINDOW это наиболее распространенное окно с заголовком, границей, поддержкой системы меню и кнопки свертывания и развертывания.
  • Далее идут 4 параметра определяющие размер и позиция окна. Для позиционирования по умолчанию ставим везде CW_USEDEFAULT.
  • Родитель окна, если это окно верхнего уровня устанавливаем NULL
  • Меню окна. В данном случае меню не используется ставим NULL
  • Дескриптор обработчика переданным функции WinMain
  • Указатель на произвольные типы данных, установим в NULL

CreateWindowEx возвращает дескриптор нового окна или NULL, если окно создать не удалось.

Для отображения окна, вызываем функцию ShowWindow.

ShowWindow(hwnd, nCmdShow);

Параметр hwnd это дескриптор окна возвращенный функцией CreateWindowEx. Параметр nCmdShow может быть использован для минимизации или максимизации окна. ОС передает этот параметр в функцию WinMain

С++: WinApi создание простого окна

Вот пример простейшего окна. Пример взят с MSDN. Только в нем пришлось изменить название функции wWinMain на WinMain и PWSTR на LPSTR, чтобы этот пример компилился в CodeBlocks (или в любой другой среде, которая использует компилятор mingw32).


#ifndef UNICODE
#define UNICODE
#endif 

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR pCmdLine, int nCmdShow)
{
    // Register the window class.
    const wchar_t CLASS_NAME[]  = L"Sample Window Class";
    
    WNDCLASS wc = { };

    wc.lpfnWndProc   = WindowProc;
    wc.hInstance     = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    // Create the window.

    HWND hwnd = CreateWindowEx(
        0,                              // Optional window styles.
        CLASS_NAME,                     // Window class
        L"Learn to Program Windows",    // Window text
        WS_OVERLAPPEDWINDOW,            // Window style

        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

        NULL,       // Parent window    
        NULL,       // Menu
        hInstance,  // Instance handle
        NULL        // Additional application data
        );

    if (hwnd == NULL)
    {
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);

    // Run the message loop.

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

    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));

            EndPaint(hwnd, &ps);
        }
        return 0;

    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Если в кратце, то принцип работы следующий. Точка входа в программу WinMain. В этой функции регистрируется класс окна (wc) и некоторая другая информация об окне. Далее программа создает окно (функция CreateWindowEx) и получает дескриптор окна, по которому можно его идентифицировать. В случае успешного создания окна программа входит в цикл while, в котором она будет находится пока пользователь не нажмет кнопку "Закрыть".

WindowProc определяет поведение окна и взаимодействие с пользователем. Эта функция не вызывается явно. Windows общается с программой, передавая ей серию сообщений. Каждый раз когда в цикле while вызывается функция DispatchMessage происходит косвенный вызов WindowProc, которая и производит обработку сообщений.