95 в двух томах Том I



Pdf просмотр
страница6/41
Дата27.11.2016
Размер4.32 Mb.
Просмотров7897
Скачиваний0
ТипРеферат
1   2   3   4   5   6   7   8   9   ...   41
0
Values of x
(значение х)

Values of y
(значение у

)
Client area
(рабочая область)
Рис. 3.2 Координаты x и y в режиме отображения MM_TEXT
Контекст устройства также определяет "регион отсечения" (clipping region). Как вы уже узнали, задаваемый по умолчанию регион отсечения — это или вся рабочая область, если описатель контекста устройства получен из функции GetDC, или недействительный регион, если описатель контекста устройства получен из функции
BeginPaint. Windows не выведет на экран ту часть строки символов, которая лежит вне региона отсечения. Если часть символа находится внутри отсекающей зоны, Windows выводит на экран только эту часть. Вывести что-нибудь за пределы рабочей области вашего окна очень непросто, поэтому не беспокойтесь о том, что это случится по невнимательности.
Системный шрифт
Контекст устройства также определяет шрифт, который Windows использует при выводе текста в рабочюю область. По умолчанию задается так называемый "системный шрифт" (system font) или (используя идентификатор заголовочных файлов Windows) SYSTEM_FONT. Системный шрифт — это шрифт, который Windows использует для текста заголовков, меню и окон диалога.
В ранних версиях Windows системный шрифт был фиксированным, т. е. у всех символов была одинаковая ширина, также как на пишущей машинке. Однако, начиная с Windows 3.0 (и до Windows 95 включительно), системный шрифт стал пропорциональным, т. е. разные символы имеют разную ширину. Например, символ W шире символа i. Так было сделано потому, что пропорциональный шрифт гораздо лучше читается, чем фиксированный. Но, как вы могли бы догадаться, изменение задаваемого по умолчанию шрифта с фиксированного на пропорциональный привело в негодность массу программ для первых версий Windows и потребовало от программистов изучить некоторые новые приемы работы с текстом.
Системный шрифт является "растровым шрифтом" (raster font). Это означает, что все символы определяются как пиксельные шаблоны. Распространяемые для продажи версии Windows включают в себя несколько системных шрифтов различных размеров для использования с различными видеоадаптерами. Когда производители новых видеоплат создают новый дисплейный драйвер, они также ответственны и за разработку системного шрифта, соответствующего разрешению дисплея. В противном случае, производителю пришлось бы указывать, какой именно системный шрифт из входящих в комплект Windows, может использоваться. Системный шрифт должен быть разработан таким образом, чтобы на экране помещалось, по крайней мере, 25 строк и 80 символов текста.
Только это гарантирует, что имеется некоторое соответствие между размером экрана и размером шрифта в
Windows.
Размер символа
Для вывода на экран нескольких строк текста с использованием функции TextOut, вам необходимо задать размеры символов шрифта. Следующие друг за другом строки текста вы размещаете на экране с учетом высоты символа, а колонки текста в рабочей области — исходя из усредненной ширины символов шрифта.
Размеры символов можно получить с помощью вызова функции GetTextMetrics. Для функции GetTextMetrics требуется описатель контекста устройства, поскольку ее возвращаемым значением является информация о шрифте, выбранном в данное время в контексте устройства. Windows копирует различные значения метрических параметров текста в структуру типа TEXTMETRIC. Значения определяются в единицах, зависящих от выбранного

48 в контексте устройства режима отображения. Выбранным по умолчанию в контексте устройства режимом отображения является режим MM_TEXT, а единицами измерения являются пиксели.
Для использования функции GetTextMetrics, во-первых, необходимо определить структурную переменную
(которую обычно называют tm):
TEXTMETRIC tm;
Далее нужно получить описатель контекста устройства и вызвать GetTextMetrics: hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
ReleaseDC(hwnd, hdc);
Теперь вы можете проанализировать значения в структуре текстовых размеров и, возможно, сохранить несколько из них для использования в будущем.
Метрические параметры текста. Подробности
Структура TEXTMETRIC обеспечивает полную информацию о выбранном в данный момент в контексте устройства шрифте. Вертикальный размер шрифта определяется только пятью величинами, как показано на рис. 3.3.
Эти величины вполне объяснимы. Значение tmInternalLeading — это величина пустого пространства, отведенного для указания специальных знаков над символом. Если это значение равно 0, то помеченные прописные буквы делаются немного меньше, чтобы специальный символ поместился внутри верхней части символа. Значение tmExternalLeading
— это величина пустого пространства, которое разработчик шрифта установил для использования между строками символов. Вы можете согласиться или отклонить предложение разработчика при распределении пространства между строками текста.
В структуре TEXTMETRIC имеется два поля, описывающих ширину символа: tmAveCharWidth (усредненная ширина символов строки) и tmMaxCharWidth (ширина самого широкого символа шрифта). Для фиксированного шрифта эти две величины одинаковы.
В примерах программ этой главы используется другое значение ширины символа — средняя ширина прописных букв. Она может быть точно рассчитана как 150% от tmAveCharWidth.
tmExternalLeading
tmInternalLeading
tmHeight
tmAscent
Baseline
tmDescent
Рис. 3.3 Пять значений, определяющих вертикальный размер шрифта
Важно понимать, что размеры системного шрифта зависят от разрешающей способности дисплея, на котором работает Windows. Система Windows обеспечивает независимый от оборудования графический интерфейс, но, тем не менее, вам необходимо помочь ей. Не пишите программы для Windows, опирающиеся на конкретные размеры

