2. Вывод текста в окно

В предыдущей главе мы привели пример приложения, создающего единственное окно. Однако это окно так и осталось пустым, потому что мы еще не умеем выводить в него ни текст, ни графические изображения. К сожалению, вы не сможете воспользоваться для вывода текста ни одной из хорошо известных вам стандартных функций библиотеки компилятора, такой, как printf, cprintf или putc. Не помогут вам и прерывания BIOS, так как приложениям Windows запрещено их использовать, во всяком случае для вывода на экран. Все эти функции ориентированы на консольный вывод в одно-единственное окно, предоставленное в полное распоряжение программе MS-DOS.

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

Способ, которым приложение выводит что-либо в свои окна, коренным образом отличается от используемого в программах MS-DOS.

Программа MS-DOS формирует изображение на экране "рассредоточенным" образом, то есть в любом месте программы могут вызываться функции, которые выводят что-либо на экран. Например, сразу после запуска программа может нарисовать на экране диалоговую панель, а затем в любой момент времени и, что самое главное, из любого места программы модифицировать ее.

Приложения Windows также могут выводить в созданные ими окна текст или графические изображения в любой момент времени и из любого места. Однако обычно разработчики поступают по-другому.

Windows следит за перемещением и изменением размера окон и при необходимости извещает приложения, о том, что им следует перерисовать содержимое окна. Для извещения в очередь приложения записывается сообщение с идентификатором WM_PAINT. Получив такое сообщение, функция окна должна выполнить перерисовку всего окна или его части, в зависимости от дополнительной информации, полученной вместе с сообщением WM_PAINT.

Для облегчения работы по отображению содержимого окна весь вывод в окно обычно выполняют в одном месте приложения - при обработке сообщения WM_PAINT в функции окна. Приложение должно быть сделано таким образом, чтобы в любой момент времени при поступлении сообщения WM_PAINT функция окна могла перерисовать все окно или любую его часть, заданную своими координатами.

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

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

2.1. Приложение TEXTOUT

Наше следующее приложение, которое называется TEXTOUT, отличается от предыдущего только использованной функцией окна. Функция окна теперь обрабатывает сообщение WM_PAINT и выводит в окно текст. Однако дополнительно мы полностью изменили структуру исходных текстов приложения и использовали некоторые возможности языка программирования C++.

Приложение TEXTOUT создает два класса с именами WinApp и Window (здесь имеются в виду классы C++, а не классы окон). Первый класс реализует приложение Windows как таковое, второй используется для создания фундаментальных объектов Windows - окон.

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

Главным файлом, который содержит функцию WinMain, является файл textout.cpp (листинг 2.1).

Листинг 2.1. Файл textout/textout.cpp

// ----------------------------------------
// Вывод текста в окно приложения
// ----------------------------------------
#define STRICT
#include <windows.h>
#include "window.hpp"
#include "winapp.hpp"
#include "textout.hpp"
// Имя класса окна
char szClassName[]   = "TextoutAppClass";
// Заголовок окна
char szWindowTitle[] = "Textout Application";
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, int       nCmdShow)
{
  // Указатель на объект класса Window - главное
  // окно приложения
  Window *PMainWindow;
  // Создаем объект класса WinApp - наше приложение
  WinApp App(hInstance, hPrevInstance, lpszCmdLine, nCmdShow);
  // Регистрируем класс для главного окна приложения
  App.RegisterWndClass(szClassName, (WNDPROC)WndProc);
  // Проверяем ошибки, которые могли возникнуть
  // при регистрации
  if(App.Error()) return App.Error();
  // Создаем объект класса Window - главное
  // окно приложения
  PMainWindow = new Window(hInstance,
        szClassName, szWindowTitle);
  // Проверяем ошибки, которые могли возникнуть
  // при создании окна
  if(PMainWindow->Error()) PMainWindow->Error();
  // Отображаем окно
  PMainWindow->Show(nCmdShow);
  // Посылаем в окно сообщение WM_PAINT
  PMainWindow->Update();
  // Запускаем цикл обработки сообщений
  App.Go();
  // Завершаем работу приложения
  return App.Error();
}

Этот файл выглядит значительно проще, чем аналогичный из предыдущего приложения.

Дополнительно к файлу windows.h в файл включаются include-файлы с именами window.hpp, winapp.hpp и textout.hpp. Первые два файла содержат соответственно определения классов Window и WinApp. Файл textout.hpp содержит все необходимые определения для файла textout.cpp.

Как и в предыдущем приложении, массивы с именами szClassName и szWindowTitle используются для хранения имени класса главного окна, создаваемого приложением, и заголовка этого же окна.

В функции WinMain определен указатель на объект класса Window с именем PMainWindow. Этот указатель будет хранить адрес объекта - главного окна приложения.

Далее в функции WinMain определяется объект класса WinApp, имеющий имя App. При создании объекта конструктор класса WinApp получает те же самые параметры, что и функция WinMain при старте приложения:

WinApp App(hInstance, hPrevInstance, lpszCmdLine, nCmdShow);

После этого выполняется регистрация класса окна, для чего вызывается функция-метод с именем RegisterWndClass, определенная в классе WinApp. В качестве параметров функции передаются указатель на имя класса szClassName и адрес функции окна WndProc.

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

