|
На предыдущих уроках Вы познакомились с основами программирования под Windows. Был приведен пример простейшей программы, которая просто выводит текст на экран. Может быть Вы уже заметили, что у каждого окна Windows есть рабочая область - часть окна, в которой обычно происходит рисование и вывод текста. Обратите внимание, что в Windows-программе рабочая область окна очень часто не имеет фиксированного размера. Ваша прогамма должна учитывать размер окна при выводе данных в него.
Может оказаться, что размеры рабочей области окна недостаточны даже для отображения единственного слова. С другой стороны, рабочая область может быть и очень большой (например, для полностью развернутого окна при использовании дисплея с высоким разрешением). Вы должны предусмотреть заранее эти два случая.
Следует также учесть, что в Windows-программе можно выводить текст с использованием различных шрифтов и размеров символов. Далее будет рассказано, как это делается.
Еще раз о WM_PAINT
При программировании в DOS Вам не нужно беспокоиться, что информация, выведенная однажды на экран, куда-то денется. Под DOS можно запустить только одну программу, и ей выделяется все экранное пространство. Под Windows это не так. Пользователь может полностью свернуть окно Вашей программы, и когда оно будет развернуто вновь, Вы должны позаботиться о том, чтобы данные, которые были раньше отображены в окне, были выведены заново. Это необходимо потому, что Windows не сохраняет содержимое окна при его перекрытии или сворачивании (т.к. при этом пришлось бы сохранять очень много данных).
Как Вы уже знаете, оконная процедура получает сообщение WM_PAINT каждый раз, когда рабочая область окна становится недействительной (требует перерисовки). Вы должны стремиться к тому, чтобы весь вывод в окно осуществлялся только при получении сообщения WM_PAINT. Структура программы от этого только выиграет.
В некоторых случаях Windows все же сохраняет часть рабочей области, когда она временно закрывается. Происходит это обычно в следующих случаях:
- Windows удаляет диалоговое окно или окно сообщения, которое перекрывало часть окна программы;
- Раскрывается/закрывается пункт меню;
- Курсор мыши перемещается по рабочей области окна;
- Иконка перемещается по рабочей области.
В этих случаях сообщение WM_PAINT не будет посылаться окну, и дополнительной перерисовки не потребуется.
Хотя Ваша оконная процедура может каждый раз полностью перерисовывать окно при поступлении сообщения WM_PAINT, очень часто достаточно перерисовать только его небольшую часть. Эту часть называют недействительным регионом (invalid region) или обновляемым регионом.
В Windows для каждого созданного окна есть структура, в которой содержится информация о рисовании (paint information structure), такая как координаты недействительного прямоугольника (invalid rectangle) или региона (invalid region). Если несколько регионов рабочей области окна становятся недействительными и оконная процедура не успевает обработать несколько сообщений WM_PAINT (точнее говоря Windows не успевает много раз вызвать оконную процедуру), то эти несколько регионов объединяются в один, и множество сообщений WM_PAINT заменяются одним сообщением.
Оконная процедура при обработке сообщения WM_PAINT может получить координаты недействительного прямоугольника и, естественно, перерисовывать только эту область. Хотя Вам никто не мешает всегда перерисовывать всю рабочую область окна (в этом случае, конечно, окно может перерисовываться значительно медленнее).
GDI - графический интерфейс
Графический интерфейс устройства (GDI) - подсистема Windows, которая отвечает за отображение графики (в том числе и текста, т.к. текст также прорисовывается) на устройствах отображения (дисплеях и принтерах).
Когда нам нужно было вывести текст в окно, мы пользовались функцией DrawText. Есть еще одна часто применяемая функция вывода текста - TextOut:
TextOut(hdc, x, y, pString, iLen);
Сейчас нас интересует прежде всего первый параметр этой функции - hdc. Это так называемый описатель контекста устройства (handle to a device context). Практически каждая функция GDI использует этот параметр. Он указывает, куда именно будет направлен вывод графических данных. Фактически этот описатель - просто число, которое является уникальным для каждого окна.
Как получить параметр hdc? Проще всего так:
...
HDC hdc;
PAINTSTRUCT ps;
...
switch(msg)
{
case WM_PAINT: hdc=BeginPaint(hwnd, &ps);
//использование функций GDI
// .
// .
// .
EndPaint(hwnd, &ps);
return 0;
...
}
//все остальные сообщения обрабатываются функцией DefWindowProc
return DefWindowProc(hwnd,msg,wParam,lParam);
Как видим параметр hdc возвращает функция BeginPaint, которая вызывается в самом начале обработки сообщения WM_PAINT. После того, как параметр hdc получен, можно вызывать любые функции GDI, использующие его. Как только дескриптор контекста устройства становится не нужен, следует (обязательно!) вызвать функцию EndPaint. Если сообщение WM_PAINT не обрабатывается в Вашей программе (это бывает редко), то оно должно обязательно обрабатываться функцией DefWindowProc. Ни в коем случае нельзя делать следующее:
...
switch(msg)
{
case WM_PAINT: return 0;
...
}
В этом случае сообщение WM_PAINT вообще не обрабатывается, недействительная область (требующая перерисовки) не становится действительной, что заставляет Windows снова и снова слать сообщение WM_PAINT. И так до бесконечности...
Как видим функция BeginPaint возвращает (точнее заполняет) еще одну переменную - структуру ps с типом PAINTSTRUCT. Этот тип определяется так:
typedef struct tagPAINTSTRUCT {
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT;
Нас будут интересовать только первые 3 поля этой структуры (остальные используются самой Windows):
hdc - описатель контекста устройства (то же, что и возвращаемое значение функции BeginPaint).
fErase - если равно TRUE, то это значит, что Windows сама перерисует фон, используя кисть hbrBackground, которая указывалась при регистрации класса окна в структуре WNDCLASS (или WNDCLASSEX). Значение FALSE указывает, что фон обновляться не будет.
rcPaint - структура типа RECT, в которой определяется недействительный прямоугольник (с координатами left (x0), top (y0), right (x1), bottom (y1)). Используя данное значение hdc, Вы не сможете рисовать вне этого прямоугольника. Значения координат недействительного прямоугольника даются относительно левого верхнего угла рабочей области окна.
Если Вы хотите рисовать не при обработке сообщения WM_PAINT, а при поступлении других сообщений, Вы можете получить hdc следующим образом:
hdc=GetDC(hwnd);
//здесь могут вызываться функции GDI
ReleaseDC(hwnd, hdc); //освобождение hdc
Также как BeginPaint и EndPaint, функции GetDC и ReleaseDC обязательно вызываются парой. В отличие от функции BeginPaint, которая делает действительной ранее недействительную область, функция GetDC этого не делает. Есть еще одно отличие между этими двумя функциями: при получении дескриптора hdc с помощью GetDC Вы получаете возможность рисовать во всей рабочей области окна, а не только в недействительном регионе.
Функция TextOut
Вот ее вызов:
TextOut(hdc, x, y, pString, iLen);
Первые три параметра этой функции уже должны быть понятны (контекст устройства и координаты вывода текста). Поговорим об остальных параметрах.
pString - указатель на строку, которая должна быть выведена. Если она оканчивается нулевым байтом, удобно заранее подсчитать длину строки так: iLen=lstrlen(pString). Функция TextOut сама не вычисляет длину строки, именно поэтому и нужен параметр iLen.
Как задаются координаты?
Существует несколько так называемых режимов отображения. Задаваемый по умолчанию режим отображения - MM_TEXT. В этом режиме координаты задаются пикселами относительно левого верхнего угла рабочей области окна. Значения по координате X увеличиваются при движении вправо, а по координате Y - вниз. Обратите внимание, что в структуре PAINTSTRUCT координаты недействительного прямоугольника всегда задаются в режиме MM_TEXT (т.е. пикселами). Как Вы уже наверное догадались, функция TextOut при выводе текста использует текущий режим отображения, шрифт и размер шрифта, текущие цвета фона и символов.
Шрифт
По умолчанию всегда используется так называемый системный шрифт (SYSTEM_FONT). Это шрифт, который используется Windows для текста заголовков, меню и окон диалога. Вы можете изменить шрифт, пользуясь функцией EzCreateFont. Вот пример программы.
#include <windows.h>
#include <math.h>
//описание оконной процедуры
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
//Это главная функция программы.
int WINAPI WinMain(HINSTANCE hInst,
HINSTANCE hPrevInst,
PSTR szCmdLine,
int iCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASSEX w;
static CHAR *szAppName={"MoveMouse"};
w.cbSize=sizeof(w);
w.style=CS_HREDRAW|CS_VREDRAW;
w.lpfnWndProc=WndProc;
w.cbClsExtra=0;
w.cbWndExtra=0;
w.hInstance=hInst;
w.hIcon=LoadIcon(NULL,IDI_APPLICATION);
w.hCursor=LoadCursor(NULL,IDC_ARROW);
w.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
w.lpszMenuName=NULL;
w.lpszClassName=szAppName;
w.hIconSm=w.hIcon;
RegisterClassEx(&w);
hwnd=CreateWindow(
szAppName,
"TextOut...",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
hInst,
NULL);
ShowWindow(hwnd,iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//-----------------------------------------------------
//выбор шрифта
#define EZ_ATTR_BOLD 1
#define EZ_ATTR_ITALIC 2
#define EZ_ATTR_UNDERLINE 4
#define EZ_ATTR_STRIKEOUT 8
//szFaceName - имя шрифта:
// "Courier New"
// "Courier New Bold"
// "Courier New Italic"
// "Courier New Bold Italic"
// "Times New Roman"
// "Times New Roman Bold"
// "Times New Roman Italic"
// "Times New Roman Bold Italic"
// "Arial"
// "Arial Bold"
// "Arial Italic"
// "Arial Bold Italic"
// "Symbol"
//iDeciPtHeight - высота шрифта в пунктах*10
//iDeciPtWidth - ширина шрифта или 0
//iAttributes - EZ_ATTR_BOLD, EZ_ATTR_ITALIC,
// EZ_ATTR_UNDERLINE, EZ_ATTR_STRIKEOUT
//fLogRes - TRUE-логическое разрешение
// FALSE-действительное разрешение
//-----------------------------------------------------
HFONT EzCreateFont(HDC hdc, char * szFaceName, int iDeciPtHeight,
int iDeciPtWidth, int iAttributes, BOOL fLogRes)
{
FLOAT cxDpi, cyDpi;
HFONT hFont;
LOGFONT lf;
POINT pt;
TEXTMETRIC tm;
SaveDC(hdc);
SetGraphicsMode(hdc, GM_ADVANCED);
ModifyWorldTransform (hdc, NULL, MWT_IDENTITY);
SetViewportOrgEx(hdc, 0, 0, NULL);
SetWindowOrgEx(hdc, 0, 0, NULL);
if(fLogRes)
{
cxDpi = (FLOAT)GetDeviceCaps(hdc, LOGPIXELSX);
cyDpi = (FLOAT)GetDeviceCaps(hdc, LOGPIXELSY);
}
else
{
cxDpi = (FLOAT)(25.4 * GetDeviceCaps(hdc, HORZRES) /
GetDeviceCaps(hdc, HORZSIZE));
cyDpi = (FLOAT)(25.4 * GetDeviceCaps(hdc, VERTRES) /
GetDeviceCaps(hdc, VERTSIZE));
}
pt.x = (int)(iDeciPtWidth * cxDpi / 72);
pt.y = (int)(iDeciPtHeight * cyDpi / 72);
DPtoLP(hdc, &pt, 1);
lf.lfHeight = - (int)(fabs (pt.y) / 10.0 + 0.5);
lf.lfWidth = 0;
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = iAttributes & EZ_ATTR_BOLD ? 700 : 0;
lf.lfItalic = iAttributes & EZ_ATTR_ITALIC ? 1 : 0;
lf.lfUnderline = iAttributes & EZ_ATTR_UNDERLINE ? 1 : 0;
lf.lfStrikeOut = iAttributes & EZ_ATTR_STRIKEOUT ? 1 : 0;
lf.lfCharSet = 0;
lf.lfOutPrecision = 0;
lf.lfClipPrecision = 0;
lf.lfQuality = 0;
lf.lfPitchAndFamily = 0;
strcpy(lf.lfFaceName, szFaceName);
hFont = CreateFontIndirect(&lf);
if(iDeciPtWidth != 0)
{
hFont = (HFONT)SelectObject(hdc, hFont);
GetTextMetrics(hdc, &tm);
DeleteObject(SelectObject(hdc, hFont));
lf.lfWidth = (int)(tm.tmAveCharWidth *
fabs (pt.x) / fabs (pt.y) + 0.5);
hFont = CreateFontIndirect(&lf);
}
RestoreDC(hdc, -1);
return hFont;
}
//Оконная процедура
LRESULT CALLBACK WndProc(HWND hwnd, UINT imsg,
WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
static HFONT hFont=NULL;
switch(imsg)
{
case WM_CREATE:
if(hFont==NULL)
hFont=EzCreateFont(ps.hdc,
(char*)"Courier New Bold",
100,//высота шрифта=10 пунктов
0,0,TRUE);
return 0;
case WM_PAINT:
BeginPaint(hwnd, &ps);
TextOut(ps.hdc,100,100,"Системный шрифт",15);
SelectObject(ps.hdc,hFont);
TextOut(ps.hdc,100,120,"Courier New Bold",16);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
DeleteObject(hFont);
hFont=NULL;
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd,imsg,wParam,lParam);
}
Функция EzCreateFont не будет обсуждаться сейчас, т.к. она довольно сложна для понимания на данном этапе. В дальнейшем Вы все поймете.
Как видим, изменить шрифт не так уж и просто. Хотя в большинстве случаев Вам будет не важно, каким шрифтом будет выводиться текст (системный шрифт вполне подойдет). С другой стороны, нужно внимательно подходить к смене шрифта. Во-первых, у пользователя Вашей программы может не оказаться требуемого шрифта (значит такой шрифт должен поставляться вместе с программой). Во-вторых, если Вы явно задаете высоту символов, следует проверить, как будет смотреться текст при различном разрешении экрана.
Сейчас нам гораздо полезнее научиться определять размер текущего шрифта.
Размер символов
Следующие друг за другом строки текста нужно выводить с учетом высоты символа. Ширину строки приблизительно можно оценить исходя из усредненной ширины символа. Итак, как определить размеры символа?
Примерно так:
TEXTMETRIC tm;
...
GetTextMetrics(hdc, &tm);
Естественно, сначала нужно получить контекст устройства hdc. Вы уже знаете, как это делается (через вызов BeginPaint при обработке сообщения WM_PAINT или вызовом GetDC при обработке других сообщений).
После вызова функции GetTextMetrics заполняется структура tm.
На следующем уроке мы поговорим о полях структуры TEXTMETRIC.
|