49 символов. Не задавайте никаких фиксированных значений. Используйте функцию GetTextMetrics для получения этой информации.
Форматирование текста
Поскольку размеры системного шрифта не меняются в рамках одного сеанса работы с системой Windows, вам необходимо вызвать функцию GetTextMetrics только один раз при выполнении программы. Хорошо сделать этот вызов при обработке сообщения WM_CREATE в оконной процедуре. Сообщение WM_CREATE — это первое сообщение, которое принимает оконная процедура. Windows вызывает вашу оконную процедуру с сообщением
WM_CREATE, когда вы вызываете в WinMain функцию CreateWindow.
Предположим, что вы пишите программу для Windows, которая выводит на экран несколько строк текста, появляющихся друг за другом в направлении к нижней границе рабочей области. Вы хотите получить значения высоты и ширины символов. Внутри оконной процедуры вы можете определить две переменные для хранения средней ширины символов (cxChar) и полной высоты символов (cyChar): static int cxChar, cyChar;
Префикс с, добавленный к именам переменных, означает "счетчик" (сount) и в сочетании с x или y относится к длине или ширине. Эти переменные определяются как статические, поскольку они должны оставаться без изменений при обработке оконной процедурой других сообщений (таких как WM_PAINT). Если эти переменные определяются как глобальные вне какой бы то ни было функции, то нет необходимости определять их как статические.
Здесь представлен код обработки сообщения WM_CREATE: case WM_CREATE: hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm); cxChar = tm.tmAveCharWidth; cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hwnd, hdc); return 0;
Если вы не хотите учитывать межстрочное пространство (external leading), то можно использовать: cyChar = tm.tmHeight;
От вас зависит, как использовать полученный размер символов для расчета координат при выводе информации.
Самое простое — оставить пустое поле cyChar в верхней части рабочей области, и cxChar в ее левой части. Для вывода на экран нескольких строк текста с выравниванием по левому краю при вызове функции TextOut используйте следующее значение координаты x: cxChar
Значение координаты y в TextOut: cyChar *(1 + i) где i — это номер строки, начиная с 0.
Вы часто будете сталкиваться с необходимостью выводить отформатированные числа как простые символьные строки. Если вы программировали для MS-DOS с использованием стандартных библиотечных функций языка С, вы, вероятно, для форматирования применяли функцию printf. В Windows функция printf не работает, поскольку
printf предназначена для стандартного устройства вывода, а установка в Windows не имеет смысла.
В Windows есть аналогичная функция sprintf. Она работает точно также, как и функция printf, отличие заключается в том, что форматируемая строка помещается в символьный массив. Для вывода строки на экран можно затем использовать функцию TextOut. Очень удобно то, что возвращаемым значением sprintf является длина строки — вы можете передать это значение в TextOut в качестве параметра iLength. Следующий фрагмент программы показывает типовое использование функций sprintf и TextOut

: int iLength; char szBuffer[40];
[другие инструкции программы]

iLength = sprintf(szBuffer, "
Сумма
%d и
%d равна
%d", nA, nB, nA + nB);
TextOut(hdc, x, y, szBuffer, iLength);
Для достаточно простых задач вы могли бы избежать вычисления iLength и объединить два оператора в один:
TextOut(hdc, x, y
, szBuffer, sprintf(szBuffer, "Сумма %d и %d равна %d", nA, nB, nA + nB));

50
Это не очень красиво, но вполне работоспособно.
Если вам не нужно выводить числа с плавающей точкой, то вместо sprintf вам лучше использовать функцию
wsprintf. Синтаксис функции wsprintf такой же, как и sprintf, но она включена в Windows, поэтому ее использование не увеличит размер вашего EXE-файла.
Соединим все вместе
Теперь, как кажется, у нас есть все необходимое, чтобы написать простую программу для вывода на экран нескольких строк текста. Мы знаем, как получить описатель контекста устройства, как использовать функцию
TextOut, и как разместить текст, основываясь на размере одного символа. Осталось только придумать, что бы такое интересное вывести на экран.
Информация, получаемая при вызове функции Windows GetSystemMetrics, достаточно полезна. Эта функция возвращает информацию о размере различных графических элементов Windows, таких как значки, курсоры, панели заголовков и полосы прокрутки. В функции GetSystemMetrics имеется один параметр, называемый "индекс"
(index). Этот индекс — один из 73 целых идентификаторов, определяемых в заголовочных файлах Windows.
Возвращаемым значением GetSystemMetrics является целое, обычно это размер элемента, указанного в параметре.
Давайте напишем программу для вывода на экран некоторой части информации, получаемой при вызове функции
GetSystemMetrics в простом формате: на каждой строке по одному элементу. Работать с этой информацией будет легче, если создать заголовочный файл, в котором нужно определить массив структур, содержащих идентификаторы индексов
GetSystemMetrics и текст, который мы хотим выводить на экран для каждого возвращаемого функцией значения.
Заголовочный файл назовем SYSMETS.H, он показан на рис. 3.4.
SYSMETS.H
/*-----------------------------------------------
SYSMETS.H -- System metrics display structure
-----------------------------------------------*/
#define NUMLINES((int)(sizeof sysmetrics / sizeof sysmetrics [0])) struct
{ int iIndex; char *szLabel; char *szDesc;
} sysmetrics [] =
{
SM_CXSCREEN, "SM_CXSCREEN", "Screen width in pixels",
SM_CYSCREEN, "SM_CYSCREEN", "Screen height in pixels",
SM_CXVSCROLL, "SM_CXVSCROLL", "Vertical scroll arrow width",
SM_CYHSCROLL, "SM_CYHSCROLL", "Horizontal scroll arrow height",
SM_CYCAPTION, "SM_CYCAPTION", "Caption bar height",
SM_CXBORDER, "SM_CXBORDER", "Window border width",
SM_CYBORDER, "SM_CYBORDER", "Window border height",
SM_CXDLGFRAME, "SM_CXDLGFRAME", "Dialog window frame width",
SM_CYDLGFRAME, "SM_CYDLGFRAME", "Dialog window frame height",
SM_CYVTHUMB, "SM_CYVTHUMB", "Vertical scroll thumb height",
SM_CXHTHUMB, "SM_CXHTHUMB", "Horizontal scroll thumb width",
SM_CXICON, "SM_CXICON", "Icon width",
SM_CYICON, "SM_CYICON", "Icon height",
SM_CXCURSOR, "SM_CXCURSOR", "Cursor width",
SM_CYCURSOR, "SM_CYCURSOR", "Cursor height",
SM_CYMENU, "SM_CYMENU", "Menu bar height",
SM_CXFULLSCREEN, "SM_CXFULLSCREEN", "Full screen client area width",
SM_CYFULLSCREEN, "SM_CYFULLSCREEN", "Full screen client area height",
SM_CYKANJIWINDOW, "SM_CYKANJIWINDOW", "Kanji window height",
SM_MOUSEPRESENT, "SM_MOUSEPRESENT", "Mouse present flag",
SM_CYVSCROLL, "SM_CYVSCROLL", "Vertical scroll arrow height",
SM_CXHSCROLL, "SM_CXHSCROLL", "Horizontal scroll arrow width",
SM_DEBUG, "SM_DEBUG", "Debug version flag",
SM_SWAPBUTTON, "SM_SWAPBUTTON", "Mouse buttons swapped flag",
SM_RESERVED1, "SM_RESERVED1", "Reserved",
SM_RESERVED2, "SM_RESERVED2", "Reserved",

51
SM_RESERVED3, "SM_RESERVED3", "Reserved",
SM_RESERVED4, "SM_RESERVED4", "Reserved",
SM_CXMIN, "SM_CXMIN", "Minimum window width",
SM_CYMIN, "SM_CYMIN", "Minimum window height",
SM_CXSIZE, "SM_CXSIZE", "Minimize/Maximize icon width",
SM_CYSIZE, "SM_CYSIZE", "Minimize/Maximize icon height",
SM_CXFRAME, "SM_CXFRAME", "Window frame width",
SM_CYFRAME, "SM_CYFRAME", "Window frame height",
SM_CXMINTRACK, "SM_CXMINTRACK", "Minimum window tracking width",
SM_CYMINTRACK, "SM_CYMINTRACK", "Minimum window tracking height",
SM_CXDOUBLECLK, "SM_CXDOUBLECLK", "Double click x tolerance",
SM_CYDOUBLECLK, "SM_CYDOUBLECLK", "Double click y tolerance",
SM_CXICONSPACING, "SM_CXICONSPACING", "Horizontal icon spacing",
SM_CYICONSPACING, "SM_CYICONSPACING", "Vertical icon spacing",
SM_MENUDROPALIGNMENT, "SM_MENUDROPALIGNENT", "Left or right menu drop",
SM_PENWINDOWS, "SM_PENWINDOWS", "Pen extensions installed",
SM_DBCSENABLED, "SM_DBCSENABLED", "Double-Byte Char Set enabled",
SM_CMOUSEBUTTONS, "SM_CMOUSEBUTTONS", "Number of mouse buttons",
SM_SHOWSOUNDS, "SM_SHOWSOUNDS", "Present sounds visually"
};
Рис. 3.4 SYSMETS.H
Программа для вывода этой информации называется SYSMETS1. Файлы, необходимые для создания
SYSMETS.EXE (make-файл и файл с исходным текстом на С) приведены на рис. 3.5. Большинство операторов должны быть теперь вам понятны. За исключением имени программы, make-файл идентичен make-файлу программы HELLOWIN. В SYSMETS1.С функция WinMain фактически идентична HELLOWIN.
SYSMETS1.MAK
#------------------------
# SYSMETS1.MAK make file
#------------------------ sysmets1.exe : sysmets1.obj
$(LINKER) $(GUIFLAGS) -OUT:sysmets1.exe sysmets1.obj $(GUILIBS) sysmets1.obj : sysmets1.c sysmets.h
$(CC) $(CFLAGS) sysmets1.c
SYSMETS1.C
/*----------------------------------------------------
SYSMETS1.C -- System Metrics Display Program No. 1
(c) Charles Petzold, 1996
----------------------------------------------------*/
#include
#include
#include "sysmets.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{ static char szAppName[] = "SysMets1";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance;

52 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass); hwnd = CreateWindow( szAppName,
"Get System Metrics No. 1",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd); while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{ static int cxChar, cxCaps, cyChar; char szBuffer[10];
HDC hdc; int i;
PAINTSTRUCT ps;
TEXTMETRIC tm; switch(iMsg)
{ case WM_CREATE : hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm); cxChar = tm.tmAveCharWidth; cxCaps =(tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2; cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hwnd, hdc); return 0; case WM_PAINT : hdc = BeginPaint(hwnd, &ps); for(i = 0; i < NUMLINES; i++)
{
TextOut( hdc, cxChar, cyChar *(1 + i), sysmetrics[i].szLabel, strlen(sysmetrics[i].szLabel)
);
TextOut( hdc, cxChar + 22 * cxCaps, cyChar *(1 + i), sysmetrics[i].szDesc,

53 strlen(sysmetrics[i].szDesc)
);
SetTextAlign(hdc, TA_RIGHT | TA_TOP);
TextOut( hdc, cxChar + 22 * cxCaps + 40 * cxChar, cyChar *(1 + i), szBuffer, wsprintf( szBuffer, "%5d",
GetSystemMetrics(sysmetrics[i].iIndex)
)
);
SetTextAlign(hdc, TA_LEFT | TA_TOP);
}
EndPaint(hwnd, &ps); return 0; case WM_DESTROY :
PostQuitMessage(0); return 0;
} return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Рис. 3.5 Программа SYSMETS1
На рис. 3.6 показано окно программы SYSMETS1, работающей на VGA. Из информации в окне программы следует, что ширина экрана составляет 640 пикселей, а высота 480 пикселей. Эти два значения, также как и многие другие значения, выведенные в окне, могут отличаться для различных типов дисплеев.
Рис. 3.6 Вывод информации программой SYSMETS1
Оконная процедура программы SYSMETS1.С
Оконная процедура WndProc программы SYSMETS1.С обрабатывает три сообщения: WM_CREATE, WM_PAINT и WM_DESTROY. Сообщение WM_DESTROY обрабатывается точно также, как и в программе HELLOWIN, рассмотренной в главе 2.
Сообщение WM_CREATE является первым сообщением, получаемым оконной процедурой. Оно генерируется операционной системой Windows, когда функция CreateWindow создает окно. При обработке сообщения
WM_CREATE SYSMETS1 получает контекст устройства для окна путем вызова функции GetDC, а также размеры

