textpub.neocities.org . [ записки: обучаюсь C++ ]

Из моих записок про самообучение C++. Оригинальный пост в ЖЖ написан 19.12.2018 г.

Состояние программы в глобальной переменной

Размышляя над смыслом первых двух абзацев статьи «Managing Application State», решил дописать текст простейшей программы с оконным интерфейсом в Windows.

В первом абзаце вышеуказанной статьи процедура окна обозначается прилагательным «stateless» (дословно — «без состояния»). (Про состояние программы я немного писал в одном из предыдущих постов). Это означает, что при каждом вызове процедура окна не помнит, что происходило при предыдущем вызове, и вообще не знает, чем, собственно, занимается данная программа в целом. Во входных параметрах у этой процедуры есть лишь информация о сообщении, по которому ее вызвала операционная система. Если свои процедуры мы вызываем сами и можем передать в них нужные входные параметры, то процедуру окна вызывает операционная система со входными параметрами, которые мы не можем изменить.

Во втором абзаце для хранения состояния программы между вызовами процедуры окна предлагается использовать в простых программах глобальные переменные.

Для иллюстрации этого подхода я придумал дописать вышеупомянутую простейшую программу с оконным интерфейсом так, чтобы она показывала число перерисовок клиентской области окна.

Так как у нас есть окно, то информацию будем выводить в него. Для этого используем функцию TextOut (строго говоря, это не функция, а макрос, который при сборке программы разворачивается в конкретную реализацию функции — TextOutA или TextOutW). Подробнее о выводе текста в окно можно почитать тут.

Пример:

...
// в обработке сообщения WM_PAINT

PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);

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

// вставляем вывод строки "Илья" в окно
TextOut(hdc, 0, 0, L"Илья", 4);

EndPaint(hwnd, &ps);
...

Так как процедура окна, как говорилось выше, является «stateless», для хранения числа перерисовок клиентской области окна введем глобальную переменную n.

#include <windows.h>

int n = 0; // счетчик перерисовок (глобальная переменная)

...

// в обработке сообщения WM_PAINT

PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);

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

// счетчик перерисовок
n = n + 1;
// вывод значения счетчика перерисовок в наше окно
TextOut(hdc, 0, 0, n, 4);

EndPaint(hwnd, &ps);
...

В таком виде программа не заработает — при сборке компилятор выдаст ошибку — функции TextOut в качестве четвертого входного параметра можно задавать строку, а не число. Преобразуем целое число в строку (об этом я писал в предыдущем посте).

#include <windows.h>
#include <string> // подключаем для работы со строковым классом

int n = 0; // счетчик перерисовок (глобальная переменная)

...

// в обработке сообщения WM_PAINT

PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);

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

// счетчик перерисовок
n = n + 1;
// преобразование числа в строку
std::wstring str = std::to_wstring(n);
// вывод значения счетчика перерисовок в наше окно
TextOut(hdc, 0, 0, str.c_str(), str.length());

EndPaint(hwnd, &ps);
...

В таком виде программа работает, но результат в окне отображается не всегда верный. Например, если окно свернуть и затем развернуть, значение количества перерисовок отобразится правильное. А если увеличить размеры окна — значение количества перерисовок в окне не изменится.

Дело в том, что счетчик-то считает каждый раз верно, а вот отображение его нового значения в окне происходит не всегда. По умолчанию в область перерисовки не всегда включается вся клиентская область окна целиком. Например, в случае увеличения размера окна в область перерисовки по умолчанию включается только та часть клиентской области, на которую увеличилось окно. Это делается из соображений оптимизации. А так как наше значение счетчика при увеличении окна отображается в левом верхнем углу, который не попадает в этом случае в область перерисовки, то новое значение счетчика и не отображается.

Чтобы наша программа перерисовывала каждый раз всю клиентскую область окна целиком, перед началом отрисовки вызовем функцию InvalidateRect со значением NULL во втором параметре. Вообще, данная функция добавляет определенную программистом во втором параметре область к области перерисовки. Но если передать в этом параметре значение NULL, то в область перерисовки будет добавлена вся клиентская область окна целиком.

Итак, окончательный текст программы:

#include <windows.h>
#include <string> // подключаем для работы со строковым классом

int n = 0; // счетчик перерисовок (глобальная переменная)

...

// в обработке сообщения WM_PAINT

// указываем, что перерисовать нужно всю клиентскую область целиком
InvalidateRect(hwnd, NULL, FALSE);

PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);

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

// счетчик перерисовок
n = n + 1;
// преобразование числа в строку
std::wstring str = std::to_wstring(n);
// вывод значения счетчика перерисовок в наше окно
TextOut(hdc, 0, 0, str.c_str(), str.length());

EndPaint(hwnd, &ps);
...

Про отрисовку (перерисовку) окна на экране можно почитать тут.

textpub.neocities.org . [ записки: обучаюсь C++ ]