После проверки ошибок, возможных на этапе регистрации класса окна, функция WinMain создает главное окно приложения, являющееся объектом класса Window:

PMainWindow = new Window(hInstance, 
  szClassName, szWindowTitle);

Конструктор класса получает в качестве параметров идентификатор приложения, указатель на имя класса, на базе которого должно быть создано окно, и указатель на заголовок окна.

После проверки ошибок функция WinMain вызывает методы Show и Update из класса Window. Метод Show отображает окно, вызывая уже известную вам функцию ShowWindow. Метод Update посылает в функцию окна сообщение WM_PAINT, вызывая функцию UpdateWindow, которая вам также знакома.

Далее вызывается функция-метод Go, определенная в классе WinApp, которая запускает цикл обработки сообщений.

После завершения цикла обработки сообщения функция WinMain возвращает управление Windows, завершая работу приложения.

Include-файл textout.hpp (листинг 2.2) содержит прототип функции окна WndProc, необходимый при создании класса окна.

Листинг 2.2. Файл textout\textout.hpp

// Прототип функции WndProc
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

Класс WinApp определяется в файле winapp.hpp (листинг 2.3).

Листинг 2.3. Файл textout\winapp.cpp

// =====================================
// Определение класса WinApp
// =====================================
class WinApp
{
  MSG  msg;   // структура для работы с сообщениями
  int  errno; // флаг ошибки
  HINSTANCE hInstance;  // идентификатор приложения
  
public:
  // Конструктор
  WinApp(HINSTANCE, HINSTANCE, LPSTR, int);
  // Регистрация класса окна
  BOOL RegisterWndClass(LPSTR, WNDPROC);
  // Запуск цикла обработки сообщений
  WORD Go(void);
  // Проверка флага ошибок
  int  Error(void) { return errno; }
};

В классе WinApp используется структура msg, нужная для временного хранения сообщения в цикле обработки сообщений, флаг ошибки errno и идентификатор приложения hInstance.

Кроме конструктора в классе WinApp определены методы RegisterWndClass (регистрация класса окна), Go (запуск цикла обработки сообщений) и Error (проверка флага ошибок). Процедуры регистрации класса окна и цикл обработки сообщения ничем не отличаются от использованных в предыдущем приложении.

Исходные тексты функций-методов класса WinApp приведены в листинге 2.4.

Листинг 2.4. Файл textout\winapp.cpp

// =====================================
// Функции-методы для класса WinApp
// =====================================
#define STRICT
#include <windows.h>
#include "winapp.hpp"
// -------------------------------------
// Конструктор класса Window
// -------------------------------------
#pragma argsused
WinApp::WinApp(HINSTANCE hInst, HINSTANCE hPrevInstance,
               LPSTR     lpszCmdLine, int nCmdShow)
{
  // Сбрасываем флаг ошибки
  errno = 0;
  // Сохраняем идентификатор приложения
  hInstance = hInst;
}
// -------------------------------------
// Регистрация класса окна
// -------------------------------------
BOOL
WinApp::RegisterWndClass(LPSTR szClassName, WNDPROC WndProc)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна
  wc.style         = 0;
  wc.lpfnWndProc   = (WNDPROC) WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName  = (LPSTR)NULL;
  wc.lpszClassName = (LPSTR)szClassName;
  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}
// -------------------------------------
// Запуск цикла обработки сообщений
// -------------------------------------
WORD
WinApp::Go(void)
{
  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

Конструктор класса WinApp сбрасывает флаг ошибки и сохраняет в переменной hInstance идентификатор текущей копии приложения, переданный ему функцией WinMain.

Функция-метод RegisterWndClass регистрирует класс окна, используя для этого указатель на имя класса и указатель на функцию окна. Регистрация выполняется, как и в предыдущем приложении, при помощи функции RegisterClass.

Функция-метод Go запускает цикл обработки сообщений, полностью идентичный использованному в предыдущем приложении.

Так как приложение может создавать много окно, удобно определить в качестве класса окно. Файл window.hpp содержит определение класса Window, который предназначен для создания окон (листинг 2.5).

Листинг 2.5. Файл textout\window.hpp

// =====================================
// Определение класса Window
// =====================================
class Window
{
  HWND hwnd; // Идентификатор окна
  int errno; // Флаг ошибки
public:
  // Конструктор
  Window(HINSTANCE, LPSTR, LPSTR);
  // Проверка флага ошибки
  int  Error(void)        { return errno; }
  // Отображение окна
  void Show(int nCmdShow) { ShowWindow(hwnd, nCmdShow); }
  // Передача функции WndProc сообщения WM_PAINT
  void Update(void)       { UpdateWindow(hwnd); }
};

В классе Window определена переменная типа HWND для хранения идентификатора окна и флаг ошибок errno. Там же определены конструктор, функции-методы Show (отображение окна), Update (передача в функцию окна сообщения WM_PAINT) и Error (проверка флага ошибок).

Исходный текст конструктора класса Window находится в файле window.cpp (листинг 2.6).

Листинг 2.6. Файл textout\window.cpp

// =====================================
// Функции-методы для класса Window
// =====================================
#define STRICT
#include <windows.h>
#include "window.hpp"
// -------------------------------------
// Конструктор класса Window
// -------------------------------------
Window::Window(HINSTANCE hInstance, LPSTR szClassName,
               LPSTR szWindowTitle)
{
  // Сбрасываем флаг ошибки
  errno = 0;
  // Создаем окно
  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT,       // задаем размеры и расположение
    CW_USEDEFAULT,       // окна, принятые по умолчанию 
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    0,                   // идентификатор родительского окна
    0,                   // идентификатор меню
    hInstance,           // идентификатор приложения
    NULL);               // указатель на дополнительные
                              // параметры
  // Если при создании окна были ошибки,
  // устанавливаем флаг ошибки
  if(!hwnd)
  {  
    errno = 1;
    return;
  }
}