54 текста для системного шрифта по умолчанию путем вызова функции GetTextMetrics. SYSMETS1 сохраняет усредненное значение ширины символа в cxChar и полную высоту символов (включая поле tmExternalLeading) в
cyChar.
SYSMETS1 также сохраняет среднюю ширину символов верхнего регистра в статической переменной cxCaps. Для фиксированного шрифта cxCaps была бы равна cxChar. Для пропорционального шрифта cxCaps равна 150% от
cxChar. Младший бит поля tmPitchAndFamily структуры TEXTMETRIC равен 1 для пропорционального шрифта и
0 для фиксированного. SYSMETS1 использует значение этого бита для расчета cxCaps из cxChar следующим образом: cxCaps =(tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
SYSMETS1 полностью выполняет процедуру рисования окна во время обработки сообщения WM_PAINT. Как правило, оконная процедура путем вызова BeginPaint, получает, в первую очередь, описатель контекста устройства. В цикле for обрабатываются все элементы структуры sysmetrics, определенной в SYSMETS.H. Три колонки текста выводятся на экран тремя функциями TextOut. В каждом случае третий параметр TextOut — это выражение: cyChar *(1 + i)
Этот параметр показывает в пикселях положение верхней границы строки символов относительно верхней границы рабочей области. Таким образом программа делает верхний отступ равным cyChar. Первая строка текста
(если i равно 0) начинается на cyChar пикселей ниже верхней границы рабочей области.
Первая инструкция TextOut выводит на экран в первую колонку идентификаторы, написанные прописными буквами. Вторым параметром TextOut является cxChar. Он оставляет левый отступ между первым символом строки и левой границей рабочей области окна равным одному символу. Текст берется из поля szLabel структуры
sysmetrics. Функция периода выполнения strlen языка С используется для получения длины строки, которая необходима в качестве последнего параметра для TextOut.
Вторая инструкция TextOut выводит на экран описание значений системных размеров. Эти описания хранятся в поле szDesc структуры sysmetrics. В этом случае второй параметр TextOut — это выражение: cxChar + 22 * cxCaps
Максимальная длина идентификаторов, выводимых на экран в первой колонке прописными буквами, составляет
20 символов, поэтому вторая колонка должна начинаться в позиции, по крайней мере, на 20* cxCaps правее начала первой колонки текста.
Третий оператор TextOut выводит на экран численные значения, полученные от функции GetTextMetrics.
Пропорциональный шрифт делает форматирование колонки, выравненных по правому краю чисел, слегка обманчивым. Каждая цифра от 0 до 9 имеют одну и ту же ширину, но эта ширина больше, чем ширина пробела.
Числа могут быть шириной в одну или более цифр, поэтому начальное горизонтальное положение чисел может меняться от строки к строке.
Возможно, было бы проще, если бы мы выводили колонку выравненных по правому краю чисел, задав положение последней цифры, вместо первой. Это позволяет делать функция SetTextAlign. После вызова этой функции в программе SYSMETS1:
SetTextAlign(hdc, TA_RIGHT | TA_TOP); координаты, переданные последующим функциям TextOut, будут задавать правый верхний угол строки текста вместо ее левого верхнего угла.
Второй параметр функции TextOut для вывода колонки чисел — это выражение: cxChar + 22 * cxCaps + 40 * cxChar
Значение 40 * cxChar позволяет согласовать ширину второй колонки и ширину третьей колонки. Следующий за функцией TextOut другой вызов функции SetTextAlign возвращает все в исходное состояние на время до начала следующего цикла.
Не хватает места!
В программе SYSMETS1 проявляется одна очень неприятная проблема: если у вас нет огромного экрана и видеоадаптера с высоким разрешением, вы не сможете увидеть последних строк перечня измерений системы. Если вы сузите окно, то не сможете увидеть даже сами значения.
Программа SYSMETS1 ничего не знает о ширине рабочей области. Программа выводит текст, начиная от верхнего края окна и заставляет Windows отрезать все, что оказывается вне рамок рабочей области. Это нежелательно.
Первым шагом в решении этой проблемы должно быть выяснение того, насколько много информации, выводимой программой, может быть помещено в рабочую область.

55
Размер рабочей области
Если вы уже работаете с существующими приложениями для Windows, то обнаружите, что размер окон может меняться в очень широких пределах. Большинство (если предположить отсутствие в окнах меню или полос прокрутки) окон можно развернуть, и рабочая область займет весь экран, за исключением панели заголовка программы. Полный размер этой развернутой рабочей области становится доступным, благодаря функции
GetSystemMetrics, использующей параметры SM_CXFULLSCREEN и SM_CYFULLSCREEN. Для дисплея типа
VGA возвращаемые значения равны 640 и 461 пикселей. Минимальный размер окна может быть совершенно незначительным, иногда почти невидимым, когда рабочая область фактически исчезает.
Одним из наиболее общих способов определения размера рабочей области окна является обработка сообщения
WM_SIZE в оконной процедуре. Windows посылает в оконную процедуру сообщение WM_SIZE при любом изменении размеров окна. Переменная lParam, переданная в оконную процедуру, содержит ширину рабочей области в младшем слове и высоту в старшем слове. Код программы для обработки этого сообщения часто выглядит следующим образом: static int cxClient, cyClient;
[другие инструкции программы]
case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0;
Макросы LOWORD и HIWORD определяются в заголовочных файлах Windows. Также как cxChar и cyChar, переменные cxClient и cyClient определяются внутри оконной процедуры в качестве статических, так как они используются позднее при обработке других сообщений.
В конечном итоге за сообщением WM_SIZE будет следовать сообщение WM_PAINT. Почему? Потому что при определении класса окна мы следующим образом задали стиль класса:
CS_HREDRAW | CS_VREDRAW
Такой стиль класса указывает Windows на необходимость перерисовки как при горизонтальных изменениях размеров, так и при вертикальных.
Вы можете рассчитать полное число строк текста, которые помещаются внутри рабочей области окна по формуле: cyClient / cyChar
Результат может быть равен 0, если высота рабочей области слишком мала для вывода на экран целого символа.
Аналогично, примерное число строчных символов, которые вы сможете горизонтально изобразить в рабочей области равно: cxClient / cxChar
Если вы определяете cxChar и cyChar при обработке сообщения WM_CREATE, не беспокойтесь о возможном делении на 0 в этом выражении. Ваша оконная процедура получает сообщение WM_CREATE, когда WinMain вызывает CreateWindow. Первое сообщение WM_SIZE приходит несколько позднее, когда WinMain вызывает
ShowWindow, благодаря этому cxChar и cyChar всегда являются положительными ненулевыми величинами.
Знание размера рабочей области окна является первым шагом в обеспечении возможности для пользователя двигать текст внутри рабочей области, если рабочая область недостаточно велика, чтобы вместить в себя что-либо целиком. Если вы хорошо знакомы с другими приложениями для Windows, имеющими аналогичные требования, вы, вероятно, знаете, что нам нужно: добавить такие великолепные средства, как полосы прокрутки.
Полосы прокрутки
Полосы прокрутки являются одними из самых лучших возможностей, который дает графический интерфейс и манипулятор мышь. Они просты в использовании и обеспечивают удобный просмотр информации. Вы можете пользоваться полосами прокрутки для вывода на экран текста, графики, электронных таблиц, записей баз данных, картинок — всего, что требует больше пространства, чем доступно в рабочей области окна.
Полосы прокрутки предназначены для просмотра информации как в вертикальном (движение вверх и вниз), так и в горизонтальном (движение вправо и влево) направлениях. Вы можете щелкнуть мышью на стрелке в любом конце полосы прокрутки или между стрелками. Бегунок ("scroll box" или "thumb") перемещается по длине полосы прокрутки, индицируя положение информации на экране относительно документа в целом. Вы также можете с помощью мыши переместить бегунок в конкретное положение. На рис. 3.7 показано рекомендуемое использование вертикальной полосы прокрутки для просмотра текста.

56
Программисты иногда сталкиваются с проблемой терминологии, относящейся к полосам прокрутки, поскольку их точка зрения отличается от пользовательской. Пользователь, который передвигает бегунок вниз, хочет увидеть нижнюю часть документа; однако, программа фактически перемещает документ вверх относительно окна.
Документация Windows и идентификаторы ее заголовочных файлов основываются на точке зрения пользователя:
Прокрутка вверх означает движение к началу документа; прокрутка вниз означает движение к концу.
Ù åëêíèòå çäåñü
äëÿ ïðîêðóòêè
íà îäíó ñòðîêó
ââåðõ
Рис. 3.7 Вертикальная полоса прокрутки
Вставить в ваше окно приложения вертикальную или горизонтальную полосу прокрутки очень просто. Все, что вам нужно сделать, это включить идентификатор WS_VSCROLL (вертикальная прокрутка) и WS_HSCROLLW
(горизонтальная прокрутка) или оба сразу в описание стиля окна в инструкции CreateWindow. Эти полосы прокрутки всегда размещаются у правого края или в нижней части окна и занимают всю высоту или ширину рабочей области. Рабочая область не включает в себя пространство, занятое полосами прокрутки. Ширина вертикальной полосы прокрутки и высота горизонтальной постоянны для конкретного дисплейного драйвера. Если вам необходимы эти значения, вы можете получить их (как вы могли бы заметить), вызвав функцию
GetSystemMetrics.
Windows обеспечивает всю логику работы мыши с полосами прокрутки. Однако, у полос прокрутки нет интерфейса клавиатуры. Если вы хотите дублировать клавишами управления курсором некоторые функции полос прокрутки, вы должны точно реализовать эту логику (как это делается, можно прочесть в главе 5, целиком посвященной клавиатуре).
Диапазон и положение полос прокрутки
Каждая полоса прокрутки имеет соответствующий "диапазон" (range) (два целых, отражающих минимальное и максимальное значение) и "положение" (position) (местоположение бегунка внутри диапазона). Когда бегунок находится в крайней верхней (или крайней левой) части полосы прокрутки, положение бегунка соответствует минимальному значению диапазона. Крайнее правое (или крайнее нижнее) положение бегунка на полосе прокрутки соответствует максимальному значению диапазона.
По умолчанию устанавливается следующий диапазон полосы прокрутки: 0 (сверху или слева) и 100 (снизу или справа), но диапазон легко изменить на какое-нибудь более подходящее для вашей программы значение:
SetScrollRange(hwnd, iBar, iMin, iMax, bRedraw);
Параметр iBar равен либо SB_VERT, либо SB_HORZ, iMin и iMax являются минимальной и максимальной границами диапазона, а bRedraw устанавливается в TRUE, если Вы хотите, чтобы Windows перерисовала полосы прокрутки на основе вновь заданного диапазона.
Положение бегунка всегда дискретно. Например, полоса прокрутки с диапазоном от 0 до 4 имеет пять положений бегунка, как показано на рис. 3.8. Для установки нового положения бегунка внутри диапазона полосы прокрутки можно использовать функцию SetScrollPos:
SetScrollPos(hwnd, iBar, iPos, bRedraw);
Параметр iPos — это новое положение бегунка, оно должно быть задано внутри диапазона от iMin до iMax. Для получения текущего диапазона и положения полосы прокрутки в Windows используются похожие функции
(GetScrollRange и GetScrollPos).
Щелкните здесь для прокрутки на одну страницу вверх
Щелкните здесь для прокрутки на одну страницу вниз
Щелкните здесь для прокрутки на одну строку вверх
Переместите бегунок для перехода в нужное место
Щелкните здесь для прокрутки на одну строку вниз

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

Управляет логикой работы мыши с полосой прокрутки.

Обеспечивает временную "инверсию цвета" при нажатии на кнопку мыши на полосе прокрутки.

Перемещает бегунок в соответствие с тем, как внутри полосы прокрутки его перемещает пользователь.

Отправляет сообщения полосы прокрутки в оконную процедуру для окна, содержащего полосу прокрутки.
Ниже представлены сферы ответственности вашей программы:

Инициализация диапазона полосы прокрутки.

Обработка сообщений полосы прокрутки.

Обновление положения бегунка полосы прокрутки.

Рис. 3.8 Полосы прокрутки с пятью положениями бегунков
Сообщения полос прокрутки
Windows посылает оконной процедуре асинхронные сообщения WM_VSCROLL и WM_HSCROLL, когда на полосе прокрутки щелкают мышью или перетаскивается бегунок. Каждое действие мыши на полосе прокрутки вызывает появление по крайней мере двух сообщений, одного при нажатии кнопки мыши и другого, когда ее отпускают.
Младшее слово параметра wParam, которое объединяет сообщения WM_VSCROLL и WM_HSCROLL — это число, показывающее, что мышь осуществляет какие-то действия на полосе прокрутки. Его значения соответствуют определенным идентификаторам, которые начинаются с SB_, что означает "полоса прокрутки"
(scroll bar). Хотя в некоторых из этих идентификаторов используются слова "UP" и "DOWN", они применяются и к горизонтальным и к вертикальным полосам прокрутки, как показано на рис. 3.9. Ваша оконная процедура может получить множество сообщений типа SB_LINEUP, SB_PAGEUP, SB_LINEDOWN или SB_PSGEDOWN, если кнопка мыши остается нажатой при перемещении по полосе прокрутки. Сообщение SB_ENDSCROLL показывает, что кнопка мыши отпущена. Как правило, сообщения SB_ENDSCROLL можно игнорировать.
Если младшее слово параметра wParam равно SB_THUMBTRACK или SB_THUMBPOSITION, то старшее слово
wParam определяет текущее положение полосы прокрутки. Это положение находится между минимальным и максимальным значениями диапазона полосы прокрутки. Во всех других случаях при работе с полосами прокрутки старшее слово wParam следует игнорировать. Вы также можете игнорировать параметр lParam, который обычно используется для полос прокрутки, создаваемых в окнах диалога.
Положение 0
Положение 1
Положение 2
Положение 3
Положение 4
Положение 0
Положение 1
Положение 2
Положение 3
Положение 4

58
Рис. 3.9 Значения идентификаторов для параметра wParam сообщений полосы прокрутки
В документации по Windows указано, что младшее слово wParam может также быть равно SB_TOP или
SB_BOTTOM. Оно показывает, что полоса прокрутки была переведена в свое максимальное или минимальное положение. Однако, вы никогда не получите эти значения для полосы прокрутки, созданной в окне вашего приложения.
Обработка сообщений SB_THUMBTRACK и SB_THUMBPOSITION весьма проблематична. Если вы устанавливаете большой диапазон полосы прокрутки, а пользователь быстро перемещает бегунок по полосе, то
Windows отправит вашей оконной функции множество сообщений SB_THUMBTRACK. Ваша программа столкнется с проблемой обработки этих сообщений. По этой причине в большинстве приложений Windows эти сообщения игнорируются, а действия предпринимаются только при получении сообщения SB_THUMBPOSITION, которое означает, что бегунок оставлен в покое.
Однако, если у вас есть возможность быстро обновлять содержимое экрана, вы можете захотеть включить в программу обработку сообщений SB_THUMBTRACK. Но знайте, что те пользователи, которые обнаружат, что ваша программа мгновенно реагирует на перемещение бегунка по полосе прокрутки, несомненно будут пытаться двигать его как можно быстрее, чтобы понаблюдать, сможет ли программа отследить это движение — и они будут несказанно удовлетворены, если этого не произойдет.
Прокрутка в программе SYSMETS
Достаточно объяснений. Самое время использовать этот материал на практике. Давайте начнем с простого. Начнем мы с вертикальной прокрутки, поскольку требуется она гораздо чаще. Горизонтальная прокрутка может подождать. SYSMETS2 представлена на рис. 3.10.
Обновленный вызов функции CreateWindow добавляет вертикальную полосу прокрутки к окну, благодаря включению в описание стиля окна в CreateWindow идентификатора WS_VSCROLL:
WS_OVERLAPPEDWINDOW | WS_VSCROLL
SYSMETS2.MAK
#------------------------
# SYSMETS2.MAK make file
#------------------------ sysmets2.exe : sysmets2.obj
$(LINKER) $(GUIFLAGS) -OUT:sysmets2.exe sysmets2.obj $(GUILIBS) sysmets2.obj : sysmets2.c sysmets.h
$(CC) $(CFLAGS) sysmets2.c
Нажата: SB_LINEUP
Отпущена: SB ENDSCROLL
Нажата: SB_PAGEUP
Отпущена: SB_ ENDSCROLL
Нажата: SB_THUMBTRACK
Отпущена:
Нажата: SB_PAGEDOWN
Отпущена: SB_ENDSCROLL
Нажата: SB_LINEDOWN
Отпущена: SB_ENDSCROLL

59
SYSMETS2.C
/*----------------------------------------------------
SYSMETS2.C -- System Metrics Display Program No. 2
(c) Charles Petzold, 1996
----------------------------------------------------*/
#include
#include
#include "sysmets.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{ static char szAppName[] = "SysMets2";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass); hwnd = CreateWindow( szAppName,
"Get System Metrics No. 2",
WS_OVERLAPPEDWINDOW | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd); while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{ static int cxChar, cxCaps, cyChar, cyClient, iVscrollPos; char szBuffer[10];
HDC hdc; int i, y;
PAINTSTRUCT ps;
TEXTMETRIC tm;

60 switch(iMsg)
{ case WM_CREATE : hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm); cxChar = tm.tmAveCharWidth; cxCaps =(tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2; cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hwnd, hdc);
SetScrollRange(hwnd, SB_VERT, 0, NUMLINES, FALSE);
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE); return 0; case WM_SIZE : cyClient = HIWORD(lParam); return 0; case WM_VSCROLL : switch(LOWORD(wParam))
{ case SB_LINEUP : iVscrollPos -= 1; break; case SB_LINEDOWN : iVscrollPos += 1; break; case SB_PAGEUP : iVscrollPos -= cyClient / cyChar; break; case SB_PAGEDOWN : iVscrollPos += cyClient / cyChar; break; case SB_THUMBPOSITION : iVscrollPos = HIWORD(wParam); break; default : break;
} iVscrollPos = max(0, min(iVscrollPos, NUMLINES)); if (iVscrollPos != GetScrollPos(hwnd, SB_VERT))
{
SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
InvalidateRect(hwnd, NULL, TRUE);
} return 0; case WM_PAINT : hdc = BeginPaint(hwnd, &ps); for(i = 0; i < NUMLINES; i++)
{ y = cyChar *(1 - iVscrollPos + i);
TextOut( hdc, cxChar, y,

61 sysmetrics[i].szLabel, strlen(sysmetrics[i].szLabel)
);
TextOut( hdc, cxChar + 22 * cxCaps, y, sysmetrics[i].szDesc, strlen(sysmetrics[i].szDesc)
);
SetTextAlign(hdc, TA_RIGHT | TA_TOP);
TextOut( hdc, cxChar + 22 * cxCaps + 40 * cxChar, y, szBuffer, wsprintf( szBuffer, "%5d",
GetSystemMetrics(sysmetrics[i].iIndex)
)
);
SetTextAlign(hdc, TA_LEFT | TA_TOP);
}
EndPaint(hwnd, &ps); return 0; case WM_DESTROY :
PostQuitMessage(0); return 0;
} return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Рис. 3.10 Программа SYSMETS2
К оконной процедуре WndProc добавляются две строки для установки диапазона и положения вертикальной полосы прокрутки во время обработки сообщения WM_CREATE:
SetScrollRange(hwnd, SB_VERT, 0, NUMLINES, FALSE);
SetScrollPass(hwnd, SB_VERT, iVscrollPos, TRUE);
Структура sysmetrics содержит NUMLINES строк текста, поэтому границы диапазона полосы прокрутки устанавливается от 0 до NUMLINES. Каждое положение полосы прокрутки соответствует строке текста в верхней части рабочей области. Если бегунок полосы прокрутки находится в положении 0, то в окне сверху остается пустая строка. При увеличении значения, определяющего положение полосы прокрутки, путем перемещения бегунка вниз, текст будет подниматься. Если положение бегунка полосы прокрутки находится в крайнем нижнем положении полосы, то последняя строка текста находится на самом верху рабочей области окна.
Для упрощения обработки сообщений WM_VSCROLL статическая переменная с именем iVscrollPos определяется внутри оконной процедуры WndProc. Эта переменная соответствует текущему положению бегунка полосы прокрутки. Что касается сообщений SB_LINEUP и SB_LINEDOWN, то все, что нужно сделать, это изменить положение прокрутки на 1. При получении сообщений SB_PAGEUP и SB_PAGEDOWN появляется возможность перемещать текст постранично (вернее "поэкранно"), или, что то же самое, изменять положение полосы прокрутки на величину, равную cyClient деленную на cyChar. Для SB_THUMBPOSITION новое положение бегунка определяется старшим словом wParam. Сообщения SB_ENDSCROLL и SB_THUMBTRACK игнорируются.
Затем параметр iVscrollPos устанавливается с использованием макросов min и max, чтобы гарантировать, что значение параметра будет находиться между минимальным и максимальным значениями диапазона. Если положение прокрутки изменилось, оно обновляется с помощью функции SetScrollPos и все окно делается недействительным путем вызова InvalidateRect.
При вызове функции InvalidateRect вырабатывается сообщение WM_PAINT. Когда исходная программа
SYSMETS1 обрабатывала сообщение WM_PAINT, координата y для каждой строки рассчитывалась следующим образом:

