лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows
.pdfОтображение текста |
141 |
|
|
|
|
Также функция позволяет более точно размещать глифы символов при исполь зовании флага ETO_GLYPH_INDEX. Такое размещение бывает необходимо для гра фических приложений, имеющих режим отображения текста WYSIWYG. В этом режиме обеспечивается печать документа на принтере точно в таком виде, в ка ком он представлен на экране. Более подробную информацию об использовании функции ExtTextOut для решения таких проблем можно найти в книге [6].
Нетривиальный вывод текста
В современных текстовых редакторах и других графических пакетах решаются различные нетривиальные задачи вывода текста. Неполный перечень таких задач приведен в следующем списке:
Управление кернингом (контекстная регулировка межсимвольных расстояний).
Закраска текста кистью.
Работа с текстом в растровом формате.
Применение траекторий GDI при выводе текста.
Эти вопросы не рассматриваются в книге, так как это привело бы к неоправданно му увеличению объема издания. Достаточно подробно они освещаются в книге [6].
Полосы прокрутки и вывод текста
При выводе текста может возникнуть ситуация, когда в клиентской области окна недостаточно места для его размещения. Например, длина строк может оказаться больше ширины окна, а количество строк слишком большим, чтобы поместиться в окне с данной высотой. Полосы прокрутки (scroll bars) являются самым удоб ным решением этой проблемы. Они просты в использовании и обеспечивают удоб ный просмотр информации.
Следует различать два вида полос прокрутки:
полоса прокрутки окна;
полоса прокрутки — элемент управления типа Scroll bar.
Второй вид полос прокрутки рассматривается в главе 7.
Полосы прокрутки окна, называемые также стандартными полосами прокрут ки, размещаются вдоль правой и нижней рамок окна, как показано на рис. 2.35. Чтобы они появились, достаточно при вызове функции CreateWindow указать фла ги WS_VSCROLL (вертикальная прокрутка) и WS_HSCROLL (горизонтальная прокрут ка) в параметре dwStyle. Вертикальная полоса прокрутки позволяет пользователю просматривать информацию в окне, прокручивая ее вниз или вверх. Горизонталь ная полоса прокрутки позволяет перемещать информацию вправо или влево. Тер минология полос прокрутки, связанная с направлениями прокрутки, ориентиро вана на пользователя. Фактически программа перемещает документ в окне в противоположном направлении.
На рисунке поясняется также рекомендуемое использование вертикальной полосы прокрутки для просмотра текста. Текущее положение информации в окне относительно документа в целом отображается положением движка (thumb). Кроме щелчков мышью в указанных местах пользователь может также при помо щи мыши переместить движок в любое необходимое положение. Использование горизонтальной полосы прокрутки осуществляется аналогичным образом.
142 |
Глава 2. GDI — графический интерфейс устройства |
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 2.35. Полосы прокрутки окна
Следует помнить, что, когда у окна появляются полосы прокрутки, клиентс кая область окна не включает в себя пространство, занятое полосами прокрутки.
Windows обеспечивает всю логику работы мыши с полосами прокрутки. Од нако у полос прокрутки нет интерфейса клавиатуры. Если необходимо дублиро вать некоторые функции полос прокрутки клавишами управления курсором, то следует реализовать эту логику самостоятельно (этот вопрос будет рассмотрен в главе 4).
Параметры полосы прокрутки
Каждая полоса прокрутки характеризуется несколькими параметрами:
Диапазон (range), задаваемый двумя целыми числами, отражающими минималь ное (nMin) и максимальное (nMax) значения в «единицах данных» проблемной области. При выводе текста «единицами данных» для вертикального измерения обычно считают строки текста, а в горизонтальном измерении1 — отдельные сим волы. Величина range определяется по формуле range = nMax – nMin + 1.
Положение (position) — целое число внутри диапазона, отражающее положе ние движка. Когда движок находится в крайней верхней или крайней левой позиции на полосе прокрутки, то его положение соответствует минимальному значению диапазона. Крайнее нижнее или крайнее правое положение движка соответствует максимальному значению диапазона.
Размер страницы (page) — целое число, отображающее количество «единиц данных», которые могут разместиться в клиентской области окна при его те кущих размерах. Windows использует размер страницы и диапазон полосы про крутки для управления длиной движка. При этом длина движка выглядит про порционально той части документа, которую пользователь видит в окне. Кроме того, если указан размер страницы, то система берет на себя управление види
1 При выводе растровых изображений «единицей данных» является 1 пиксел.
Отображение текста |
143 |
|
|
|
|
мостью полосы прокрутки при изменении размеров окна. Как только размер страницы становится больше диапазона, полоса прокрутки становится неви димой и недоступной.
По умолчанию для полосы прокрутки установлен диапазон от 0 до 100. Но вы можете переопределить этот диапазон. Так, если отображаемый документ содержит nLine строк, то можно использовать два варианта определения диапа зона. В первом случае для границ диапазона задаются значения nMin = 0 и nMax = = nLine – 1. Во втором случае задаются nMin = 1 и nMax = nLine. Первый вариант считается более предпочтительным, так как он согласуется с индексацией эле ментов в массиве, а отображаемый текст часто хранится в виде массива строк.
Для установки диапазона полосы прокрутки, размера страницы и текущего положения движка предназначена функция SetScrollInfo:
int SetScrollInfo(HWND hwnd, int fnBar, LPCSCROLLINFO lpsi, BOOL fRedraw);
Параметр hwnd содержит либо дескриптор окна, если функция применяется для стандартных полос прокрутки, либо дескриптор элемента управления Scroll Bar. Интерпретация зависит от значения второго параметра — fnBar.
Параметр fnBar определяет тип полосы прокрутки. Он может принимать зна чения SB_VERT (стандартная вертикальная полоса), SB_HORZ (стандартная гори зонтальная полоса) и SB_CTL (элемент управления Scroll Bar). Параметр lpsi содер жит указатель на структуру SCROLLINFO, которая будет рассматриваться ниже.
Параметр bRedraw определяет, должна ли система перерисовать полосу про крутки сразу после выполнения функции SetScrollInfo.
Структура SCROLLINFO определена в файле winuser.h следующим образом:
typedef struct tagSCROLLINFO |
|
|
||
{ |
|
|
|
|
UINT cbSize; |
// размер структуры в байтах |
|||
UINT fMask; |
// |
маска устанавливаемых/возвращаемых параметров |
||
int nMin; |
// |
минимальное |
значение |
диапазона |
int nMax; |
// |
максимальное |
значение |
диапазона |
UINT nPage; |
// |
размер страницы |
|
|
int nPos; |
// |
положение движка |
|
|
int nTrackPos; |
// |
текущее положение движка, перемещаемого пользователем |
||
} SCROLLINFO; |
|
|
|
|
Поле fMask задает параметры, которые будут устанавливаться функцией SetScrollInfo или возвращаться функцией GetScrollInfo. Значением этого поля может быть комбинация флагов, перечисленных в табл. 2.33.
Для получения информации о текущих значениях параметров полосы прокрут ки приложение может воспользоваться функцией GetScrollInfo.
Важно понимать, что если в программе используются полосы прокрутки, то вместе с Windows вы берете на себя ответственность за их поддержку и обновле ние положения движка. Так, Windows отвечает за следующие аспекты:
управление логикой работы мыши с полосой прокрутки;
обеспечение временной инверсии цвета при щелчке кнопкой мыши в зоне про крутки на одну страницу;
перемещение движка, когда пользователь захватывает его мышью и передви гает по полосе;
отправка сообщений полосы прокрутки в оконную процедуру;
144 |
Глава 2. GDI — графический интерфейс устройства |
|
|
переключение видимости полосы прокрутки в зависимости от того, помеща ется ли документ по своим размерам полностью в окне.
Таблица 2.33. Значения флагов для параметра fMask
Ôëàã |
Описание |
|
|
SIF_PAGE |
Поле nPage содержит размер страницы для пропорциональной |
|
полосы прокрутки |
SIF_POS |
Поле nPos содержит позицию движка, которая не обновляется при |
|
перемещении движка пользователем |
SIF_RANGE |
Поля nMin и nMax содержат минимальное и максимальное значения |
|
для диапазона прокрутки |
SIF_TRACKPOS |
Поле nTrackPos содержит текущую позицию движка, когда пользова- |
|
тель перетаскивает его (поле доступно только для функции |
|
GetScrollInfo) |
SIF_ALL |
Объединение SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS |
SIF_DISABLENOSCROLL |
Это значение используется только при установке параметров полосы |
|
прокрутки. Если новые параметры делают полосу прокрутки ненуж- |
|
ной, то вместо ее удаления система делает ее недоступной |
|
|
Код вашей программы отвечает за следующие аспекты:
инициализацию диапазона полосы прокрутки;
задание размера страницы для «пропорциональной» полосы прокрутки;
обработку сообщения полосы прокрутки;
обновление положения движка.
Сообщения полос прокрутки
Windows посылает оконной процедуре синхронные сообщения WM_VSCROLL и WM_HSCROLL, когда пользователь щелкает мышью на различных зонах вертикаль ной или горизонтальной полосы прокрутки либо перемещает движок.
Когда оконная процедура получает эти сообщения, параметр wParam содержит в своем младшем слове некоторый код, по которому можно узнать, какое событие произошло. Возможным значениям кода соответствуют определенные идентифи каторы, начинающиеся с префикса SB_.
На рис. 2.36 показано, какие коды сообщений вырабатывает Windows при тех или иных действиях пользователя. С каждым действием мыши связаны как ми нимум два сообщения. Одно сообщение создается при нажатии кнопки мыши, а второе — когда пользователь отпускает ее. Оконная процедура приложения мо жет получить множество сообщений с кодами SB_LINEUP и SB_PAGEUP, если кнопка мыши остается нажатой в соответствующей позиции полосы прокрутки. Сообще ние с кодом SB_ENDSCROLL показывает, что кнопка мыши отпущена. Как правило, сообщения SB_ENDSCROLL можно игнорировать.
При перемещении движка (thumb) мышью система вырабатывает серию сооб щений с кодом SB_THUMBTRACK. Если младшее слово параметра wParam имеет зна чение SB_THUMBTRACK или SB_THUMBPOSITION, то старшее слово параметра wParam
определяет текущее положение полосы прокрутки. Во всех остальных случаях старшее слово параметра wParam можно не учитывать. Также можно игнориро вать параметр lParam, который обычно используется для полос прокрутки, созда ваемых в окнах диалога.
Отображение текста |
145 |
Рис. 2.36. Значения младшего слова параметра wParam для сообщений полосы прокрутки |
|
Получив сообщение от полосы прокрутки, оконная процедура должна его обра ботать. Для этого сначала определяется приращение текущей позиции: yInc для вер тикальной полосы или xInc для горизонтальной полосы. Приращение выражено в «единицах данных». Оно может быть как положительным, так и отрицательным. Затем для прокрутки содержимого окна вызывается функция ScrollWindow:
BOOL ScrollWindow( |
|
|
HWND hWnd, |
// |
дескриптор окна |
int XAmount, |
// |
горизонтальный скроллинг |
int YAmount, |
// |
вертикальный скроллинг |
CONST RECT* lpRect, |
// указатель на прямоугольник клиентской области |
|
CONST RECT* lpClipRect |
// |
указатель на прямоугольник отсечения |
); |
|
|
Параметры XAmount и YAmount задают величину прокрутки в пикселах. Пара метры lpRect и lpClipRect чаще всего получают значение NULL, что означает про крутку для всей клиентской области.
Таким образом, обработка сообщения WM_VSCROLL сопровождается следующим вызовом:
ScrollWindow(hWnd, 0, -yStep * yInc, NULL, NULL);
Обработка сообщения WM_HSCROLL реализуется следующим вызовом:
ScrollWindow(hWnd, -cxChar * xInc, 0, NULL, NULL);
Вуказанных вызовах величина yStep задает шаг между строками в пикселах,
авеличина cxChar равна средней ширине символов в пикселах. Знак «минус» пе ред приращением отражает тот факт, что фактическое перемещение документа в окне противоположно направлению прокрутки с точки зрения пользователя.
После прокрутки содержимого окна вы должны позаботиться об обновлении позиции движка на полосе прокрутки. Например, для вертикальной полосы мо гут быть использованы следующие инструкции:
si.nPos += yInc;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
где si — переменная типа SCROLLINFO.
146 |
Глава 2. GDI — графический интерфейс устройства |
|
|
Заметим, что в результате выполнения функции ScrollWindow часть клиентс кой области освобождается от информации. Для того чтобы обеспечить вывод в освободившуюся зону новой порции текстового документа, приложение обыч но вызывает инструкции
InvalidateRect(hWnd, NULL, TRUE); UpdateWindow(hWnd);
Напомним, что функция UpdateWindow вызывает передачу сообщения WM_PAINT непосредственно оконной процедуре. Это важно, если вы хотите обеспечить не медленную реакцию приложения на действия пользователя, когда тот быстро пе ремещает движок полосы прокрутки (сообщение SB_THUMBTRACK). Дело в том, что при обычном порядке обработки функция InvalidateRect вызывает постановку со общения WM_PAINT в очередь приложения, а там это сообщение обрабатывается с самым низким приоритетом.
Наконец, в блоке обработки сообщения WM_PAINT вы должны предусмотреть код для вывода той части текстового документа, которая определяется текущим положением вертикального и горизонтального движков.
Пример работы с полосами прокрутки приводится в листинге 2.2.
Примеры программ
Просмотрщик текстовых файлов
Приложение TextViewer предназначено для чтения и вывода на экран текстового файла. В приложении демонстрируются: использование полос прокрутки, вывод текста при помощи функций TextOut и TabbedTextOut, а также использование реги она отсечения.
В этом примере также показано, что использование идей ООП вряд ли ухуд шает качество программного кода, скорее — наоборот. С этой целью разработан класс KDocument для решения тех подзадач, которые связаны с загрузкой, хране нием и выводом документа в клиентскую область окна с учетом его прокрутки. Интерфейс класса содержится в файле KDocument.h, а его реализация — в файле
KDocument.cpp.
Программа представляет собой многофайловый проект, приведенный в лис тинге 2.21.
Листинг 2.2. Проект TextViewer
//////////////////////////////////////////////////////////////////////
// KDocument.h #include <windows.h> #include <vector> #include <string> using namespace std;
class KDocument {
1Как всегда, не забудьте скопировать в каталог проекта файлы KWnd.h и KWnd.cpp (из листинга 1.2 в главе 1) и включить их в состав проекта при помощи окна Project Workspace интегрированной среды.
Примеры программ |
147 |
|
|
|
|
public:
BOOL Open(const char* file);
void Initialize(LPTEXTMETRIC tm);
void ScrollSettings(HWND hwnd, int width, int height); void UpdateHscroll(HWND hwnd, int xInc);
void UpdateVscroll(HWND hwnd, int yInc); void PutText(HWND hwnd, HDC hdc);
int cxChar; |
// средняя ширина символа |
int yStep; |
// высота (шаг) строки |
int lineLenMax; |
// максимальная длина строки |
SCROLLINFO vsi; |
// вертикальный скроллинг |
int vertRange; |
// диапазон вертикальной полосы прокрутки |
SCROLLINFO hsi; |
// горизонтальный скроллинг |
int horzRange; |
// диапазон горизонтальной полосы прокрутки |
private:
vector<string> lines; // вектор для хранения строк документа
};
//////////////////////////////////////////////////////////////////////
// KDocument.cpp #include <windows.h> #include <fstream> #include "KDocument.h"
BOOL KDocument::Open(const char* file) { ifstream finp(file);
char buf[200];
if(!finp.good()) {
MessageBox(NULL, "Не найден входной файл", "Error", MB_OK); return FALSE;
}
//Прочитать файл, сохранив информацию в векторе строк lines while(!finp.eof()) {
finp.getline(buf, 199); buf[199] = 0; lines.push_back(string(buf));
}
//Вычислить максимальную длину строки
lineLenMax = 0;
for (int i = 0; i < lines.size(); ++i) { int lineLen = lines[i].size();
// Корректировка, если строка содержит символы табуляции int iTabPos = 0;
while (1) {
iTabPos = lines[i].find('\t', iTabPos); if (iTabPos != -1) {
lineLen += 8; iTabPos++;
}
else break;
}
if (lineLen > lineLenMax) lineLenMax = lineLen;
}
return TRUE;
}
void KDocument::Initialize(LPTEXTMETRIC tm) { |
продолжение |
|
148 Глава 2. GDI — графический интерфейс устройства
Листинг 2.2 (продолжение)
cxChar = tm->tmAveCharWidth;
yStep = tm->tmHeight + tm->tmExternalLeading; vsi.nMin = vsi.nPos = 0;
hsi.nMin = hsi.nPos = 0;
}
void KDocument::ScrollSettings(HWND hwnd, int width, int height) {
//Вертикальный скроллинг vsi.cbSize = sizeof(vsi);
vsi.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; vsi.nPage = height / yStep - 1;
vsi.nMax = lines.size() - 1; if (vsi.nPage > vsi.nMax)
vsi.nPos = vsi.nMin;
vertRange = vsi.nMax - vsi.nMin + 1; SetScrollInfo(hwnd, SB_VERT, &vsi, TRUE);
//Горизонтальный скроллинг
hsi.cbSize = sizeof(SCROLLINFO);
hsi.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; hsi.nPage = width/cxChar - 2;
hsi.nMax = lineLenMax;
if (hsi.nPage > hsi.nMax) hsi.nPos = hsi.nMin;
horzRange = hsi.nMax - hsi.nMin + 1; SetScrollInfo(hwnd, SB_HORZ, &hsi, TRUE);
}
void KDocument::UpdateVscroll(HWND hwnd, int yInc) { // ограничение на положительное приращение
yInc = min(yInc, vertRange - (int)vsi.nPage - vsi.nPos); // ограничение на отрицательное приращение
yInc = max(yInc, vsi.nMin - vsi.nPos);
if (yInc) {
ScrollWindow(hwnd, 0, -yStep * yInc, NULL, NULL); vsi.nPos += yInc;
SetScrollInfo(hwnd, SB_VERT, &vsi, TRUE); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow (hwnd);
}
}
void KDocument::UpdateHscroll(HWND hwnd, int xInc) { // ограничение на положительное приращение
xInc = min(xInc, horzRange - (int)hsi.nPage - hsi.nPos); // ограничение на отрицательное приращение
xInc = max(xInc, hsi.nMin - hsi.nPos);
if (xInc) {
ScrollWindow(hwnd, -cxChar * xInc, 0, NULL, NULL); hsi.nPos += xInc;
SetScrollInfo(hwnd, SB_HORZ, &hsi, TRUE); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow (hwnd);
}
Примеры программ |
149 |
|
|
|
|
}
void KDocument::PutText(HWND hwnd, HDC hdc) { RECT rect;
GetClientRect(hwnd, &rect); rect.left += cxChar; rect.right -= cxChar;
HRGN hRgn = CreateRectRgnIndirect(&rect); SelectClipRgn(hdc, hRgn);
int x = cxChar * (hsi.nMin - hsi.nPos + 1); int y = yStep;
int amountLines = lines.size(); int iBeg = vsi.nPos;
int iEnd = (vsi.nPos+vsi.nPage < amountLines)? vsi.nPos+vsi.nPage : amountLines;
for (int i = iBeg; i < iEnd; ++i) { int iTabPos = lines[i].find('\t'); if (-1 == iTabPos)
TextOut(hdc, x, y, lines[i].c_str(), lines[i].size()); else
TabbedTextOut(hdc, x, y, lines[i].c_str(), lines[i].size(), 0, 0, x); y += yStep;
}
SelectClipRgn(hdc, NULL);
}
//////////////////////////////////////////////////////////////////////
// TextViewer.cpp #include <windows.h> #include "KWnd.h" #include "KDocument.h"
#define FILE_NAME "D:\\Program files\\Microsoft Visual Studio\\VC98\\MFC\\SRC\\README.TXT"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
KDocument doc; //==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
if (!doc.Open(FILE_NAME)) return 0;
KWnd mainWnd("Text Viewer", hInstance, nCmdShow, WndProc, NULL,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, CS_HREDRAW | CS_VREDRAW,
WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL);
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
}
return msg.wParam;
}
//====================================================================
продолжение
150 |
Глава 2. GDI — графический интерфейс устройства |
|
|
Листинг 2.2 (продолжение) |
|
LRESULT CALLBACK WndProc(HWND |
hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) |
{ |
|
HDC hDC; PAINTSTRUCT ps; TEXTMETRIC tm;
int cxClient=0, cyClient=0; static int xInc, yInc;
switch (uMsg)
{
case WM_CREATE:
hDC = GetDC(hWnd); GetTextMetrics(hDC, &tm); doc.Initialize(&tm); ReleaseDC(hWnd, hDC); break;
case WM_SIZE:
hDC = GetDC(hWnd); cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); if (cxClient > 0)
doc.ScrollSettings(hWnd, cxClient, cyClient); ReleaseDC(hWnd, hDC);
break;
case WM_VSCROLL: switch(LOWORD(wParam)) { case SB_LINEUP:
yInc = -1; break; case SB_LINEDOWN:
yInc = 1; break; case SB_PAGEUP:
yInc = -(int)doc.vsi.nPage; break; case SB_PAGEDOWN:
yInc = (int)doc.vsi.nPage; break; case SB_THUMBTRACK:
yInc = HIWORD(wParam) - doc.vsi.nPos; break; default: yInc = 0;
}
doc.UpdateVscroll(hWnd, yInc); break;
case WM_HSCROLL: switch(LOWORD(wParam)) { case SB_LINELEFT:
xInc = -1; break; case SB_LINERIGHT:
xInc = 1; break; case SB_PAGELEFT:
xInc = -0.8 * (int)doc.hsi.nPage; break; case SB_PAGERIGHT:
xInc = 0.8 * (int)doc.hsi.nPage; break; case SB_THUMBTRACK:
xInc = HIWORD(wParam) - doc.hsi.nPos; break; default: xInc = 0;
}