Конструктор класса Window сбрасывает флаг ошибок и создает окно, вызывая для этого известную вам функцию CreateWindow. Если при создании окна были ошибки, устанавливается флаг ошибок.

До настоящего момента в приложении TEXOUT вам не встречалось ничего нового по сравнению с предыдущим приложением, за исключением того, что мы подготовили его для объектно-ориентированного программирования. Однако нашей задачей было научиться выводить в окно текст.

Как мы и говорили, эта операция выполняется функцией окна (листинг 2.7).

Листинг 2.7. Файл textout\winproc.cpp

// =====================================
// Функция WndProc
// Функция выполняет обработку сообщений главного
// окна приложения
// =====================================
#define STRICT
#include <windows.h>
LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;             // индекс контекста устройства
  PAINTSTRUCT ps;      // структура для рисования
  switch (msg)
  {
    case WM_PAINT:
    {
      // Получаем индекс контекста устройства
      hdc = BeginPaint(hwnd, &ps);
      // Выводим текстовую строку
      TextOut(hdc, 10, 20, "Сообщение WM_PAINT", 18);
      // Отдаем индекс контекста устройства
      EndPaint(hwnd, &ps);
      return 0;
    }
    case WM_LBUTTONDOWN:
    {
      // Получаем индекс контекста устройства
      hdc = GetDC(hwnd);
      // Выводим текстовую строку
      TextOut(hdc, 10, 40, "Сообщение WM_LBUTTONDOWN", 24);
      // Отдаем индекс контекста устройства
      ReleaseDC(hwnd, hdc);
      return 0;
    }
    case WM_RBUTTONDOWN:
    {
      hdc = GetDC(hwnd);
      TextOut(hdc, 10, 60, "Сообщение WM_RBUTTONDOWN", 24);
      ReleaseDC(hwnd, hdc);
      return 0;
    }
    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Эта функция обрабатывает сообщение WM_PAINT, а также уже известные вам сообщения, попадающие в очередь сообщения, когда вы выполняете щелчок клавишами мыши над окном приложения - WM_LBUTTONDOWN и WM_RBUTTONDOWN. Во время обработки этих сообщений приложение выполняет вывод текста в окно, вызывая специально предназначенную для этого функцию TextOut, входящую в состав программного интерфейса Windows.

Последний файл, входящий в проект приложения, - это файл определения модуля с именем textout.def (листинг 2.8), который почти полностью повторяет аналогичный файл из предыдущего приложения.

Листинг 2.8. Файл textout\textout.def

; =============================
; Файл определения модуля
; =============================
NAME        TEXTOUT
DESCRIPTION 'Приложение TEXTOUT, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   5120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

2.2. Контекст отображения

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

При обработке сообщения WM_PAINT перед выводом текста вызывается функция BeginPaint, возвращающая идентификатор так называемого контекста отображения (display context):

hdc = BeginPaint(hwnd, &ps);

Контекст отображения используется функцией вывода текста TextOut:

TextOut(hdc, 10, 10, "Сообщение WM_PAINT", 18);

Перед завершением обработки сообщения WM_PAINT приложение должно обязательно вызвать функцию EndPaint, освобождающую полученный ранее контекст отображения устройства:

EndPaint(hwnd, &ps);

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

Функция TextOut, которую мы использовали для вывода текста, также относится к интерфейсу GDI. Она имеет пять параметров:

BOOL TextOut(HDC hdc, int nXStart, int nYStart,
        LPCSTR lpszString, int cbString);

Первый параметр (hdc) определяет используемый контекст отображения. При обработке сообщения WM_PAINT контекст отображения создается функцией BeginPaint для того окна, идентификатор которого указывается этой функции в качестве первого параметра.

Второй (nXStart) и третий (nYStart) параметры задают координаты (x, y) начальной позиции, начиная с которой будет выполняться вывод текста.

Четвертый параметр (lpszString) является дальним указателем на выводимую строку, длина которой определяется последним, пятым параметром (cbString).

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

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

Используемая система координат также определяется в контексте отображения. По умолчанию в левом верхнем углу внутренней области окна (client area), ограниченной сверху заголовком, а с других сторон рамкой, находится начало координат - точка с координатами (0,0). Ось x направлена слева направо, ось y - сверху вниз. Изменяя контекст отображения, вы можете выбрать другую систему координат, например расположив начало координат в центре внутренней области окна.

Контекст отображения не позволит вам вывести текст за пределами внутренней области окна. Однако позже вы научитесь создавать другой тип контекста - контекст устройства (device context), относящийся к всей поверхности экрана видеомонитора. Этот контекст позволит вам выводить текст или графические изображения в любое место экрана.

Существует и другой способ получения контекста отображения, который должен использоваться при обработке других, отличных от WM_PAINT, сообщений:

hdc = GetDC(hwnd);
TextOut(hdc, 10, 40, "Сообщение WM_LBUTTONDOWN", 24);
ReleaseDC(hwnd, hdc);

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

Обратим еще раз ваше внимание на то, что при обработке сообщения WM_PAINT для создания и освобождения контекста отображения необходимо использовать функции BeginPaint и EndPaint, а во всех остальных случаях - функции GetDC и ReleaseDC.

2.3. Обработка сообщения WM_PAINT

Как мы уже говорили, окно должно быть способно перерисовать свою внутреннюю поверхность или любую его часть в любой момент времени при получении сообщения WM_PAINT.

Сообщение WM_PAINT передается функции окна, если стала видна область окна, скрытая раньше другими окнами, если вы изменили размер окна или выполнили операцию свертки (пролистывания) изображения в окне. Приложение может передать функции окна сообщение WM_PAINT явным образом, вызывая функции UpdateWindow, InvalidateRect или InvalidateRgn.

Иногда операционная система Windows может сама восстановить содержимое окна, не посылая сообщение WM_PAINT. Например, при перемещении курсора мыши или пиктограммы свернутого приложения Windows самостоятельно восстанавливает содержимое окна. Если же Windows не может восстановить окно, функция окна получает сообщение WM_PAINT и перерисовывает окно самостоятельно.

Перед тем как записать сообщение WM_PAINT в очередь приложения, Windows посылает функции окна сообщение WM_ERASEBKGND. Если функция окна не обрабатывает сообщение WM_ERASEBKGND, передавая его функции DefWindowProc, последняя в ответ на это сообщение закрашивает внутреннюю область окна с использованием кисти, указанной в классе окна (при регистрации класса окна). Поэтому, если функция окна нарисует что-либо в окне во время обработки других сообщений, отличных от WM_PAINT, после прихода первого же сообщения WM_PAINT нарисованное изображение будет закрашено.

Что делать в том случае, когда по логике работы приложения требуется изменить содержимое окна не во время обработки сообщения WM_PAINT, а в любом другом месте приложения?

В этом случае приложение должно сообщить Windows, что необходимо перерисовать часть окна или все окно. При этом в очередь приложения будет записано сообщение WM_PAINT, обработка которого приведет к нужному вам результату. Можно указать Windows, что был изменен прямоугольный участок окна или область произвольной формы, например эллипс или многоугольник. Для этого предназначены функции InvalidateRect и InvalidateRgn, которые мы рассмотрим позже.

Как функция окна может определить область окна, подлежащую обновлению при обработке сообщения WM_PAINT?

Для ответа на этот вопрос вспомним о втором параметре функции BeginPaint, который является указателем на структуру PAINTSTRUCT:

HDC BeginPaint(HWND hwnd, PAINTSTRUCT FAR * lpps);

Структура PAINTSTRUCT определена в файле windows.h следующим образом:

typedef struct tagPAINTSTRUCT
{
  HDC  hdc;
  BOOL fErase;
  RECT rcPaint;
  BOOL fRestore;
  BOOL fIncUpdate;
  BYTE rgbReserved[16];
} PAINTSTRUCT;

Поле hdc после вызова функции BeginPaint будет содержать идентификатор контекста отображения (тот же самый, что и возвращаемый самой функцией BeginPaint).

Начальные координаты и размер области, подлежащей обновлению в процессе обработки сообщения WM_PAINT, передаются через поле rcPaint. Это поле представляет собой структуру типа RECT, описывающую прямоугольную область:

typedef struct tagRECT
{
   int left;
   int top;
   int right;
   int bottom;
} RECT;

Поля left, top, right и bottom задают координаты области следующим образом:

Поле Описание
left x-координата верхнего левого угла области
top y-координата верхнего левого угла области
right x-координата правого нижнего угла области
bottom y-координата правого нижнего угла области

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

Поле fErase структуры PAINTSTRUCT определяет необходимость стирания фона окна в области, подлежащей обновлению. Если это поле установлено в состояние TRUE, функция BeginPaint посылает функции окна сообщение WM_ERASEBKGND.

Как правило, сообщение WM_ERASEBKGND передается функции DefWindowProc, которая при получении этого сообщения перерисовывает фон соответствующей области окна (используя кисть, определенную при регистрации класса окна). Если поле fErase содержит значение FALSE, фон окна не изменяется.

Остальные поля структуры PAINTSTRUCT используются Windows, приложение не должно изменять их содержимое.

Если окно содержит несколько областей, подлежащих обновлению, приложение получает только одно сообщение WM_PAINT, в котором определена область, охватывающая все указанные выше области.

Рассмотрим некоторые функции, имеющие отношение к сообщению WM_PAINT.

Функция UpdateWindow имеет следующий прототип:

void UpdateWindow(HWND hwnd);

Эта функция посылает сообщение WM_PAINT функции окна, идентификатор которого задан в качестве параметра hwnd. Сообщение посылается в обход очереди сообщений приложения, и только в том случае, если для окна существует непустая область обновления. Если в окне обновлять нечего, сообщение WM_PAINT не посылается.

При помощи функции InvalidateRect вы можете объявить любую область окна как требующую обновления. Прототип функции:

void InvalidateRect(HWND hwnd, LPRECT lprc, BOOL fErase);

Первый параметр (hwnd) функции является идентификатором окна, для которого выполняется операция. Второй параметр (lprc) - дальний указатель на структуру типа RECT, определяющую прямоугольную область, подлежащую обновлению. Третий параметр (fErase) определяет необходимость стирания фона окна. Если этот параметр задан как TRUE, фон окна подлежит стиранию (см. поле fErase структуры PAINTSTRUCT).

Функция ValidateRect удаляет прямоугольную область из списка областей, подлежащих обновлению. Она имеет следующий прототип:

void ValidateRect(HWND hwnd, LPRECT lprc);

Первый параметр определяет идентификатор окна, второй является дальним указателем на структуру типа RECT, определяющую прямоугольную область, которая должна быть исключена из области обновления. Если в результате вызова функции ValidateRect в окне не остается областей, подлежащих обновлению, сообщение WM_PAINT удаляется из очереди сообщений приложения.

Подводя итог, отметим еще раз несколько моментов, важных для понимания методов, с помощью которых приложение Windows рисует в своих окнах.

Приложение должно выполнять вывод в окно "централизованно" в функции окна при получении сообщения WM_PAINT.

При обработке сообщения WM_PAINT для увеличения скорости работы следует использовать координаты области окна, подлежащей обновлению, хотя можно обновить и все окно.

Используя специальные функции, приложение в любой момент времени может определить любую область окна как подлежащую (или не подлежащую) обновлению и послать самому себе в соответствующую функцию окна сообщение WM_PAINT.

2.4. Система координат и режим отображения

При выводе текста из программы MS-DOS вы пользовались простой системой координат, начало которой находилось в левом верхнем углу экрана. Ось x была направлена слева направо, ось y - сверху вниз. Так как в текстовом режиме на экране обычно помещаются 25 строк по 80 символов, возможные значения для x находились в пределах от 0 до 79, а для y - от 0 до 24.

При необходимости вывода текста из программы MS-DOS в графическом режиме вы должны были учитывать, что видеоконтроллер может работать в различных режимах с разным разрешением, например 640 x 480 в одном из режимов VGA или 1024 x 1200 в одном из режимов SVGA. Для каждого типа видеоконтроллера и для каждого видеорежима ваша программа при выводе текста должна была использовать отдельный набор шрифтов.

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

В процессе вывода изображения функции графического интерфейса GDI преобразуют логические координаты в физические. Для определения способа такого преобразования используется атрибут с названием режим отображения (mapping mode), который хранится в контексте устройства вывода.

Контекст устройства - это структура данных, связанная с тем или иным устройством ввода/вывода. Контекст устройства определяет различные параметры, используемые при работе с устройством, аналогично контексту отображения. В этом контексте хранится режим отображения, то есть указывается способ, с помощью которого логические координаты отображаются в физические.

Для указания режима отображения в файле windows.h определены символьные константы с префиксом MM_ (от Mapping Mode - режим отображения). При создании контекста устройства по умолчанию устанавливается режим отображения, обозначаемый как MM_TEXT. При выводе текста мы будем использовать именно этот режим отображения.

В режиме отображения MM_TEXT используются логическая система координат, полностью эквивалентная физической. Логическое начало координат (0, 0) соответствует физическому началу координат устройства (0, 0). Каждая единица по оси x или y соответствует одному пикселу экрана.

В нашем примере мы вывели строку текста, использовав логические координаты (10, 20):

TextOut(hdc, 10, 20, "Сообщение WM_PAINT", 18);

При использовании режима отображения MM_TEXT в качестве начала координат берется верхняя левая точка устройства вывода. В нашем случае устройством вывода является главное окно приложения, поэтому начало координат (0, 0) находится в верхнем левом углу главного окна приложения. Текст будет выведен начиная с точки (10, 20) в логической системе координат, связанной с главным окном приложения.

Однако как будет расположен текст относительно точки (10, 20)?

Если очертить строку текста воображаемым прямоугольником, то по умолчанию в точке (10, 20) будет находиться верхний левый угол этого прямоугольника (рис. 2.1).

Рис. 2.1. Координаты текстовой строки

В контексте отображения можно задать так называемый режим выравнивания текста (text alignment mode). По умолчанию используется выравнивание на левую границу, причем координаты вывода текста указывают верхнюю границу воображаемого прямоугольника, охватывающего текст.

При помощи функции SetTextAlign можно изменить режим выравнивания. Приведем прототип функции:

UINT WINAPI SetTextAlign(HDC hdc, UINT fuAlign);

Функция SetTextAlign возвращает старое значение режима выравнивания. Ключевое слово WINAPI определено в файле windows.h следующим образом:

#define WINAPI  _far _pascal

Как и все функции программного интерфейса Windows версии 3.1 для компьютеров с процессорами фирмы Intel, функция SetTextAlign использует при передаче параметров соглашение языка Паскаль и является дальней функцией.

Первый параметр функции (hdc) указывает идентификатор контекста отображения, для которого надо изменить выравнивание. Это тот самый идентификатор, который возвращается функцией BeginPaint при обработке сообщения WM_PAINT.

Второй параметр (fuAlign) указывает новый режим выравнивания и задается при помощи трех групп флагов. Символические имена флагов определены в файле windows.h и начинаются с префикса TA_.

Первая группа флагов отвечает за выравнивание текстовой строки по горизонтали:

Флаг Описание
TA_LEFT Выравнивание по левой границе. Координаты соответствуют левой границе воображаемого прямоугольника, охватывающего текст (используется по умолчанию)
TA_CENTER Выравнивание по центру. Координаты соответствуют центру воображаемого прямоугольника, охватывающего текст
TA_RIGHT Выравнивание по правой границе

Вторая группа флагов отвечает за выравнивание текста по вертикали:

Флаг Описание
TA_TOP Выравнивание по верхней границе. Координаты соответствуют верхней границе воображаемого прямоугольника, охватывающего текст (используется по умолчанию)
TA_BASELINE Выравнивание по базовой линии выбранного шрифта
TA_BOTTOM Выравнивание по нижней границе

Третья группа флагов относится к текущей позиции вывода текста:

Флаг Описание
TA_NOUPDATECP Не изменять значение текущей позиции вывода текста (используется по умолчанию)
TA_UPDATECP После использования функций TextOut и ExtTextOut вычислить новое значение текущей позиции вывода текста

Понятие текущей позиции вам уже знакомо, если вы составляли программы MS-DOS, выводящие текст. Для приложений Windows в контексте отображения вы можете разрешить или запретить использование текущей позиции. Если использование текущей позиции вывода текста разрешено, ее значение будет обновляться при вызове функций вывода текста TextOut и ExtTextOut (еще одна функция для вывода текста, ее мы рассмотрим позже).

Из каждой группы флагов можно использовать только один, например:

SetTextAlign(hdc, TA_CENTER | TA_BASELINE | TA_UPDATECP);

Если задан режим выравнивания TA_UPDATECP, функция TextOut начинает вывод текста с текущей позиции, игнорируя параметры, определяющие расположение текста в окне.

В любой момент времени вы можете определить текущий режим выравнивания, вызвав функцию GetTextAlign:

UINT GetTextAlign(hdc);

Эта функция возвращает текущий режим отображения для контекста, указанного функции в качестве параметра.

Небольшое замечание относительно типа данных UINT. Для операционной системы Windows этот тип данных определен следующим образом:

typedef unsigned int  UINT;

Однако не следует думать, что для операционной системы Windows NT будет использовано такое же определение. Поэтому для беззнаковых данных размером в одно машинное слово следует пользоваться типом UINT, но не unsigned int. При этом у вас будет меньше проблем с переносом ваших приложений в другую среду, отличную от Windows версии 3.1.

2.5. Функция окна приложения TEXTOUT

Отложим описание деталей вывода текста, связанное с изменением шрифтов и других параметров текста, и вернемся к функции окна приложения TEXTOUT (листинг 2.7). Эта функция выводит текст при получении сообщений WM_PAINT, WM_LBUTTONDOWN и WM_RBUTTONDOWN.

Когда функция окна получает сообщение WM_PAINT, фон окна закрашивается кистью, определенной при регистрации класса окна, и начиная с точки с логическими координатами (10, 20) выводится строка "Сообщение WM_PAINT".

Если вы установите курсор мыши в главное окно приложения и будете нажимать левую или правую клавишу мыши, в функцию окна будет посылаться соответственно сообщение WM_LBUTTONDOWN или WM_RBUTTONDOWN. По первому сообщению в точке (10, 40) будет выведена строка "Сообщение WM_LBUTTONDOWN", по второму - в точке (10, 60) будет выведена строка "Сообщение WM_RBUTTONDOWN" (рис. 2.2).

Рис. 2.2. Главное окно приложения TEXTOUT

Если вы при помощи рамки измените размер главного окна, уменьшив, а затем немного увеличите его, первая строка, выведенная по сообщению WM_QUIT, будет восстановлена полностью. Остальные строки могут исчезнуть целиком или частично, в зависимости от того, остались ли они в окне при уменьшении размера окна (рис. 2.3).

Рис. 2.3. Исчезновение строк при изменении размера окна

Если бы все строки выводились только при обработке сообщения WM_PAINT, в случае частичного или полного перекрытия окна другими окнами изображение было бы полностью восстановлено.

2.6. Другие функции для вывода текста

Кроме рассмотренной нами функции TextOut программный интерфейс Windows содержит и другие, более сложные и многоцелевые функции, предназначенные для вывода текста.

Функция ExtTextOut

Функция ExtTextOut обеспечивает более широкие возможности по сравнению с функцией TextOut и, соответственно, имеет большее количество параметров:

BOOL WINAPI ExtTextOut(HDC hdc, 
   int nXStart, int nYStart,
   UINT fuOptions, const RECT FAR* lprc, 
   LPCSTR lpszString, UINT cbString, int FAR* lpDx);

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

Параметр hdc задает идентификатор контекста отображения, который используется для вывода текста.

Параметры nXStart и nYStart определяют соответственно X- и Y-координаты начальной позиции вывода текста. Если перед вызовом этой функции вы установили режим обновления текущей позиции (вызвав функцию SetTextAligh с параметром TA_UPDATECP), параметры nXStart и nYStart игнорируются. Текст будет выведен начиная с текущей позиции, которая устанавливается за последним выведенным ранее символом.

Параметр fuOptions позволяет определить тип ограничивающей прямоугольной области, заданной параметром lprc. Этот параметр задается в виде двух флагов, которые можно объединять логической операцией ИЛИ.

Первый флаг имеет имя ETO_CLIPPED. Если указан этот флаг, прямоугольная область, заданная параметром lprc, определяет область ограничения для вывода текста.

Второй флаг имеет имя ETO_OPAQUE. Этот флаг позволяет закрасить прямоугольную область цветом, заданным при помощи функции SetBkColor.

Параметр lprc является дальним указателем на структуру типа RECT. Он определяет прямоугольную область, используемую для ограничения или закрашивания. В качестве этого параметра вы можете указать значение NULL, при этом область ограничения не используется.

Для определения адреса выводимой текстовой строки следует указать параметр lpszString. Этот параметр является дальним указателем на строку символов.

Длина строки символов задается в байтах параметром cbString.

Параметр lpDx позволяет задать расстояние между отдельными символами. Если этот параметр указан как NULL, при выводе текста расстояние между символами определяется шрифтом, выбранным в контекст отображения. Если же в качестве этого параметра указать дальний адрес массива значений типа int, вы сможете определять индивидуальное расстояние между отдельными символами. Элемент массива с индексом n определяет расстояние между n-м и n+1-м символами строки. Размер массива должен быть равен значению, указанному в параметре cbString.

Функция ExtTextOut при нормальном завершении возвращает ненулевое значение (TRUE). В противном случае возвращается значение FALSE.

Функция TabbedTextOut

Функция TabbedTextOut предназначена для вывода текстовых строк, содержащих символы табуляции. Эту функцию удобно использовать для вывода текстовых таблиц.

Приведем прототип функции:

LONG WINAPI TabbedTextOut(HDC hdc, 
   int xPosStart, intyPosStart, 
   LPCSTR lpszString, int cbString,
   int cTabStops, int FAR* lpnTabPositions, int nTabOrigin);

Параметр hdc задает идентификатор контекста отображения, который используется для вывода текста.

Параметры xPosStart и yPosStart определяют соответственно X- и Y-координаты начальной позиции вывода текста. Если перед вызовом этой функции вы установили режим обновления текущей позиции (вызвав функцию SetTextAligh с параметром TA_UPDATECP), параметры xPosStart и yPosStart игнорируются. Текст будет выведен начиная с текущей позиции, которая устанавливается за последним выведенным ранее символом.

Для определения адреса выводимой текстовой строки следует указать параметр lpszString. Этот параметр является дальним указателем на строку символов.

Длина строки символов задается в байтах параметром cbString.

Параметр cTabStops определяет количество значений в массиве позиций символов табуляции. Если значение этого параметра равно 1, расстояние между символами табуляции определяется первым элементом массива, адрес которого передается через параметр lpnTabPositions.

Указатель lpnTabPositions определяет адрес массива целых чисел, определяющих расположение символов табуляции. Массив должен быть отсортирован в порядке увеличения значений.

Параметр nTabOrigin определяет логическую X-координату начальной позиции, относительно которой происходит расширение символов табуляции.

Функция TabbedTextOut возвращает размер (в логических единицах) области, занятой выведенной строкой. Старшее слово возвращаемого значения содержит высоту строки, младшее - ширину строки.

Функция DrawText

Функция DrawText предназначена для форматированного вывода текста в прямоугольную область окна:

int WINAPI DrawText(HDC hdc, 
   LPCSTR lpsz, int cb, 
   RECT FAR* lprc, UINT fuFormat);

Параметр hdc определяет контекст устройства вывода. В качестве этого параметра нельзя указывать контекст метафайла (метафайлами мы займемся позже, в одном из следующих томов "Библиотеки системного программиста").

Адрес выводимой текстовой строки задается при помощи параметра lpsz. Этот параметр является дальним указателем на строку символов.

Длина строки символов задается в байтах параметром cb.

Параметр lprc является дальним указателем на структуру типа RECT, определяющую координаты верхнего левого и правого нижнего углов прямоугольной области, в которую будет выведен текст. Текст может быть выравнен и отформатирован внутри этой области в соответствии со значением, указанным параметром fuFormat.

Параметр задается как набор флагов с использованием операции логического ИЛИ:

Значение Описание
DT_BOTTOM Выравнивание текста по верхней границе прямоугольника, заданного параметром lprc. Этот флаг должен использоваться в комбинации с флагом DT_SINGLELINE
DT_CALCRECT Определение высоты и ширины прямоугольника без вывода текста. Если указан этот флаг, функция DrawText возвращает высоту текста. Если выводимый текст состоит из нескольких строк, функция использует ширину прямоугольника, заданную параметром lprc, и расширяет базу этого прямоугольника до тех пор, пока прямоугольник не вместит в себя последнюю строку текста. Если текст состоит из одной строки, функция изменяет правую сторону прямоугольника до тех пор, пока последний символ строки не поместится в прямоугольник.
В структуру, заданную параметром lprc, после возврата из функции будут записаны размеры прямоугольной области, использованной для вывода текста
DT_CENTER Центрирование текста по горизонтали
DT_EXPANDTABS Расширение символов табуляции. По умолчанию каждый символ табуляции расширяется в восемь символов
DT_EXTERNALLEADING Вывод текста выполняется с учетом межстрочного расстояния (external leading), определенного для выбранного шрифта разработчиком шрифтов
DT_LEFT Выравнивание текста по левой границе прямоугольной области, заданной параметром lprc
DT_NOCLIP Вывод текста выполняется без ограничения области вывода. Этот режим увеличивает скорость вывода текста
DT_NOPREFIX Выключение директивы подчеркивания &. По умолчанию символ & используется для того, чтобы вывести следующий символ с выделением подчеркиванием. Для вывода самого символа & его следует повторить дважды. Флаг DT_NOPREFIX выключает этот режим
DT_RIGHT Выравнивание текста по правой границе прямоугольной области, заданной параметром lprc
DT_SINGLELINE Текст состоит только из одной строки. Символы возврата каретки и перевода строки не вызывают перехода на следующую строку
DT_TABSTOP Установить точки останова по символам табуляции
DT_TOP Выравнивание текста по верхней границе прямоугольной области, заданной параметром lprc. Флаг используется только для текста, состоящего из одной строки
DT_VCENTER Выравнивание текста по вертикали. Флаг используется только для текста, состоящего из одной строки. Если текст состоит из одной строки, необходимо вместе с этим флагом указывать флаг DT_SINGLELINE
DT_WORDBREAK Выполнять свертку слов в пределах заданной параметром lprc прямоугольной области. Если слово не помещается в строке, оно может быть перенесено на следующую строку

Если перед вызовом функции вы установили режим обновления текущей позиции (вызвав функцию SetTextAligh с параметром TA_UPDATECP), текст будет выведен начиная с текущей позиции, которая устанавливается за последним выведенным ранее символом. Свертка слов при этом не выполняется.

Функция DrawText в случае успешного завершения возвращает высоту прямоугольной области, использованной для вывода текста.

2.7. Изменение режимов вывода текста

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

Функция SetTextAlign позволяет определить выравнивание прямоугольной области, используемой для вывода текста, относительно указанных координат вывода. Эта функция имеет следующий прототип:

UINT WINAPI SetTextAlign(HDC hdc , UINT fuAlign);

Первый параметр функции, hdc, является идентификатором контекста отображения, используемого для вывода текста.

Второй параметр, fuAlign, состоит из трех наборов битовых флагов. Флаги из каждого набора можно объединять при помощи логической операции ИЛИ.

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

Символическое имя флага Описание
TA_CENTER Выравнивание по центру
TA_LEFT Выравнивание по левой границе. Этот способ выравнивания используется по умолчанию
TA_RIGHT Выравнивание по правой границе

Вторая группа флагов отвечает за выравнивание текста по вертикали:

Символическое имя флага Описание
TA_BASELINE Выравнивание по базовой линии шрифта
TA_TOP Выравнивание по верхней границе. Этот режим используется по умолчанию
TA_BOTTOM Выравнивание по нижней границе

Последняя, третья группа флагов отвечает за обновление текущей позиции вывода текста:

Символическое имя флага Описание
TA_NOUPDATECP Не обновлять текущую позицию после вывода текста функциями TextOut и ExtTextOut. Этот режим используется по умолчанию
TA_UPDATECP Обновлять текущую позицию после вывода текста функциями TextOut и ExtTextOut

Вы можете узнать состояние флагов выравнивания, выбранных в контекст отображения, при помощи функции GetTextAlign:

UINT WINAPI GetTextAlign(HDC hdc);

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

Можно изменить цвет текста. Для этого необходимо вызвать функцию SetTextColor:

COLORREF WINAPI SetTextColor(HDC hdc, COLORREF clrref);

Параметр hdc определяет контекст отображения, для которого вы собираетесь изменить цвет текста.

Структура clrref определяет цвет.

Функция SetTextColor возвращает значение цвета, которое использовалось для вывода текста раньше.

Тип данных COLORREF определен в файле windows.h следующим образом:

typedef DWORD COLORREF;

Для указания цвета удобно использовать макрокоманду RGB, определенную в файле windows.h:

#define RGB(r,g,b) ((COLORREF)(((BYTE)(r) | 
   ((WORD)(g)<<8)) | (((DWORD)(BYTE)(b))<<16)))

В качестве параметров для этой макрокоманды вы можете указывать интенсивность отдельных цветов в диапазоне от 0 до 255:

Параметр Цвет
r Красный
g Зеленый
b Голубой

Для определения отдельных цветовых компонент из возвращаемого функцией SetTextColor значения удобно использовать макрокоманды GetRValue, GetGValue и GetBValue, определенные в файле windows.h:

#define GetRValue(rgb)      ((BYTE)(rgb))
#define GetGValue(rgb)      ((BYTE)(((WORD)(rgb)) >> 8))
#define GetBValue(rgb)      ((BYTE)((rgb)>>16))

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

В любой момент времени вы можете определить текущий цвет, используемый для вывода текста. Для этого следует вызвать функцию GetTextColor:

COLORREF WINAPI GetTextColor(HDC hdc);

Для указанного при помощи единственного параметра контекста отображения функция возвращает цвет, используемый для вывода текста.