62 cyChar *(1 + i)
В SYSMETS2 эта формула выглядит так: cyChar *(1 - iVscrollPos +i)
Цикл по прежнему выводит на экран NUMLINES строк текста, но для значений параметра iVscrollPos от 2 и выше, цикл начинает выводить строки за пределами верхней границы рабочей области. Поскольку строки находятся за пределами рабочей области, Windows эти строки на экран не выводит.
Вам уже говорилось, что мы начнем с простой программы. Она достаточно неэкономична и неэффективна. В дальнейшем мы ее модифицируем, но сначала разберемся с тем, как обновить рабочую область после сообщения
WM_VSCROLL.
Структурирование вашей программы для рисования
Оконная процедура в SYSMETS2 не перерисовывает рабочую область после обработки сообщения полосы прокрутки. Вместо этого она вызывает функцию InvalidateRect для того, чтобы сделать рабочую область недействительной. Это заставляет Windows поместить сообщение WM_PAINT в очередь сообщений.
Было бы хорошо организовать вашу Windows-программу таким образом, чтобы все действия по перерисовке рабочей зоны окна осуществлялись в ответ на сообщение WM_PAINT. Поскольку ваша программа должна быть готова перерисовать всю рабочую область окна в любое время по получении сообщения WM_PAINT, вам, вероятно, следует просто продублировать соответствующий код в других рисующих частях программы.
Вначале вы можете возмутиться по поводу этого высказывания, поскольку это так непохоже на нормальное программирование для PC. Нельзя отрицать, что иногда рисование в ответ на другие, отличные от WM_PAINT сообщения гораздо более удобно. (Программа KEYLOOK в главе 5 — именно такой пример). Но во многих случаях это просто не нужно, и, поскольку вы теперь специалист в области сбора всей необходимой для рисования информации в ответ на сообщение WM_PAINT, вы должны быть довольны результатами своей работы. Однако, вашей программе часто будет необходимо перерисовать только отдельную область экрана при обработке другого, отличного от WM_PAINT сообщения. В таком случае становится удобной функция InvalidateRect. Вы можете использовать ее, чтобы делать недействительными конкретные прямоугольные зоны рабочей области или рабочую область в целом.
Для некоторых приложений может оказаться недостаточным просто пометить некоторые зоны окна как недействительные для выработки сообщения WM_PAINT. После вызова функции InvalidateRect, Windows помещает сообщение WM_PAINT в очередь сообщений, и оконная процедура в конце концов его обрабатывает.
Однако, Windows обращается с сообщениями WM_PAINT как с сообщениями, имеющими наименьший приоритет, поэтому, если в системе происходит множество каких-то событий, то эти события могут произойти до того, как оконная процедура получит сообщения WM_PAINT. Каждый из вас видел в программах пустые белые "дыры", появляющиеся на месте окон диалога.
Если вы хотите немедленно обновить недействительную область, вы можете после вызова функции InvalidateRect вызвать UpdateWindow:
UpdateWindow(hwnd);
Вызов UpdateWindow приводит к немедленному вызову оконной процедуры с сообщением WM_PAINT в случае, если существует какая-либо недействительная зона в рабочей области окна. (Если вся рабочая область действительна, вызова оконной процедуры не произойдет.) Такие сообщения WM_PAINT минуют очередь сообщений. Оконная процедура вызывается прямо из Windows. После того как оконная процедура завершает перерисовку, она заканчивает свою работу, и Windows возвращает управление в программу на следующую после вызова UpdateWindow инструкцию.
Заметьте, что UpdateWindow — это та же самая функция, которая использовалась в WinMain для выработки первого сообщения WM_PAINT. При создании окна вся рабочая область является недействительной.
UpdateWindow заставляет оконную процедуру перерисовать ее.
Создание улучшенной прокрутки
Поскольку программа SYSMETS2 слишком неэффективна, как модель для повторения ее в других программах, давайте модифицируем ее. SYSMETS3 — наша окончательная версия программы SYSMETS этой главы — показана на рис. 3.11. В этой версии добавлена горизонтальная полоса прокрутки для прокрутки рабочей области влево и вправо, и перерисовка рабочей области организована более эффективно.
SYSMETS3.MAK
#------------------------
# SYSMETS3.MAK make file

63
#------------------------ sysmets3.exe : sysmets3.obj
$(LINKER) $(GUIFLAGS) -OUT:sysmets3.exe sysmets3.obj $(GUILIBS) sysmets3.obj : sysmets3.c sysmets.h
$(CC) $(CFLAGS) sysmets3.c
SYSMETS3.C
/*----------------------------------------------------
SYSMETS3.C -- System Metrics Display Program No. 3
(c) Charles Petzold, 1996
----------------------------------------------------*/
#include
#include "sysmets.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{ static char szAppName[] = "SysMets3";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass); hwnd = CreateWindow( szAppName,
"Get System Metrics No. 3",
WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd); while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{ static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth,

64 iVscrollPos, iVscrollMax, iHscrollPos, iHscrollMax; char szBuffer[10];
HDC hdc; int i, x, y, iPaintBeg, iPaintEnd, iVscrollInc, iHscrollInc;
PAINTSTRUCT ps;
TEXTMETRIC tm; switch(iMsg)
{ case WM_CREATE : hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm); cxChar = tm.tmAveCharWidth; cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2; cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hwnd, hdc); iMaxWidth = 40 * cxChar + 22 * cxCaps; return 0; case WM_SIZE : cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); iVscrollMax = max(0, NUMLINES + 2 - cyClient / cyChar); iVscrollPos = min(iVscrollPos, iVscrollMax);
SetScrollRange(hwnd, SB_VERT, 0, iVscrollMax, FALSE);
SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE); iHscrollMax = max(0, 2 +(iMaxWidth - cxClient) / cxChar); iHscrollPos = min(iHscrollPos, iHscrollMax);
SetScrollRange(hwnd, SB_HORZ, 0, iHscrollMax, FALSE);
SetScrollPos(hwnd, SB_HORZ, iHscrollPos, TRUE); return 0; case WM_VSCROLL : switch(LOWORD(wParam))
{ case SB_TOP : iVscrollInc = -iVscrollPos; break; case SB_BOTTOM : iVscrollInc = iVscrollMax - iVscrollPos; break; case SB_LINEUP : iVscrollInc = -1; break; case SB_LINEDOWN : iVscrollInc = 1; break; case SB_PAGEUP : iVscrollInc = min(-1, -cyClient / cyChar); break; case SB_PAGEDOWN : iVscrollInc = max(1, cyClient / cyChar);

65 break; case SB_THUMBTRACK : iVscrollInc = HIWORD(wParam) - iVscrollPos; break; default : iVscrollInc = 0;
} iVscrollInc = max(
-iVscrollPos, min(iVscrollInc, iVscrollMax - iVscrollPos)
); if (iVscrollInc != 0)
{ iVscrollPos += iVscrollInc;
ScrollWindow(hwnd, 0, -cyChar * iVscrollInc, NULL, NULL);
SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
UpdateWindow(hwnd);
} return 0; case WM_HSCROLL : switch(LOWORD(wParam))
{ case SB_LINEUP : iHscrollInc = -1; break; case SB_LINEDOWN : iHscrollInc = 1; break; case SB_PAGEUP : iHscrollInc = -8; break; case SB_PAGEDOWN : iHscrollInc = 8; break; case SB_THUMBPOSITION : iHscrollInc = HIWORD(wParam) - iHscrollPos; break; default : iHscrollInc = 0;
} iHscrollInc = max(
-iHscrollPos, min(iHscrollInc, iHscrollMax - iHscrollPos)
); if (iHscrollInc != 0)
{ iHscrollPos += iHscrollInc;
ScrollWindow(hwnd, -cxChar * iHscrollInc, 0, NULL, NULL);
SetScrollPos(hwnd, SB_HORZ, iHscrollPos, TRUE);
} return 0; case WM_PAINT : hdc = BeginPaint(hwnd, &ps);

66 iPaintBeg = max(0, iVscrollPos + ps.rcPaint.top / cyChar - 1); iPaintEnd = min(NUMLINES, iVscrollPos + ps.rcPaint.bottom / cyChar); for(i = iPaintBeg; i < iPaintEnd; i++)
{ x = cxChar *(1 - iHscrollPos); y = cyChar *(1 - iVscrollPos + i);
TextOut( hdc, x, y, sysmetrics[i].szLabel, strlen(sysmetrics[i].szLabel)
);
TextOut( hdc, x + 22 * cxCaps, y, sysmetrics[i].szDesc, strlen(sysmetrics[i].szDesc)
);
SetTextAlign(hdc, TA_RIGHT | TA_TOP);
TextOut( hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer, wsprintf( szBuffer, "%5d",
GetSystemMetrics(sysmetrics[i].iIndex)
)
);
SetTextAlign(hdc, TA_LEFT | TA_TOP);
}
EndPaint(hwnd, &ps); return 0; case WM_DESTROY :
PostQuitMessage(0); return 0;
} return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Рис. 3.11 Программа SYSMETS3
Далее рассматриваются улучшения в программе SYSMETS3 и то, как они реализованы в программе:

Вы не можете прокручивать экран так, чтобы последняя строка оказалась на самом верху рабочей зоны окна. Процесс прокрутки продолжается только до появления последней строки текста в нижней части рабочей области. Для этого необходимо, чтобы программа рассчитала новый диапазон полосы прокрутки
(и, возможно, новое положение бегунка) при обработке сообщения WM_SIZE. Логика WM_SIZE вычисляет диапазон полосы прокрутки на основе числа строк текста и размера рабочей области. Такой подход приводит к уменьшению диапазона — необходимо только получить возможность вновь увидеть текст, оказавшийся вне рабочей области.
Это решение дает интересный результат. Предположим, что рабочая область окна достаточно велика, чтобы вывести на экран весь текст, включая верхний и нижний отступы. В таком случае, и минимальное и максимальное положение диапазона полосы прокрутки будут равны 0. Что с этой информацией будет делать Windows? Она удалит полосу прокрутки из окна! Она больше не нужна. Аналогично получается, если рабочая область достаточно широка для вывода текста со строкой в 60 знаков, тогда горизонтальная полоса прокрутки не появляется на экране.

67

Для каждого действия с полосой прокрутки сначала рассчитывается приращение ее текущей позиции при обработке сообщений WM_VSCROLL и WM_HSCROLL. Это значение затем используется для прокрутки имеющегося в окне содержимого с помощью вызова функции ScrollWindow. Эта функция имеет следующий формат:
ScrollWindow(hwnd, xInc, yInc, pRect, pClipRect);
Значения xInc и yInc задают величину прокрутки в пикселях. В SYSMETS3 значения pRect и pClipRect устанавливаются в NULL для указания, что необходимо прокручивать всю рабочую область. Windows делает недействительным прямоугольную зону рабочей области, открываемую операцией прокрутки. Это приводит к выдаче сообщения WM_PAINT. InvalidateRect больше не нужна. (Отметьте, что функция
ScrollWindow не является процедурой GDI, поскольку ей не нужен описатель контекста устройства. Это одна из немногих функций Windows, которая меняет вид рабочей области окна, не являясь функциями
GDI.)

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

Поскольку сообщения WM_PAINT стали обрабатываться быстрее, теперь, очевидно, есть смысл обрабатывать в SYSMETS3 действия SB_THUMBTRACK для сообщений WM_VSCROLL. Ранее программа игнорировала сообщения SB_THUMBTRACK (которые посылаются, когда пользователь перемещает бегунок полосы прокрутки), а реагировала только на сообщения SB_THUMBPOSITION, которые посылаются, когда пользователь прекращает перемещение бегунка. Сообщение WM_VSCROLL также приводит к вызову функции UpdateWindow для немедленного обновления рабочей области окна. Когда Вы перемещаете бегунок по вертикальной полосе прокрутки, SYSMETS3 непрерывно прокручивает и обновляет рабочую область. Вам самим предлагается решить, насколько быстро работает SYSMETS3 (и Windows), и насколько оправданны внесенные изменения.
Мне не нравится пользоваться мышью
В первое время существования Windows довольно многие пользователи пытались не пользоваться мышью. На самом деле в самой Windows (и во многих программах для Windows) мышь не требовалась. Хотя сегодня, в основном, персональные компьютеры без мыши ушли в прошлое вместе с монохромными дисплеями и матричными принтерами, вам по-прежнему рекомендуется писать программы, где бы действия мыши дублировались с помощью клавиатуры. Это особенно очевидно для таких фундаментальных средств, как полосы прокрутки, тем более, что в наших клавиатурах имеется целый набор клавиш, управляющих движением курсора, которые должны выполнять те же операции.
В главе 5 вы узнаете, как использовать клавиатуру и как добавить в вашу программу интерфейс клавиатуры. Вы также увидите, что SYSMETS3 обрабатывает сообщения WM_VSCROLL, когда младшее слово в wParam равно
SB_TOP и SB_BOTTOM. Уже упоминалось, что оконная процедура не получает эти сообщения от полос прокрутки, поэтому для данного случая эти коды излишни. Когда мы в главе 5 вернемся к этой программе, вы увидите смысл во включении в программу этих действий.
1   2   3   4   5   6   7   8   9   ...   41


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал