WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 12 |

«Ч. Петзолд Программирование для Windows® 95 в двух томах Том I «BHV — Санкт-Петербург» Дюссельдорф Киев Москва Санкт-Петербург Содержание ЧАСТЬ I ВВЕДЕНИЕ ...»

-- [ Страница 5 ] --

Наконец, при обработке сообщения WM_DESTROY программа EMF1 удаляет метафайл, вызывая функцию DeleteEnhMetaFile.

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

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

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

Рис. 4.31 Окна программ EMF1.

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

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

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

Заглянем внутрь Программа EMF2, приведенная на рис. 4.32, строит дисковый метафайл.

EMF2.MAK #-------------------- # EMF2.MAK make file #-------------------- emf2.exe : emf2.obj $(LINKER) $(GUIFLAGS) -OUT:emf2.exe emf2.obj $(GUILIBS) emf2.obj : emf2.c $(CC) $(CFLAGS) emf2.c EMF2.C /*------------------------------------- EMF2.C -- Enhanced Metafile Demo # (c) Charles Petzold, -------------------------------------*/ #include LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "EMF2";

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, "Enhanced Metafile Demo #2", 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) { HDC hdc, hdcEMF;

HENHMETAFILE hemf;

PAINTSTRUCT ps;

RECT rect;

switch(iMsg) { case WM_CREATE:

hdcEMF = CreateEnhMetaFile(NULL, "emf2.emf", NULL, "EMF2\0EMF Demo #2\0");

Rectangle(hdcEMF, 100, 100, 200, 200);

MoveToEx (hdcEMF, 100, 100, NULL);

LineTo (hdcEMF, 200, 200);

MoveToEx (hdcEMF, 200, 100, NULL);

LineTo (hdcEMF, 100, 200);

hemf = CloseEnhMetaFile(hdcEMF);

DeleteEnhMetaFile(hemf);

return 0;

case WM_PAINT:

hdc = BeginPaint(hwnd, &ps);

GetClientRect(hwnd, &rect);

rect.left = rect.right / 4;

rect.right = 3 * rect.right / 4;

rect.top = rect.bottom / 4;

rect.bottom = 3 * rect.bottom / 4;

hemf = GetEnhMetaFile("emf2.emf");

PlayEnhMetaFile(hdc, hemf, &rect);

DeleteEnhMetaFile(hemf);

EndPaint(hwnd, &ps);

return 0;

case WM_DESTROY:

PostQuitMessage(0);

return 0;

} return DefWindowProc(hwnd, iMsg, wParam, lParam);

} Рис. 4.32 Программа EMF Обратите внимание, что первый параметр функции CreateEnhMetaFile — это описатель контекста устройства. GDI использует этот параметр для вставки метрической информации в заголовок метафайла. Если этот параметр установлен в NULL, то GDI берет эту метрическую информацию из контекста устройства дисплея.

Второй параметр функции CreateEnhMetaFile — это имя файла. Если вы установите этот параметр в NULL (как в программе EMF1), то функция построит метафайл в памяти. Программа EMF2 строит дисковый метафайл с именем EMF2.EMF.

Третий параметр функции — адрес структуры типа RECT, описывающей общие размеры метафайла. Эта часть важнейшей информации (та, что отсутствовала в предыдущем формате метафайлов Windows) заносится в заголовок метафайла. Если вы установите этот параметр в NULL, то GDI определит размеры за вас. Приятно, что операционная система делает это для нас, поэтому имеет смысл устанавливать этот параметр в NULL. Если производительность вашего приложения критична, то вы можете использовать этот параметр для того, чтобы избежать лишней работы GDI.

Наконец, последний параметр — текстовая строка, описывающая метафайл. Эта строка делится на две части:

Первая часть — имя приложения (не обязательно имя программы), за которым следует NULL-символ. Вторая часть описывает визуальный образ. Эта часть завершается двумя NULL-символами. Например, используя нотацию языка C ‘\0’ для NULL-символа, можно получить строку описания следующего вида "HemiDemiSemiCad V6.4\0Flying Frogs\0\0". Поскольку C обычно помещает NULL-символ в конец строки, заданной в кавычках, вам нужен только один символ ‘\0’ в конце строки, как показано в программе EMF2.

После создания метафайла программа EMF2 работает также, как программа EMF1, вызывая несколько функций GDI и используя описатель контекста устройства, возвращенный функцией CreateEnhMetaFile. Эти функции рисуют прямоугольник и две линии, соединяющие его противоположные вершины. Затем программа вызывает функцию CloseEnhMetaFile для уничтожения описателя контекста устройства и получения описателя сформированного метафайла.

Затем, еще при обработке сообщения WM_CREATE, программа EMF2 делает то, чего не делала программа EMF1:

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

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

Теперь, для использования созданного метафайла программе EMF2 необходимо получить доступ к дисковому файлу. Она делает это при обработке сообщения WM_PAINT путем вызова функции GetEnhMetaFile.

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

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

Поскольку программа EMF2 оставляет не удаленным дисковый метафайл, вы можете взглянуть на него. Он состоит из записей переменной длины, описываемых структурой ENHMETARECORD, определенной в заголовочных файлах Windows. Расширенный метафайл всегда начинается с заголовка типа структуры ENHMETAHEADER.

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

GetEnhMetaFileHeader(hemf, cbSize, &emh);

Первый параметр — описатель метафайла. Последний — указатель на структуру типа ENHMETAHEADER, а второй — размер этой структуры. Вы можете использовать функцию GetEnhMetaFileDescription для получения строки описания. Поле rclBounds структуры ENHMETAHEADER — структура прямоугольника, хранящая размеры изображения в пикселях. Поле rclFrame структуры ENHMETAHEADER — структура прямоугольника, хранящая размеры изображения в других единицах (0.01 мм).

Заголовочная запись завершается двумя структурами типа SIZE, содержащими два 32-разрядных поля, szlDevice и szlMillimeters. Поле szlDevice хранит размеры устройства вывода в пикселях, а поле szlMillimeters хранит размеры устройства вывода в миллиметрах. Эти данные основываются на контексте устройства, описатель которого передается в функцию CreateEnhMetaFile первым параметром. Если этот параметр равен NULL, то GDI использует экран дисплея. GDI получает метрические данные с помощью функции GetDeviceCaps.

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

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

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

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

Поэтому эту работу отложим до главы 15.

Текст и шрифты Первая задача, которую мы решали в этой книге, используя программирование под Windows, была задача отображения простой строки текста в центре окна. В предыдущей главе мы пошли дальше и рассмотрели отображение на экране текста, состоящего из нескольких строк, и его прокрутку в окне. Теперь наступило время рассмотреть механизм отображения текста на экране более детально. Обсуждение задач, связанных с текстом, будет продолжено также в следующих главах. В главе 11 мы увидим, как использование библиотеки диалоговых окон общего пользования (Common Dialog Box library) значительно упрощает программы, давая пользователю возможность выбирать шрифты. В главе 15 мы исследуем проблемы отображения текста на экране в таком же виде, как на бумаге.

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

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

TextOut(hdc, xStart, yStart, pString, iCount);

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

Смысл параметров xStart и yStart функции TextOut может быть изменен с помощью функции SetTextAlign. Флаги TA_LEFT, TA_RIGHT и TA_CENTER влияют на использование xStart при позиционировании строки по горизонтали. По умолчанию установлен флаг TA_LEFT. Если вы установите флаг TA_RIGHT при вызове функции SetTextAlign, то последующие вызовы функции TextOut устанавливают правую границу последнего символа строки в xStart. При заданном флаге TA_CENTER в xStart устанавливается середина строки.

Аналогично, флаги TA_TOP, TA_BOTTOM и TA_BASELINE влияют на вертикальное позиционирование строки.

По умолчанию установлен флаг TA_TOP, который означает, что строка позиционируется таким образом, что yStart определяет вершину символов в строке. Использование флага TA_BOTTOM означает, что строка позиционируется над yStart. Вы можете использовать флаг TA_BASELINE для размещения строки таким образом, чтобы положение базовой линии определялось значением yStart. Базовая линия — это линия, ниже которой располагаются "хвостики" некоторых строчных букв (например, р, q, у).

Если вы вызываете SetTextAlign с флагом TA_UPDATECP, Windows игнорирует параметры xStart и yStart функции TextOut и вместо них использует текущее положение пера, ранее установленное функциями MoveToEx, LineTo или какой-либо другой функцией, изменяющей текущее положение пера. Флаг TA_UPDATECP также заставляет функцию TextOut изменить значение текущего положения пера на конец строки (при установленном флаге TA_LEFT) или на начало строки (при установленном флаге TA_RIGHT). Это используется для отображения строки текста с помощью последовательных вызовов функции TextOut. Когда горизонтальное позиционирование осуществляется при установленном флаге TA_CENTER, текущее положение пера не меняется после вызова функции TextOut.

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

TabbedTextOut(hdc, xStart, yStart, pString, iCount, iNumTabs, piTabStops, xTabOrigin);

Если строка символов содержит символы табуляции (‘\t’ или 0х09), то функция TabbedTextOut будет при выводе заменять символы табуляции числом пробелов, соответствующих списку целых параметров, которые вы передаете в функцию.

Первые пять параметров функции TabbedTextOut такие же, как у функции TextOut. Шестой параметр — число позиций табуляции, седьмой параметр — массив позиций табуляции, заданных в пикселях. Например, если средняя ширина символа 8 пикселей, и вы хотите установить позиции табуляции через каждые 5 символов, то этот список будет содержать числа 40, 80, 120 и т. д., в порядке возрастания.

Если шестой и седьмой параметры имеют значения 0 или NULL, то позиции табуляции устанавливаются через равные промежутки, равные восьмикратной средней ширине символов. Если шестой параметр равен 1, то седьмой параметр указывает на простое целое, которое каждый раз прибавляется для определения следующей позиции табуляции. (Например, если шестой параметр равен 1, а седьмой параметр является указателем на переменную, содержащую число 30, то позиции табуляции будут установлены так: 30, 60, 90,... пикселей.) Последний параметр задает логическую координату по горизонтали точки отсчета позиций табуляции. Точка отсчета может совпадать с начальной позицией строки или отличаться от нее.

Примером другой расширенной функции вывода текста является функция ExtTextOut (приставка Ext означает расширенная):

ExtTextOut(hdc, xStart, yStart, iOptions, &rect, pString, iCount, pxDistance);

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

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

Одной из функций вывода текста более высокого уровня является функция DrawText, которую мы использовали в программе HELLOWIN в главе 2. Вместо указания координат начальной позиции вы задаете структуру типа RECT, определяющую прямоугольник, в котором вы хотите разместить текст:

DrawText(hdc, pString, iCount, &rect, iFormat);

Так же, как и другие функции вывода текста, функция DrawText требует задания в качестве параметров дальнего указателя на символьную строку и длину строки. Однако, при использовании функции DrawText для вывода строки, оканчивающейся символом NULL, вы можете задать значение параметра iCount равным —1. В этом случае Windows вычислит длину строки.

Если параметр iFormat имеет значение 0, то Windows интерпретирует текст как ряд строк, разделенных символами возврата каретки (‘\r’ или 0х0D) или символами конца строки (‘\n’ или 0х0А). Вывод текста производится, начиная с верхнего левого угла прямоугольника. Возврат каретки или конец строки интерпретируется как символ "новая строка" (newline). В соответствии с этим Windows прерывает вывод текущей строки и начинает новую строку.

Вывод новой строки начинается под предыдущей строкой от левого края прямоугольника с интервалом равным высоте символа в строке (без учета величины пространства, заданного в шрифте в качестве межстрочного интервала (external leading)). Любой текст, в том числе и части букв, которые при отображении на экране попадают правее или ниже границ прямоугольника, отсекаются.

Вы можете изменить действие функции DrawText по умолчанию, задав значение параметра iFormat, как комбинацию одного или нескольких флагов. Флаг DT_LEFT (установлен по умолчанию) задает выравнивание выводимого текста влево, флаг DT_RIGHT — выравнивание вправо, флаг DT_CENTER — выравнивание по центру относительно левой и правой сторон прямоугольника. Поскольку флаг DT_LEFT имеет значение 0, вы можете не задавать его значение в явном виде, если хотите, чтобы весь выводимый текст был выровнен влево.

Если вы не хотите, чтобы символы возврата каретки и символы конца строки интерпретировались как символы начала новой строки, вы можете включить идентификатор DT_SINGLELINE. В этом случае Windows интерпретирует символы возврата каретки и конца строки как отображаемые символы, а не как управляющие символы. Если вы используете идентификатор DT_SINGLELINE, вам необходимо также задать положение строки по вертикали: вверху прямоугольника (флаг DT_TOP, включен по умолчанию), внизу прямоугольника (флаг DT_BOTTOM) или посередине между верхней и нижней границами прямоугольника вывода (флаг DT_VCENTER).

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

Если текст содержит символы табуляции (‘\t’ или 0х09), вам необходимо включить флаг DT_EXPANDTABS. По умолчанию позиции табуляции установлены через каждые восемь символьных позиций. Вы можете задать разные позиции табуляции, используя флаг DT_TABSTOP. В этом случае старший байт параметра iFormat содержит число символьных позиций для каждой новой позиции табуляции. Однако, здесь рекомендуется избегать использования флага DT_TABSTOP, поскольку старший байт параметра iFormat используется также для некоторых других флагов.

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

SetTextColor(hdc, rgbColor);

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

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

SetBkMode(hdc, iMode);

где параметр iMode имеет значение OPAQUE или TRANSPARENT. По умолчанию режим фона установлен равным OPAQUE. Это означает, что Windows закрашивает пространство между строками текста цветом фона. Вы можете изменить цвет фона с помощью функции:

SetBkColor(hdc, rgbColor);

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

Многие программы под Windows задают кисть WHITE_BRUSH, которую Windows использует для закрашивания фона окна. Кисть определяется в структуре класса окна. Кроме того, можно сделать цвет окна вашей программы совпадающим с системными цветами, которые пользователь может установить в программе Control Panel (панель управления). В этом случае вы задаете цвет фона в структуре WNDCLASS таким образом:

wndclass.hbrBackground =(HBRUSH)(COLOR_WINDOW + 1);

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

SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));

SetBkColor(hdc, GetSysColor(COLOR_WINDOW));

Можно сделать так, чтобы ваша программа отслеживала изменение системных цветов:

case WM_SYSCOLORCHANGE :

InvalidateRect(hwnd, NULL, TRUE);

break;

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

SetTextCharacterExtra(hdc, iExtra);

Значение параметра iExtra задается в логических единицах. Windows преобразует его до ближайшего значения в пикселях, которое может быть и 0. Если вы зададите значение параметра iExtra отрицательным (например, чтобы прижать символы ближе друг к другу), Windows использует абсолютное значение заданного числа: вы не можете сделать эту величину меньше 0. Вы можете получить текущее значение межсимвольного интервала, используя функцию GetTextCharacterExtra. Windows предварительно преобразует возвращаемое значение межсимвольного интервала из пикселей в логические единицы.

Использование стандартных шрифтов Когда вы вызываете одну из функций вывода текста TextOut, TabbedTextOut, ExtTextOut или DrawText, Windows использует шрифт, выбранный в момент вызова функции в контексте устройства. Шрифт определяет особенности изображения символов и размер. Простейший путь выводить текст различными шрифтами состоит в использовании стандартных шрифтов, поддерживаемых Windows. Однако, их список достаточно ограничен.

Вы можете получить описатель стандартного шрифта, вызывая функцию:

hFont = GetStockObject(iFont);

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

SelectObject(hdc, hFont);

Вы можете осуществить это и за один шаг:

SelectObject(hdc, GetStockObject(iFont));

Функция GetStockObject — это та же самая функция, которую мы использовали ранее для получения стандартных перьев и кистей;

функцию SelectObject мы использовали при выборе перьев, кистей, битовых образов и регионов в контекст устройства.

Шрифт, выбранный в контекст устройства по умолчанию, называется системным шрифтом и определяется параметром SYSTEM_FONT функции GetStockObject. Это пропорциональный шрифт, состоящий из ANSI символов, который Windows использует для вывода текста в меню, диалоговых окнах, окнах подсказок и в строках заголовков окон. Задание параметра SYSTEM_FIXED_FONT функции GetStockObject (которое было сделано в программе WHATSIZE ранее в этой главе) дает вам описатель шрифта фиксированной ширины (fixed-pitch), совместимого с системным шрифтом, который использовался в более ранних (до 3.0) версиях Windows. Это очень удобно, когда все символы шрифта имеют одинаковую ширину.

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

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

Типы шрифтов Windows поддерживает две больших категории шрифтов — " шрифты GDI " и "шрифты устройства" (device fonts).

Шрифты GDI хранятся в файлах на вашем жестком диске. Шрифты устройства соответствуют конкретному устройству вывода. Например, большинство принтеров имеет набор встроенных шрифтов устройства.

Шрифты GDI могут быть одного из трех типов — растровые шрифты, векторные шрифты и шрифты типа True Type.

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

В более ранних версиях (до 3.1) Windows поддерживала, кроме шрифтов GDI, еще и векторные шрифты.

Векторные шрифты определены как набор соединенных друг с другом отрезков прямых (connect-the-dots).

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

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

Далее рассмотрим шрифты TrueType, которым посвятим остаток главы.

Шрифты TrueType С введением шрифтов TrueType в версии Windows 3.1 значительно повысились возможности и гибкость работы с текстами. TrueType — это технология контурных шрифтов, которая была разработана Apple Computer Inc. и Microsoft Corporation;

она поддерживается многими производителями шрифтов. Отдельные символы шрифтов TrueType определяются контурами, состоящими из прямых линий и кривых. Таким образом, Windows может масштабировать эти шрифты, изменяя определяющие контур координаты. Шрифты TrueType могут быть использованы как для вывода на экран, так и для вывода на принтер, делая реально возможным режим отображения текста WYSIWYG (what-you-see-is-what-you-get, что видите — то и получаете).

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

Эти битовые образы кэшируются в памяти для последующего применения.

Windows 95 оснащена 13 шрифтами TrueType. Они имеют следующие имена в соответствии с их видом:

• 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 Шрифт Courier New — это фиксированный шрифт (т. е. все символы имеют одинаковую ширину). Он разработан похожим на выводимые данные такого устаревшего устройства, как пишущая машинка. Группа шрифтов Times New Roman — это производные от шрифта Times, впервые разработанного специально для Times of London, и используемого во многих печатных материалах. Они рассчитаны на то, чтобы обеспечить максимальное удобство чтения. Группа шрифтов Arial — это производные от шрифта Helvetica, являющегося рубленым (sans serif) шрифтом. Это означает, что символы не имеют засечек на концах. Шрифт Symbol содержит ряд часто используемых специальных символов.

Обычно вы задаете шрифт путем указания его типового имени и типового размера. Типовой размер выражается в единицах, называемых пунктами. Пункт — это приблизительно 1/72 дюйма, разница настолько несущественна, что в компьютерной полиграфии пункт часто определяют как 1/72 дюйма без всяких оговорок. Текст этой книги напечатан шрифтом типового размера 12 пунктов. Типовой размер иногда определяют как высоту символа от верхней границы букв, имеющих выступающую верхнюю часть, до нижней границы букв, имеющих выступающую нижнюю часть. Это достаточно убедительный способ оценки типового размера, но он не является точным для всех шрифтов. Иногда разработчики шрифтов действуют по-другому.

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

В предыдущих изданиях этой книги подробно рассматривалась функция EnumFonts, которая предоставляет любой программе структуры LOGFONT (logical font, логический шрифт) и TEXTMETRIC для каждого шрифта GDI, установленного для использования под Windows, и для всех шрифтов устройств. Диалоговое окно ChooseFont (которое подробнее будет рассмотрено в главе 11) делает ненужным использование этой функции и всего, что с ней связано. Также подробно обсуждалась функция CreateFontIndirect, которая позволяет программе описывать создаваемый шрифт без его немедленного именования. Это объясняет то, каким образом шрифты печатающего устройства аппроксимировались на экране монитора.

В этой главе вам будет предложено нечто совсем другое. Здесь будет показано, как можно использовать различные стандартные шрифты TrueType в ваших программах наиболее эффективно и одновременно с учетом требований традиционной полиграфии. Для этого надо задать название шрифта (одного из 13, перечисленных выше) и его размер (который будет рассмотрен вскоре). Шрифт назван EZFONT (easy font, простой шрифт). На рис. 4.33 приведены два файла, тексты которых вам потребуются.

EZFONT.H /*---------------------- EZFONT.H header file ----------------------*/ HFONT EzCreateFont(HDC hdc, char * szFaceName, int iDeciPtHeight, int iDeciPtWidth, int iAttributes, BOOL fLogRes);

#define EZ_ATTR_BOLD #define EZ_ATTR_ITALIC #define EZ_ATTR_UNDERLINE #define EZ_ATTR_STRIKEOUT EZFONT.C /*--------------------------------------- EZFONT.C -- Easy Font Creation (c) Charles Petzold, ---------------------------------------*/ #include #include #include #include "ezfont.h" 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;

} Рис. 4.33 Файлы EZFONT Программа EZFONT.C содержит только одну функцию — EzCreateFont — которую вы можете использовать, например, таким образом:

hFont = EzCreateFont(hdc, szFaceName, iDeciPtHeight, iDeciPtWidth, iAttributes, fLogRes);

Функция возвращает описатель шрифта. Шрифт может быть выбран в контекст устройства посредством вызова функции SelectObject. Затем вы можете вызывать функции GetTextMetrics или GetOutlineTextMetrics для определения действительного размера шрифта в логических координатах. Перед окончанием вашей программы необходимо удалить все созданные шрифты, вызвав функцию DeleteObject.

Параметр szFaceName — одно из 13 типовых имен шрифта TrueType из приведенного ранее списка. Если в вашей системе есть другие шрифты TrueType, вы можете также использовать их названия, но только 13 шрифтов, перечисленных ранее, обязательно присутствуют во всех системах Windows 95.

Третий параметр определяет желаемый размер шрифта в пунктах, но его особенность состоит в том, что он задается в деципунктах (каждый деципункт равен 1/10 пункта). Следовательно, если вы хотите задать размер в пунктах, равный 121/2, используйте значение 125.

Обычно четвертый параметр должен быть установлен в ноль или быть таким же, как третий параметр. Однако, вы можете создать более широкий или более узкий шрифт TrueType, установив другое значение этого параметра.

Иногда этот размер называют "эм-шириной" (em-width) шрифта, и он описывает ширину шрифта в пунктах. Не путайте эту величину со средней шириной символов шрифта или с чем-нибудь похожим. На раннем этапе развития типографского дела заглавная буква ‘M’ имела одинаковые ширину и высоту. Так возникла концепция "эм квадрат", а впоследствии и такая мера как "эм-ширина". Когда эм-ширина равна эм-высоте (размеру шрифта в пунктах), то ширины символов установлены такими, какими изначально задумывались разработчиком шрифта.

Задание большей или меньшей эм-ширины позволяет вам создавать более широкие или более узкие символы.

Параметр iAttributes может быть установлен в одно из следующих значений, определенных в EZFONT.H:

• EZ_ATTR_BOLD • EZ_ATTR_ITALIC • EZ_ATTR_UNDERLINE • EZ_ATTR_STRIKEOUT Может быть вам и не потребуется использовать значения EZ_ATTR_BOLD или EZ_ATTR_ITALIC, потому что эти атрибуты являются частью полного типового имени шрифта TrueType. Если вы их используете, то Windows синтезирует соответствующие эффекты.

Наконец, вы должны установить в TRUE значение последнего параметра для того, чтобы видимый размер шрифта основывался на логическом разрешении (logical resolution), возвращаемом функцией GetDeviceCaps. В противном случае он базируется на действительном разрешении.

Внутренняя работа Функция EzCreateFont разработана для использования в системах Windows 95 или Windows NT. Для совместимости с NT используются функции SetGraphicsMode и ModifyWorldTransform, которые никак не влияют на работу Windows 95. (Другими словами, ModifyWorldTransform в Windows NT могла бы оказать влияние на видимый размер шрифта. Поэтому эта функция устанавливается в режим по умолчанию — без преобразования — перед тем, как вычисляется размер шрифта.) Функция EzCreateFont сначала устанавливает поля структуры LOGFONT и вызывает функцию CreateFont, которая возвращает описатель шрифта. Для выбранных шрифтов TrueType большинство полей может быть установлено в ноль. Вам необходимо установить значения следующих полей:

• lfHeight — это желаемая высота символов (включая поле, отведенное для специальных знаков над символами, но не включая поле, установленное для межстрочного интервала) в логических единицах.

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

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

• lfWeight — это поле позволяет вам задать полужирный шрифт путем установки значения, равного 700.

• lfItalic — ненулевое значение поля определяет курсив.

• lfUnderline — ненулевое значение поля задает подчеркивание.

• lfStrikeOut — ненулевое значение поля определяет шрифт с зачеркиванием символов.

• lfFaceName (массив типа BYTE) — это имя шрифта (например, Courier New, Arial или Times New Roman).

Одним из действий, выполняемых функцией EzCreateFont, является преобразование размера в пунктах в логические единицы для последующей установки этого значения в структуре LOGFONT. Сначала размер в пунктах должен быть преобразован в единицы устройства (пиксели), а затем в логические единицы. Чтобы выполнить этот первый шаг, мы используем информацию, получаемую от функции GetDeviceCaps.

Вызов функции GetDeviceCaps c параметрами HORZRES и VERTRES дает нам ширину и высоту экрана (или области печати выводимой на принтере страницы) в пикселях. Вызов функции GetDeviceCaps c параметрами HORZSIZE и VERTSIZE дает нам физическую ширину и высоту экрана (или области печати выводимой на принтере страницы) в миллиметрах. Если последний параметр имеет значение FALSE, то функция EzCreateFont использует эти значения для получения разрешающей способности устройств в точках на дюйм.

Здесь возможны два варианта. Вы можете получить разрешение устройства в точках на дюйм непосредственно, используя параметры LOGPIXELSX и LOGPIXELSY в функции GetDeviceCaps. Это называется "логическим разрешением" (logical resolution) устройства. Для печатающих устройств нормальное разрешение и логическое разрешение одинаковы (отбрасывая ошибки округления). Для дисплеев, однако, логическое разрешение лучше, чем нормальное разрешение. Например, для VGA наилучшим значением нормального разрешения является примерно 68 точек на дюйм. В то время, как логическое разрешение — 96 точек на дюйм.

Это различие, можно сказать, драматическое. Предположим, что мы работаем со шрифтом размера 12 пунктов, который имеет высоту 1/6 дюйма. Предположив, что нормальное разрешение равно 68 точкам на дюйм, полная высота символов будет около 11 пикселей. При логическом разрешении 96 точек на дюйм эта величина будет пикселей. То есть разница составляет около 45%.

Чем объясняется такая разница? Если немного задуматься над этим, то на самом деле не существует истинного разрешения VGA. Стандартный VGA показывает 640 пикселей по горизонтали и 480 пикселей по вертикали, но размер экрана реального компьютера может быть различным — от маленького в компьютерах notebook до большого в VGA — проекторах. Windows не имеет способа самостоятельно определить действительный размер экрана. Значения HORZSIZE и VERTSIZE основаны на стандартных размерах настольных (desktop) дисплеев VGA, которые могли быть установлены для каких-нибудь ранних моделей IBM (году в 1987) путем простого измерения линейкой экрана каким-то программистом Microsoft.

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

Люди, постоянно работающие с текстами, часто используют текстовые процессоры и настольные издательские системы. Довольно часто вывод осуществляется на бумаге размером 81/2х11 дюймов (или 8х10 дюймов с учетом отступов). Многие дисплеи VGA шире, чем 8 дюймов. Отображение на экране более крупных символов предпочтительнее, чем изображение в реальных размерах на экране.

Однако, если вы используете логическое разрешение шрифта, то могут возникнуть проблемы при совмещении текста и другой графики. Если вы используете функцию SetMapMode для рисования графики в дюймах или миллиметрах и одновременно логическое разрешение устройства для установки размера шрифта, то вы придете к несоответствию — не при выводе на принтере (т. к. здесь нормальное разрешение совпадает с логическим), а при выводе на экран, где существует разница в 45%. Решение этой проблемы будет продемонстрировано далее в этой главе в программе JUSTIFY1.

Структура LOGFONT, которую вы передаете функции CreateFontIndirect, требует задания высоты шрифта в логических единицах. Однажды получив это значение в пикселях, вы легко преобразуете его в логические единицы посредством вызова функции DPtoLP (device point to logical point, точка устройства в логическую точку).

Но для того чтобы преобразование DPtoLP выполнялось правильно, должен быть установлен тот же режим отображения (mapping mode), с каким вы далее будете работать при отображении текста на экране, используя созданный шрифт. Это значит, что вы должны установить режим отображения до того, как будете вызывать функцию EzCreateFont. В большинстве случаев вы используете только один режим отображения для рисования в конкретной области окна, так что выполнение этого требования не является проблемой.

Форматирование простого текста Разобравшись с файлами EZFONT, наступило время потренироваться в форматировании текста. Процесс форматирования заключается в расположении каждой строки текста в пределах установленных полей одним из четырех способов: с выравниванием влево, с выравниванием вправо, с выравниванием по центру или с выравниванием по всей ширине (когда строка растягивается от левого края до правого края с формированием одинаковых интервалов между словами). Первые три задачи можно решить, используя функцию DrawText с параметром DT_WORDBREAK, но ее использование ограничено. Например, вы не можете определить, какую часть текста функция DrawText сможет разместить в прямоугольнике вывода. Функция DrawText удобна для некоторых простых задач, но для более сложных задач форматирования вы, вероятно, захотите применить функцию TextOut.

Одной из наиболее часто используемых функций работы с текстом является функция GetTextExtentPoint32. (Это функция, имя которой претерпело некоторые изменения со времени более ранних версий Windows.) Эта функция дает значения ширины и высоты строки символов, основываясь на текущем шрифте, выбранном в контексте устройства:

GetTextExtentPoint32(hdc, pString, iCount, &size);

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

char *szText [ ] = "Hello, how are you?";

Вам нужно, чтобы текст начинался в позиции с вертикальной координатой yStart и находился между границами, установленными координатами xLeft и xRight. Ваша задача заключается в том, чтобы вычислить значение xStart горизонтальной координаты начала текста. Эта задача была бы значительно проще, если бы текст отображался с использованием шрифта фиксированной ширины, но это не является общим случаем. Сначала определим длину строки текста:

GetTextExtentPoint32(hdc, szText, strlen(szText), &size);

Если значение size.cx больше, чем (xRight — xLeft), то срока слишком длинна, чтобы поместиться в указанных границах. Предположим, что строка все же помещается.

Для того, чтобы выровнять текст влево, нужно просто установить значение xStart равным xLeft и затем вывести текст:

TextOut(hdc, xStart, yStart, szText, strlen(szText));

Это просто. Теперь вы можете прибавить значение size.cy к yStart и затем выводить следующую строку текста.

Чтобы выровнять текст вправо, воспользуемся для вычисления xStart следующей формулой:

xStart = xRight — size.cx;

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

xStart =(xLeft + xRight — size.cx) / 2;

Теперь перед нами самая трудная задача — выровнять текст по всей ширине между левой и правой границами.

Расстояние между ними вычисляется как (xRight — xLeft). Без растягивания по ширине текст имеет ширину size.cx.

Разница между этими двумя величинами:

xRight — xLeft — size.cx должна быть равномерно распределена между тремя символами пробелов в символьной строке. На первый взгляд это кажется труднейшей задачей, но на самом деле это не так уж и трудно. Чтобы сделать это, вы вызываете функцию:

SetTextJustification(hdc, xRight — xLeft — size.cx, 3) Второй параметр — это величина пробела, который должен быть распределен поровну между тремя символами пробела в символьной строке. Третий параметр — число символов пробела в строке, в нашем примере — 3.

Теперь установим значение xStart равным xLeft и выведем текст с помощью функции TextOut:

TextOut(hdc, xStart, yStart, szText, strlen(szText));

Текст будет выровнен по всей ширине между границами xLeft и xRight.

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

SetTextJustification(hdc, 0, 0);

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

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

Программа JUSTIFY1, приведенная на рис. 4.34, проделывает эту процедуру с первым параграфом книги автора Herman Melville, которая называется "Moby Dick". Программа использует встроенный шрифт Times New Roman размера 15 пунктов, но вы можете изменить его в функции MyCreateFont, описанной в начале программы, и перекомпилировать саму программу. Вы также можете изменить тип выравнивания, используя определение в начале программы. На рис. 4.35 показан вид текста, выведенного программой JUSTIFY1 на экран.

JUSTIFY1.MAK #------------------------ # JUSTIFY1.MAK make file #------------------------ justify1.exe : justify1.obj ezfont.obj $(LINKER) $(GUIFLAGS) -OUT:justify1.exe justify1.obj ezfont.obj $(GUILIBS) justify1.obj : justify1.c $(CC) $(CFLAGS) justify1.c ezfont.obj : ezfont.c $(CC) $(CFLAGS) ezfont.c JUSTIFY1.C /*----------------------------------------- JUSTIFY1.C -- Justified Type Program (c) Charles Petzold, -----------------------------------------*/ #include #include "ezfont.h" #define LEFT #define RIGHT #define CENTER #define JUSTIFIED #define ALIGN JUSTIFIED #define MyCreateFont EzCreateFont(hdc, "Times New Roman", 150, 0, 0, TRUE) LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "Justify1";

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 = szAppName;

wndclass.lpszClassName = szAppName;

wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

RegisterClassEx(&wndclass);

hwnd = CreateWindow(szAppName, "Justified Type", 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;

} void DrawRuler(HDC hdc, RECT *prc) { static int iRuleSize [16] = { 360, 72, 144, 72, 216, 72, 144, 72, 288, 72, 144, 72, 216, 72, 144, 72 };

int i, j;

POINT ptClient;

SaveDC(hdc);

// Set Logical Twips mapping mode SetMapMode(hdc, MM_ANISOTROPIC);

SetWindowExtEx(hdc, 1440, 1440, NULL);

SetViewportExtEx(hdc, GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY), NULL);

// Move the origin to a half inch from upper left SetWindowOrgEx(hdc, -720, -720, NULL);

// Find the right margin(quarter inch from right) ptClient.x = prc->right;

ptClient.y = prc->bottom;

DPtoLP(hdc, &ptClient, 1);

ptClient.x -= 360;

// Draw the rulers MoveToEx(hdc, 0, -360, NULL);

LineTo (hdc, ptClient.x, -360);

MoveToEx(hdc, -360, 0, NULL);

LineTo (hdc, -360, ptClient.y);

for(i = 0, j = 0;

i <= ptClient.x;

i += 1440 / 16, j++) { MoveToEx(hdc, i, -360, NULL);

LineTo (hdc, i, -360 - iRuleSize [j % 16]);

} for(i = 0, j = 0;

i <= ptClient.y;

i += 1440 / 16, j++) { MoveToEx(hdc, -360, i, NULL);

LineTo (hdc, -360 - iRuleSize [j % 16], i);

} RestoreDC(hdc, -1);

} void Justify(HDC hdc, PSTR pText, RECT *prc, int iAlign) { int xStart, yStart, iBreakCount;

PSTR pBegin, pEnd;

SIZE size;

yStart = prc->top;

do // for each text line { iBreakCount = 0;

while(*pText == ' ') // skip over leading blanks pText++;

pBegin = pText;

do // until the line is known { pEnd = pText;

while(*pText != '\0' && *pText++ != ' ');

if(*pText == '\0') break;

// for each space, calculate extents iBreakCount++;

SetTextJustification(hdc, 0, 0);

GetTextExtentPoint32(hdc, pBegin, pText - pBegin - 1, &size);

} while((int) size.cx <(prc->right - prc->left));

iBreakCount--;

while(*(pEnd - 1) == ' ') // eliminate trailing blanks { pEnd--;

iBreakCount--;

} if(*pText == '\0' || iBreakCount <= 0) pEnd = pText;

SetTextJustification(hdc, 0, 0);

GetTextExtentPoint32(hdc, pBegin, pEnd - pBegin, &size);

switch(iAlign) // use alignment for xStart { case LEFT:

xStart = prc->left;

break;

case RIGHT:

xStart = prc->right - size.cx;

break;

case CENTER:

xStart =(prc->right + prc->left - size.cx) / 2;

break;

case JUSTIFIED:

if(*pText != '\0' && iBreakCount > 0) SetTextJustification(hdc, prc->right - prc->left - size.cx, iBreakCount);

xStart = prc->left;

break;

} TextOut(hdc, xStart, yStart, pBegin, pEnd - pBegin);

yStart += size.cy;

pText = pEnd;

} while(*pText && yStart < prc->bottom);

} LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static char szText[] = "Call me Ishmael. Some years ago -- never mind " "how long precisely -- having little or no money " "in my purse, and nothing particular to interest " "me on shore, I thought I would sail about a " "little and see the watery part of the world. It " "is a way I have of driving off the spleen, and " "regulating the circulation. Whenever I find " "myself growing grim about the mouth;

whenever " "it is a damp, drizzly November in my soul;

" "whenever I find myself involuntarily pausing " "before coffin warehouses, and bringing up the " "rear of every funeral I meet;

and especially " "whenever my hypos get such an upper hand of me, " "that it requires a strong moral principle to " "prevent me from deliberately stepping into the " "street, and methodically knocking people's hats " "off -- then, I account it high time to get to sea " "as soon as I can. This is my substitute for " "pistol and ball. With a philosophical flourish " "Cato throws himself upon his sword;

I quietly " "take to the ship. There is nothing surprising " "in this. If they but knew it, almost all men in " "their degree, some time or other, cherish very " "nearly the same feelings towards the ocean with " "me.";

HDC hdc;

PAINTSTRUCT ps;

RECT rcClient;

switch(iMsg) { case WM_PAINT:

hdc = BeginPaint(hwnd, &ps);

GetClientRect(hwnd, &rcClient);

DrawRuler(hdc, &rcClient);

rcClient.left += GetDeviceCaps(hdc, LOGPIXELSX) / 2;

rcClient.top += GetDeviceCaps(hdc, LOGPIXELSY) / 2;

rcClient.right -= GetDeviceCaps(hdc, LOGPIXELSX) / 4;

SelectObject(hdc, MyCreateFont);

Justify(hdc, szText, &rcClient, ALIGN);

DeleteObject(SelectObject(hdc, GetStockObject(SYSTEM_FONT)));

EndPaint(hwnd, &ps);

return 0;

case WM_DESTROY:

PostQuitMessage(0);

return 0;

} return DefWindowProc(hwnd, iMsg, wParam, lParam);

} Рис. 4.34 Файлы JUSTIFY Рис. 4.35 Вывод программы JUSTIFY Программа JUSTIFY1 выводит на экран линейки (в логических дюймах, конечно) вдоль верхнего края и вдоль левого края рабочей области. Функция DrawRuler рисует линейки. Структура прямоугольника определяет область, в которой может быть расположен текст.

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

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

Часть II Средства ввода Глава 5 Клавиатура Как и большинство интерактивных программ, работающих на персональных компьютерах, приложения Windows 95 активно используют пользовательский ввод с клавиатуры. Хотя Windows поддерживает в качестве устройства ввода также и мышь, работа с клавиатурой по-прежнему превалирует. Действительно, поскольку некоторые пользователи персональных компьютеров предпочитают пользоваться не мышью, а клавиатурой, рекомендуется, чтобы создатели программ пытались реализовать все функциональные возможности программы с помощью клавиатуры. (Конечно, в некоторых случаях, таких как программы рисования или издательские системы, это просто не практикуется, и в этом случае необходима мышь.) Клавиатура не может рассматриваться исключительно как устройство для ввода информации, изолированно от других функций программы. Например, программы часто повторяют ввод с клавиатуры путем отображения печатаемых символов в рабочей области окна. Таким образом, обработка ввода с клавиатуры и вывод текста должны рассматриваться совместно. Если вам важно адаптировать ваши программы к иностранным языкам и рынкам, вам также нужно знать о том, как Windows 95 поддерживает расширенный набор символов ASCII (коды от 128 и выше), двухбайтные наборы символов (DBCS), и поддерживаемую Windows NT 16-разрядную кодировку клавиатуры, известную как Unicode.

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

На самом деле все происходит не столь просто: когда пользователь нажимает и отпускает клавиши, драйвер клавиатуры передает информацию о нажатии клавиш в Windows. Windows сохраняет эту информацию (в виде сообщений) в системной очереди сообщений. Затем она передает сообщения клавиатуры, по одному за раз, в очередь сообщений программы, содержащей окно, имеющее "фокус ввода" (input focus) (о котором вскоре будет рассказано). Затем программа отправляет сообщения соответствующей оконной процедуре.

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

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

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

Программе не нужно отслеживать нажатие этих клавиш, поскольку Windows извещает программу об эффекте, вызванном их нажатием. (Однако, при желании программа сама может их отслеживать.) Например, когда пользователь Windows выбирает пункт меню с помощью клавиатуры, Windows посылает программе сообщение, что выбран пункт меню, независимо от того, был ли он выбран с помощью мыши, или с помощью клавиатуры. (О работе с меню рассказывается в главе 10.) В некоторых программах для Windows используются "быстрые клавиши" (keyboard accelerators) для быстрого доступа к часто употребляемым пунктам меню. В качестве быстрых клавиш обычно используются функциональные клавиши или комбинация символьной клавиши и клавиши . Такие быстрые клавиши определяются в описании ресурсов программы. (В главе 10 показано, как Windows преобразует быстрые клавиши в сообщения команд меню. Вам не нужно самим делать это преобразование.) Окна диалога (о которых рассказывается в главе 11) также имеют интерфейс клавиатуры, но обычно программам не нужно отслеживать клавиатурные события, когда активно окно диалога. Интерфейс клавиатуры обслуживается самой Windows, и Windows посылает сообщения вашей программе о действиях, соответствующих нажимаемым клавишам. В окнах диалога могут содержаться "окна редактирования" (edit) для ввода текста. Это обычно небольшие окна, в которых пользователь набирает строки символов. Windows управляет всей логикой окон редактирования и дает вашей программе окончательное содержимое этих окон, после того, как пользователь завершит ввод строки.

Даже внутри вашего главного окна вы можете определить дочерние окна как окна редактирования. Особым примером этого является программа POPPAD, представленная в главе 8. Эта программа практически представляет собой просто большое окно редактирования, причем вся черновая работа в ней возложена на Windows.

Фокус ввода Клавиатура должна разделяться между всеми приложениями, работающими под Windows. Некоторые приложения могут иметь больше одного окна, и клавиатура должна разделяться между этими окнами в рамках одного и того же приложения. Когда на клавиатуре нажата клавиша, только одна оконная процедура может получить сообщение об этом. Окно, которое получает это сообщение клавиатуры, является окном, имеющем "фокус ввода" (input focus).

Концепция фокуса ввода тесно связана с концепцией "активного окна". Окно, имеющее фокус ввода — это либо активное окно, либо дочернее окно активного окна. Определить активное окно обычно достаточно просто. Если у активного окна имеется панель заголовка, то Windows выделяет ее. Если у активного окна вместо панели заголовка имеется рамка диалога (это наиболее часто встречается в окнах диалога), то Windows выделяет ее. Если активное окно минимизировано, то Windows выделяет текст заголовка в панели задач.

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

Если фокус ввода находится в дочернем окне, то активным является родительское окно этого дочернего окна. То, что фокус ввода находится в дочерних окнах, обычно показывается посредством мигающего курсора или каретки (caret).

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

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

Аппаратные и символьные сообщения Сообщения, которые приложение получает от Windows о событиях, относящихся к клавиатуре, различаются на "аппаратные" (keystrokes) и "символьные" (characters). Такое положение соответствует двум представлениям о клавиатуре. Во-первых, вы можете считать клавиатуру набором клавиш. В клавиатуре имеется только одна клавиша. Нажатие на эту клавишу является аппаратным событием. Отпускание этой клавиши является аппаратным событием. Но клавиатура также является устройством ввода, генерирующем отображаемые символы.

Клавиша <А>, в зависимости от состояния клавиш , и , может стать источником нескольких символов. Обычно, этим символом является строчное ‘a’. Если нажата клавиша или установлен режим Caps Lock, то этим символом является прописное <А>. Если нажата клавиша , этим символом является +<А>. На клавиатуре, поддерживающей иностранные языки, аппаратному событию ‘А’ может предшествовать либо специальная клавиша, либо , либо , либо, либо их различные сочетания.

Эти сочетания могут стать источником вывода строчного ‘а’ или прописного ‘А’ с символом ударения.

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

Аппаратные сообщения Когда вы нажимаете клавишу, Windows помещает либо сообщение WM_KEYDOWN, либо сообщение WM_SYSKEYDOWN в очередь сообщений окна, имеющего фокус ввода. Когда вы отпускаете клавишу, Windows помещает либо сообщение WM_KEYUP, либо сообщение WM_SYSKEYUP в очередь сообщений.

Клавиша нажата Клавиша отпущена Несистемные аппаратные сообщения WM_KEYDOWN WM_KEYUP Системные аппаратные сообщения WM_SYSKEYDOWN WM_SYSKEYUP Обычно сообщения о "нажатии" (down) и "отпускании" (up) появляются парами. Однако, если вы оставите клавишу нажатой так, чтобы включился автоповтор, то Windows посылает оконной процедуре серию сообщений WM_KEYDOWN (или WM_SYSKEYDOWN) и одно сообщение WM_KEYUP (или WM_SYSKEYUP), когда в конце концов клавиша будет отпущена. Также как и все синхронные сообщения, аппаратные сообщения клавиатуры также становятся в очередь. Вы можете с помощью функции GetMessageTime получить время нажатия и отпускания клавиши относительно старта системы.

Системные и несистемные аппаратные сообщения клавиатуры Префикс "SYS" в WM_SYSKEYDOWN и WM_SYSKEYUP означает "системное" (system) и относится к аппаратным сообщениям клавиатуры, которые больше важны для Windows, чем для приложений Windows.

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

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

case WM_SYSKEYDOWN;

case WM_SYSKEYUP;

case WM_SYSCHAR;

return 0;

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

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

Переменная lParam Для всех аппаратных сообщений клавиатуры, 32-разрядная переменная lParam, передаваемая в оконную процедуру, состоит из шести полей: счетчика повторений (Repeat Count), cкан-кода OEM (Original Equipment Manufacturer Scan Code), флага расширенной клавиатуры (Extended Key Flag), кода контекста (Context Code), флага предыдущего состояния клавиши (Previous Key State) и флага состояния клавиши (Transition State). (См. рис. 5.1) Extended Key Flag Context Code 16-Bit Repeat Count Previos Key State 8-Bit OEM Scan Code Transition State Рис. 5.1 Шесть полей переменной lParam аппаратных сообщений клавиатуры Счетчик повторений Счетчик повторений равен числу нажатий клавиши, которое отражено в сообщении. В большинстве случаев он устанавливается в 1. Однако, если клавиша остается нажатой, а ваша оконная процедура недостаточно быстра, чтобы обрабатывать эти сообщения в темпе автоповтора (по умолчанию это приблизительно 10 символов в секунду), то Windows объединяет несколько сообщений WM_KEYDOWN или WM_SYSKEYDOWN в одно сообщение и соответственно увеличивает счетчик повторений. Для сообщения WM_KEYUP или WM_SYSKEYUP счетчик повторений всегда равен 1.

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

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

Скан-код OEM Скан-код OEM является кодом клавиатуры, генерируемым аппаратурой компьютера. (Если вы хорошо знакомы с программированием на языке ассемблера, то скан-код — это код, передаваемый программе в регистре AH при вызове прерывания 16H BIOS.) Приложения Windows обычно игнорируют скан-код OEM, поскольку имеют более совершенные способы расшифровки информации от клавиатуры.

Флаг расширенной клавиатуры Флаг расширенной клавиатуры устанавливается в 1, если сообщение клавиатуры появилось в результате работы с дополнительными клавишами расширенной клавиатуры IBM. (Расширенная клавиатура IBM имеет функциональные клавиши сверху и отдельную комбинированную область клавиш управления курсором и цифр.) Этот флаг устанавливается в 1 для клавишей и на правой стороне клавиатуры, клавиш управления курсором (включая и ), которые не являются частью числовой клавиатуры, клавиш наклонной черты () и на числовой клавиатуре и клавиши . Программы для Windows обычно игнорируют флаг расширенной клавиатуры.

Код контекста Код контекста устанавливается в 1, если нажата клавиша. Этот разряд всегда равен 1 для сообщений WM_SYSKEYDOWN и WM_SYSKEYUP и 0 для сообщений WM_KEYDOWN и WM_KEYUP с двумя исключениями:

• Если активное окно минимизировано, оно не имеет фокус ввода. Все нажатия клавиш вырабатывают сообщения WM_SYSKEYDOWN и WM_SYSKEYUP. Если не нажата клавиша, поле кода контекста устанавливается в 0. (Windows использует SYS сообщения клавиатуры так, чтобы активное окно, которое минимизировано, не обрабатывало эти сообщения.) • На некоторых иноязычных клавиатурах некоторые символы генерируются комбинацией клавиш , или с другой клавишей. В этих случаях, у переменной lParam, которая сопровождает сообщения WM_KEYDOWN и WM_KEYUP, в поле кода контекста ставится 1, но эти сообщения не являются системными сообщениями клавиатуры.

Флаг предыдущего состояния клавиши Флаг предыдущего состояния клавиши равен 0, если в предыдущем состоянии клавиша была отпущена, и 1, если в предыдущем состоянии она была нажата. Он всегда устанавливается в 1 для сообщения WM_KEYUP или WM_SYSKEYUP, но для сообщения WM_KEYDOWN или WM_SYSKEYDOWN он может устанавливаться как в 1, так и в 0. В этом случае 1 показывает наличие второго и последующих сообщений от клавиш в результате автоповтора.

Флаг состояния клавиши Флаг состояния клавиши равен 0, если клавиша нажимается, и 1, если клавиша отпускается. Это поле устанавливается в 0 для сообщения WM_KEYDOWN или WM_SYSKEYDOWN и в 1 для сообщения WM_KEYUP или WM_SYSKEYUP.

Виртуальные коды клавиш Хотя некоторая информация в lParam при обработке сообщений WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP и WM_SYSKEYDOWN может оказаться полезной, гораздо более важен параметр wParam. В этом параметре содержится "виртуальный код клавиши" (virtual key code), идентифицирующий нажатую или отпущенную клавишу. Разработчики Windows попытались определить виртуальные клавиши независимым от аппаратуры способом. По этой причине, некоторые виртуальные коды клавиш не могут вырабатываться на персональных компьютерах IBM и совместимых с ними, но их можно встретить на клавиатурах других производителей.

Виртуальные коды клавиш, которые вы используете, наиболее часто имеют имена, определенные в заголовочных файлах Windows. В показанной ниже таблице представлены эти имена, числовые коды клавиш, а также клавиши персональных компьютеров IBM, соответствующие виртуальным клавишам. Хотя все клавиши вызывают аппаратные сообщения клавиатуры, в таблицу не включены клавиши символов (например, клавиши символов / или ?). Эти клавиши имеют виртуальные коды от 128 и выше, и они в международных клавиатурах часто определяются по-другому. Вы можете определить значения этих виртуальных кодов клавиш с помощью программы KEYLOOK, представленной далее в этой главе, но обычно вам не нужно обрабатывать аппаратные сообщения клавиатуры для этих клавиш.

Виртуальные коды клавиш Деся- Шестнадца- WINDOWS.H Требу- Клавиатура IBM тичное теричное Идентификатор ется 1 01 VK_LBUTTON 2 02 VK_RBUTTON 3 03 VK_CANCEL Ctrl-Break 4 04 VK_MBUTTON 8 08 VK_BACK Backspace 9 09 VK_TAB Tab 12 0C VK_CLEAR 5 на дополнительной цифровой клавиатуре при отключенном Num Lock 13 0D VK_RETURN Enter 16 10 VK_SHIFT Shift 17 11 VK_CONTROL Ctrl 18 12 VK_MENU Alt 19 13 VK_PAUSE Pause 20 14 VK_CAPITAL Caps Lock 27 1B VK_ESCAPE Esc 32 20 VK_SPACE Пробел (Spacebar) 33 21 VK_PRIOR Page Up 34 22 VK_NEXT Page Down 35 23 VK_END End 36 24 VK_HOME Home 37 25 VK_LEFT Стрелка влево 38 26 VK_UP Стрелка вверх 39 27 VK_RIGHT Стрелка вправо 40 28 VK_DOWN Стрелка вниз 41 29 VK_SELECT 42 2A VK_PRINT 43 2B VK_EXECUTE 44 2C VK_SNAPSHOT Print Screen 45 2D VK_INSERT Insert Деся- Шестнадца- WINDOWS.H Требу- Клавиатура IBM тичное теричное Идентификатор ется 46 2E VK_DELETE Delete 47 2F VK_HELP 48-57 30-39 От 0 до 9 на основной клавиатуре 65-90 41-5A От A до Z 96 60 VK_NUMPAD0 0 на дополнительной цифровой клавиатуре при включенном Num Lock 97 61 VK_NUMPAD1 1 на дополнительной цифровой клавиатуре при включенном Num Lock 98 62 VK_NUMPAD2 2 на дополнительной цифровой клавиатуре при включенном Num Lock 99 63 VK_NUMPAD3 3 на дополнительной цифровой клавиатуре при включенном Num Lock 100 64 VK_NUMPAD4 4 на дополнительной цифровой клавиатуре при включенном Num Lock 101 65 VK_NUMPAD5 5 на дополнительной цифровой клавиатуре при включенном Num Lock 102 66 VK_NUMPAD6 6 на дополнительной цифровой клавиатуре при включенном Num Lock 103 67 VK_NUMPAD7 7 на дополнительной цифровой клавиатуре при включенном Num Lock 104 68 VK_NUMPAD8 8 на дополнительной цифровой клавиатуре при включенном Num Lock 105 69 VK_NUMPAD9 9 на дополнительной цифровой клавиатуре при включенном Num Lock 106 6A VK_MULTIPLY * на дополнительной цифровой клавиатуре 107 6B VK_ADD + на дополнительной цифровой клавиатуре 108 6C VK_SEPARATOR 109 6D VK_SUBTRACT - на дополнительной цифровой клавиатуре 110 6E VK_DECIMAL. на дополнительной цифровой клавиатуре 111 6F VK_DIVIDE / на дополнительной цифровой клавиатуре 112 70 VK_F1 Функциональная клавиша F 113 71 VK_F2 Функциональная клавиша F 114 72 VK_F3 Функциональная клавиша F 115 73 VK_F4 Функциональная клавиша F 116 74 VK_F5 Функциональная клавиша F 117 75 VK_F6 Функциональная Деся- Шестнадца- WINDOWS.H Требу- Клавиатура IBM тичное теричное Идентификатор ется клавиша F 118 76 VK_F7 Функциональная клавиша F 119 77 VK_F8 Функциональная клавиша F 120 78 VK_F9 Функциональная клавиша F 121 79 VK_F10 Функциональная клавиша F 122 7A VK_F11 Функциональная клавиша F (расширенная клавиатура) 123 7B VK_F12 Функциональная клавиша F (расширенная клавиатура) 124 7C VK_F 125 7D VK_F 126 7E VK_F 127 7F VK_F 144 90 VK_NUMLOCK Num Lock 145 91 VK_SCROLL Scroll Lock Пометка () в столбце "Требуется" показывает, что клавиша предопределена для любой реализации Windows.

Windows также требует, чтобы клавиатура и драйвер клавиатуры позволяли комбинировать клавиши , , а также и вместе, со всеми буквенными клавишами, всеми клавишами управления курсором и всеми функциональными клавишами. Виртуальные коды клавиш VK_LBUTTON, VK_MBUTTON и VK_RBUTTON относятся к левой, центральной и правой кнопкам мыши. Однако, вам никогда не удастся получить сообщения клавиатуры с параметром wParam, в котором установлены эти значения. Мышь, как мы увидим в следующей главе, вырабатывает свои собственные сообщения.

Положения клавиш сдвига и клавиш-переключателей Параметры wParam и lParam сообщений WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP и WM_SYSKEYDOWN ничего не сообщают вашей программе о положении клавиш сдвига и клавиш переключателей. Вы можете получить текущее состояние любой виртуальной клавиши с помощью функции GetKeyState. Эта функция в основном используется для получения информации о состоянии клавиш сдвига (, и ) и клавиш-переключателей (, и ). Например:

GetKeyState(VK_SHIFT);

возвращает отрицательное значение (т. е., установлен старший разряд), если клавиша нажата. В возвращаемом функцией:

GetKeyState(VK_CAPITAL);

значении установлен младший разряд, если переключатель включен. Вы также можете получить положение кнопок мыши с помощью виртуальных кодов клавиш VK_LBUTTON, VK_MBUTTON и VK_RBUTTON. Однако, большая часть программ для Windows, которым надо отслеживать сочетание состояний кнопок мыши и клавиш клавиатуры, делают это другим способом — проверяя состояние клавиш клавиатуры при получении сообщения от мыши. Фактически, информация о положении клавиш сдвига и клавиш-переключателей включается в сообщения от мыши (как вы увидите в следующей главе).

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

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

while(GetKeyState(VK_F1) >= 0);

// ОШИБКА!!!

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

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

однако, большинство программ для Windows игнорируют все, кроме нескольких сообщений о нажатии и отпускании клавиш. Сообщения WM_SYSKEYUP и WM_SYSKEYDOWN адресованы системным функциям Windows, и вам не нужно их отслеживать. Если вы обрабатываете сообщения WM_KEYDOWN, то сообщения WM_KEYUP вам обычно также можно игнорировать.

Программы для Windows обычно используют сообщения WM_KEYDOWN для нажатия и отпускания клавиш, которые не генерируют символьные сообщения. Хотя вы можете подумать, что есть возможность использовать сообщения о нажатии клавиш в сочетании с информацией о состоянии клавиш сдвига для преобразования сообщений о нажатии клавиш в символьные сообщения, не делайте так. У вас будут проблемы из-за отличий международных клавиатур. Например, если вы получаете сообщение WM_KEYDOWN с wParam равным 33H, вы знаете, что пользователь нажал клавишу <3>. Так, хорошо. Если вы используете GetKeyState и обнаруживаете, что клавиша нажата, то можно было бы предположить, что пользователь печатает знак фунта стерлингов <>.

Вовсе необязательно. Британский пользователь печатает знак <&>. Поэтому сообщения WM_KEYDOWN более удобны для клавиш управления курсором, функциональных клавиш и специальных клавиш, таких как и . Однако, иногда клавиши и , а также функциональные клавиши используются в качестве быстрых клавиш меню. Поскольку Windows преобразует быстрые клавиши меню в сообщения команд меню, вы не должны сами обрабатывать эти сообщения. Некоторые программы, написанные не для Windows, широко используют функциональные клавиши в сочетании с клавишами , и. Вы можете сделать что-то похожее в ваших программах для Windows, но это не рекомендуется. Если вы хотите использовать функциональные клавиши, то лучше, чтобы они дублировали команды меню. Одна из задач Windows — обеспечить такой пользовательский интерфейс, для которого не требуется заучивание или использование сложного набора команд.

Мы собираемся отказаться от всего, за исключением последнего пункта: большую часть времени вы будете обрабатывать сообщения WM_KEYDOWN только для клавиш управления курсором. Если вы используете клавиши управления курсором, то можете контролировать состояние клавиш и с помощью функции GetKeyState. Функции Windows часто используют клавишу в сочетании с клавишами управления курсором для расширения выбора, например, в программах текстовых редакторов. Клавиша часто используется для изменения значения клавиш управления курсором. (Например, в сочетании с клавишей стрелки вправо могло бы означать перемещение курсора на одно слово вправо.) Одним из лучших способов выяснить то, как использовать клавиатуру должно быть изучение использования клавиатуры в существующих популярных программах для Windows. Если вам это не подходит, можете действовать как-то иначе. Но запомните, что в этом случае вы можете помешать пользователю быстро изучить вашу программу.

Модернизация SYSMETS: добавление интерфейса клавиатуры Когда в главе 3 мы написали три версии программы SYSMETS, мы ничего не знали о клавиатуре. Мы могли прокрутить текст только с помощью мыши на полосе прокрутки. Теперь мы знаем, как обрабатывать сообщения клавиатуры, давайте добавим интерфейс клавиатуры в программу SYSMETS. Это очевидно будет работа для клавиш управления курсором. Мы используем большинство клавиш управления курсором , , , , <> и <> для вертикальной прокрутки. Клавиши <> и <> можно оставить на менее важную горизонтальную прокрутку.

Логика обработки сообщений WM_KEYDOWN Один из простейших способов создать интерфейс клавиатуры — это использовать логику обработки сообщений WM_KEYDOWN в оконной процедуре, которая будет работать параллельно с логикой обработки сообщений WM_VSCROLL и WM_HSCROLL:

case WM_KEYDOWN iVscrollInc = iHscrollInc = 0;

switch(wParam) { case VK_HOME: // аналогично WM_VSCROLL, SB_TOP iVscrollInc = — iVscrollPos;

break;

case VK_END: // аналогично WM_VSCROLL, SB_BOTTOM iVscrollInc = iVscrollMax — iVscrollPos;

break;

case VK_UP: // аналогично WM_VSCROLL, SB_LINEUP iVscrollInc = — 1;

break;

case VK_DOWN: // аналогично WM_VSCROLL, SB_LINEDOWN iVscrollInc = 1;

break;

case VK_PRIOR: // аналогично WM_VSCROLL, SB_PAGEUP iVscrollInc = min(-1, — cyClient / cyChar);

break;

case VK_NEXT: // аналогично WM_VSCROLL, SB_PAGEDOWN iVscrollInc = max(1, cyClient / cyChar);

break;

case VK_LEFT: // аналогично WM_HSCROLL, SB_PAGEUP iHscrollInc = — 8;

break;

case VK_RIGHT:// аналогично WM_HSCROLL, SB_PAGEDOWN iHscrollInc = 8;

break;

default:

break;

} if (iVscrollInc = max(- iVscrollPos, min(iVscrollInc, iVscrollMax — iVscrollPos))) { iVscrollPos += iVscrollInc;

ScrollWindow(hwnd, 0, — cyChar * iVscrollInc, NULL, NULL);

SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);

UpdateWindow(hwnd);

} if (iHscrollInc = max(- iHscrollPos, min(iHscrollInc, iHscrollMax — iHscrollPos))) { iHscrollPos += iHscrollInc;

ScrollWindow(hwnd, — cxChar * iHscrollInc, 0, NULL, NULL);

SetScrollPos(hwnd, SB_HORZ, iHscrollPos, TRUE);

} return 0;

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

Посылка асинхронных сообщений Не лучше было бы просто преобразовать каждое из этих сообщений WM_KEYDOWN в эквивалентное сообщение WM_VSCROLL и WM_HSCROLL и, таким образом, быть может, обмануть оконную процедуру WndProc, чтобы ей казалось, что она получает сообщения полосы прокрутки WM_VSCROLL или WM_HSCROLL? Windows позволяет это сделать. Функция называется SendMessage, и имеет те же параметры, что и параметры, передаваемые в оконную процедуру:

SendMessage(hwnd, message, wParam, lParam);

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

Далее показано, как можно было бы использовать SendMessage для обработки WM_KEYDOWN в программе SYSMETS:

case WM_KEYDOWN:

switch(wParam) { case VK_HOME:

SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0L);

break;

case VK_END:

SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0L);

break;

case VK_PRIOR:

SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0L);

break;

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

Теперь вы понимаете, почему в программу SYSMETS3 включена обработка SB_TOP и SB_BOTTOM для сообщений WM_VSCROLL. Тогда это не использовалось, а сейчас используется для обработки клавиш Home и End. Последняя программа SYSMETS, показанная на рис. 5.2, включает в себя все эти изменения. Вам для компиляции этой программы понадобится также файл SYSMETS.H из главы 3 (рис. 3.4).

SYSMETS.MAK #----------------------- # SYSMETS.MAK make file #----------------------- sysmets.exe : sysmets.obj $(LINKER) $(GUIFLAGS) -OUT:sysmets.exe sysmets.obj $(GUILIBS) sysmets.obj : sysmets.c sysmets.h $(CC) $(CFLAGS) sysmets.c SYSMETS.C /*----------------------------------------------------- SYSMETS.C -- System Metrics Display Program(Final) (c) Charles Petzold, -----------------------------------------------------*/ #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[] = "SysMets";

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, "System Metrics", 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, 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);

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_KEYDOWN :

switch(wParam) { case VK_HOME :

SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0L);

break;

case VK_END :

SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0L);

break;

case VK_PRIOR :

SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0L);

break;

case VK_NEXT :

SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0L);

break;

case VK_UP :

SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0L);

break;

case VK_DOWN :

SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0L);

break;

case VK_LEFT :

SendMessage(hwnd, WM_HSCROLL, SB_PAGEUP, 0L);

break;

case VK_RIGHT :

SendMessage(hwnd, WM_HSCROLL, SB_PAGEDOWN, 0L);

break;

} return 0;

case WM_PAINT :

hdc = BeginPaint(hwnd, &ps);

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);

} Рис. 5.2 Программа SYSMETS Символьные сообщения Ранее рассматривалась идея преобразования аппаратных сообщений клавиатуры в символьные сообщения путем учета информации о положении клавиш сдвига и предупреждалось, что информации о положении этих клавиш недостаточно: вам необходимо также знать об особенностях реализации вашей национальной клавиатуры. По этой причине вам не следует пытаться самостоятельно преобразовывать аппаратные сообщения клавиатуры в символьные коды.

Для вас это делает Windows. Вы уже встречали такой код раньше:

while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg);

DispatchMessage(&msg);

} Это типичный цикл обработки сообщений, содержащийся в WinMain. Функция GetMessage заполняет поля структуры msg данными следующего сообщения из очереди. Вызов DispatchMessage вызывает соответствующую оконную процедуру.

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

Существует четыре символьных сообщения:

Символы Немые символы Несистемные символы: WM_CHAR WM_DEADCHAR Системные символы: WM_SYSCHAR WM_SYSDEADCHAR Сообщения WM_CHAR и WM_DEADCHAR являются следствием сообщений WM_KEYDOWN. Сообщения WM_SYSCHAR и WM_SYSDEADCHAR являются следствием сообщений WM_SYSKEYDOWN. В большинстве случаев ваши программы для Windows могут игнорировать все сообщения, за исключением WM_CHAR. Параметр lParam, передаваемый в оконную процедуру как часть символьного сообщения, является таким же, как параметр lParam аппаратного сообщения клавиатуры, из которого сгенерировано символьное сообщение. Параметр wParam — это код символа ASCII.

Символьные сообщения доставляются в вашу оконную процедуру в промежутке между аппаратными сообщениями клавиатуры. Например, если переключатель не включен и вы нажимаете и отпускаете клавишу <А>, оконная процедура получит три следующих сообщения:

Сообщение Клавиша или код WM_KEYDOWN Виртуальная клавиша А WM_CHAR ASCII код а WM_KEYUP Виртуальная клавиша А Если вы набираете прописное ‘А’, удерживая клавишу , нажимая клавишу <А>, отпуская клавишу <А>, и затем отпуская клавишу , оконная процедура получит пять сообщений:

Сообщение Клавиша или код WM_KEYDOWN Виртуальная клавиша VK_SHIFT WM_KEYDOWN Виртуальная клавиша А WM_CHAR ASCII код А WM_KEYUP Виртуальная клавиша А WM_KEYUP Виртуальная клавиша VK_SHIFT Сама по себе клавиша не вырабатывает символьного сообщения.

Если вы удерживаете клавишу <А> нажатой так, что автоповтор генерирует аппаратные сообщения клавиатуры, то на каждое сообщение WM_KEYDOWN, вы получите символьное сообщение:

Сообщение Клавиша или код WM_KEYDOWN Виртуальная клавиша А WM_CHAR ASCII код А WM_KEYDOWN Виртуальная клавиша А WM_CHAR ASCII код А WM_KEYDOWN Виртуальная клавиша А WM_CHAR ASCII код А WM_KEYDOWN Виртуальная клавиша А WM_CHAR ASCII код А WM_KEYUP Виртуальная клавиша А Если у некоторых сообщений WM_KEYDOWN счетчик повторений больше 1, то у соответствующих сообщений WM_CHAR счетчик повторений будет иметь то же значение.

Клавиша в сочетании с буквенной клавишей генерирует управляющие коды ASCII от 01H (+) до 1AH (+). Для выработки таких управляющих кодов вы можете также использовать и другие клавиши. В следующей таблице показано значение wParam в сообщении WM_CHAR для клавиш, вырабатывающих управляющие коды.

Клавиша ASCII код Дублирующая комбинация клавиш Backspace 08H Ctrl-H Tab 09H Ctrl-I Ctrl-Enter 0AH Ctrl-J Enter 0DH Ctrl-M Esc 1BH Ctrl-[ В программах для Windows клавиша иногда используется с клавишами букв в качестве быстрых клавиш, в таком случае сообщения от буквенных клавиш не преобразуются в символьные сообщения.

Сообщения WM_CHAR Если вашей Windows-программе необходимо обрабатывать символы клавиатуры (например, в программах обработки текстов или коммуникационных программах), то она будет обрабатывать сообщения WM_CHAR.

Вероятно, вы захотите как-то по особому обрабатывать клавиши , и (и может быть клавишу ), но все остальные символы вы будете обрабатывать похожим образом:

case WM_CHAR:

switch(wParam) { case '\b': // забой(Backspace) [другие строки программы] break;

case '\t': // табуляция(Tab) [другие строки программы] break;

case '\n': // перевод строки(Linefeed) [другие строки программы] break;

case '\r': // возврат каретки(Enter) [другие строки программы] break;

default: // символьный код [другие строки программы] break;

} return 0;

Этот фрагмент программы фактически идентичен обработке символов клавиатуры в обычных программах MS_DOS.

Сообщения немых символов Программы для Windows обычно могут игнорировать сообщения WM_DEADCHAR и WM_SYSDEADCHAR. На некоторых, не американских клавиатурах, некоторые клавиши определяются добавлением диакритического знака к букве. Они называются "немыми клавишами" (dead keys), поскольку эти клавиши сами по себе не определяют символов. Например, при инсталляции немецкой клавиатуры, клавиша, находящаяся там же, где в американской клавиатуре находится клавиша <+/=>, становится немой клавишей для указания высокого звука (), при ненажатой клавише сдвига, и низкого звука (`), при нажатой клавише сдвига.

Если пользователь нажимает немую клавишу, оконная процедура получает сообщение WM_DEADCHAR с параметром wParam равным коду ASCII самого диакритического знака. Когда затем пользователь нажимает клавишу буквы (например, клавишу <А>), оконная процедура получает сообщение WM_CHAR, где параметр wParam равен коду ASCII буквы с диакритическим знаком. Таким образом, ваша программа не должна обрабатывать сообщение WM_DEADCHAR, поскольку сообщение WM_CHAR и так дает программе всю необходимую информацию. Windows имеет даже встроенную систему отслеживания ошибок: если за немой клавишей следует буква, у которой не может быть диакритического знака (например буква s), то оконная процедура получает два сообщения WM_CHAR подряд — первое с wParam равным коду ASCII самого диакритического знака (такое же значение wParam, как было передано с сообщением WM_DEADCHAR) и второе wParam равным коду ASCII буквы s.

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

KEYLOOK.MAK #----------------------- # KEYLOOK.MAK make file #----------------------- keylook.exe : keylook.obj $(LINKER) $(GUIFLAGS) -OUT:keylook.exe keylook.obj $(GUILIBS) keylook.obj : keylook.c $(CC) $(CFLAGS) keylook.c KEYLOOK.C /*------------------------------------------------------- KEYLOOK.C -- Displays Keyboard and Character Messages (c) Charles Petzold, -------------------------------------------------------*/ #include #include LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

RECT rect;

int cxChar, cyChar;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "KeyLook";

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, "Keyboard Message Looker", 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;

} void ShowKey(HWND hwnd, int iType, char *szMessage, WPARAM wParam, LPARAM lParam) { static char *szFormat[2] = { "%-14s %3d %c %6u %4d %3s %3s %4s %4s", "%-14s %3d %c %6u %4d %3s %3s %4s %4s" };

char szBuffer[80];

HDC hdc;

ScrollWindow(hwnd, 0, -cyChar, &rect, &rect);

hdc = GetDC(hwnd);

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

TextOut(hdc, cxChar, rect.bottom - cyChar, szBuffer, wsprintf(szBuffer, szFormat [iType], szMessage, wParam, (BYTE)(iType ? wParam : ' '), LOWORD(lParam), HIWORD(lParam) & 0xFF, (PSTR)(0x01000000 & lParam ? "Yes" : "No"), (PSTR)(0x20000000 & lParam ? "Yes" : "No"), (PSTR)(0x40000000 & lParam ? "Down" : "Up"), (PSTR)(0x80000000 & lParam ? "Up" : "Down")));

ReleaseDC(hwnd, hdc);

ValidateRect(hwnd, NULL);

} LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static char szTop[] = "Message Key Char Repeat Scan Ext ALT Prev Tran";

static char szUnd[] = "_ _ _ _ ";

HDC hdc;

PAINTSTRUCT ps;

TEXTMETRIC tm;

switch(iMsg) { case WM_CREATE :

hdc = GetDC(hwnd);

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

GetTextMetrics(hdc, &tm);

cxChar = tm.tmAveCharWidth;

cyChar = tm.tmHeight;

ReleaseDC(hwnd, hdc);

rect.top = 3 * cyChar / 2;

return 0;

case WM_SIZE :

rect.right = LOWORD(lParam);

rect.bottom = HIWORD(lParam);

UpdateWindow(hwnd);

return 0;

case WM_PAINT :

InvalidateRect(hwnd, NULL, TRUE);

hdc = BeginPaint(hwnd, &ps);

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

SetBkMode(hdc, TRANSPARENT);

TextOut(hdc, cxChar, cyChar / 2, szTop,(sizeof szTop) - 1);

TextOut(hdc, cxChar, cyChar / 2, szUnd,(sizeof szUnd) - 1);

EndPaint(hwnd, &ps);

return 0;

case WM_KEYDOWN :

ShowKey(hwnd, 0, "WM_KEYDOWN", wParam, lParam);

return 0;

case WM_KEYUP :

ShowKey(hwnd, 0, "WM_KEYUP", wParam, lParam);

return 0;

case WM_CHAR :

ShowKey(hwnd, 1, "WM_CHAR", wParam, lParam);

return 0;

case WM_DEADCHAR :

ShowKey(hwnd, 1, "WM_DEADCHAR", wParam, lParam);

return 0;

case WM_SYSKEYDOWN :

ShowKey(hwnd, 0, "WM_SYSKEYDOWN", wParam, lParam);

break;

// ie, call DefWindowProc case WM_SYSKEYUP :

ShowKey(hwnd, 0, "WM_SYSKEYUP", wParam, lParam);

break;

// ie, call DefWindowProc case WM_SYSCHAR :

ShowKey(hwnd, 1, "WM_SYSCHAR", wParam, lParam);

break;

// ie, call DefWindowProc case WM_SYSDEADCHAR :

ShowKey(hwnd, 1, "WM_SYSDEADCHAR", wParam, lParam);

break;

// ie, call DefWindowProc case WM_DESTROY :

PostQuitMessage(0);

return 0;

} return DefWindowProc(hwnd, iMsg, wParam, lParam);

} Рис. 5.3 Программа KEYLOOK Дисплей в программе KEYLOOK используется также, как устаревшее устройство вывода информации — телетайп.

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

Функция TextOut используется для вывода строки новой информации на экран, начиная с высоты одного символа от нижнего края рабочей области. Это почти также просто, как может происходить вывод информации на телетайпе. На рис. 5.4 показано, как выглядит окно программы KEYLOOK, когда вы печатаете слово "Windows". В первом столбце показаны сообщения клавиатуры, во втором — коды виртуальных клавиш для аппаратных сообщений клавиатуры, в третьем — коды символов (и сами символы) для символьных сообщений и, наконец, в шести оставшихся столбцах показано состояние шести полей параметра сообщения lParam.

Рис. 5.4 Вывод на экран программы KEYLOOK В большей части KEYLOOK.С используются те возможности Windows, которые уже были рассмотрены в различных программах SYSMETS, но также здесь используются несколько новых функций. Отметьте однако, что форматирование выводимого текста программы KEYLOOK по столбцам было бы затруднительно при использовании задаваемого по умолчанию пропорционального шрифта. Для того, чтобы получить выравненное изображение, код для вывода каждой строки пришлось бы разбить на девять секций. Чтобы избежать всех этих трудностей, гораздо легче просто использовать фиксированный шрифт. Как уже говорилось в последней главе, для этого нужны две функции, которые здесь объединены в одну инструкцию:

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

Программа KEYLOOK вызывает эти две функции везде, где она получает контекст устройства. Это происходит в трех местах: в функции ShowKey, при обработке сообщения WM_CREATE в WndProc и при обработке сообщения WM_PAINT. Функция GetStockObject получает описатель стандартного графического объекта, каковым является фиксированный шрифт, используемый в ранних версиях Windows, предшествовавших Windows 3.0. Вызов функции SelectObject выбирает этот объект в контекст устройства. Благодаря этому вызову, весь текст будет выводиться на экран фиксированным шрифтом. Вернуться обратно к пропорциональному шрифту можно с помощью функции:

SelectObject(hdc, GetStockObject(SYSTEM_FONT));

Функция ShowKey вызывает ScrollWindow для прокрутки вверх предыдущих строк перед выводом новой строки.

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

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

(То, что программа KEYLOOK не хранит полученные аппаратные сообщения клавиатуры, и следовательно не может перерисовать окно, пока обрабатывается сообщение WM_PAINT, несомненно является недостатком. В программе TYPER, показанной далее в этой главе, эта недостаток устранен.) Вверху рабочей области программа KEYLOOK рисует заголовок таблицы и, таким образом, идентифицирует девять столбцов. Хотя можно создать шрифт, в котором символы будут подчеркнуты, здесь применяется немного другой подход. Определены две переменные типа строка символов, которые называются szTop (в ней содержится текст) и szUnd (в ней содержатся символы подчеркивания) и при обработке сообщения WM_PAINT они выводятся в одну и ту же позицию в верхней части окна. Обычно Windows выводит текст в режиме "opaque", означающим, что Windows обновляет область фона символа при его выводе на экран. Использование этого режима фона может привести к тому, что вторая символьная строка (szUnd) сотрет первую (szTop). Чтобы предотвратить это, переключите контекст устройства в режим "transparent" (режим без заполнения фона символов):

SetBkMode(hdc, TRANSPARENT);

Каретка (не курсор) Когда вы в программе набираете текст, обычно маленький символ подчеркивания или маленький четырехугольник показывает вам место, где следующий набираемый вами символ появится на экране. Вы можете считать, что это курсор (cursor), но, если вы программируете для Windows, необходимо отказаться от такого представления. В Windows это называется "каретка" (caret). Слово же "курсор" относится к битовому образу, отражающему положение мыши на экране.

Функции работы с кареткой Здесь перечислены пять основных функций работы с кареткой:

• CreateCaret — создает связанную с окном каретку.

• SetCaretPos — устанавливает положение каретки в окне.

• ShowCaret — показывает каретку.

• HideCaret — прячет каретку.

• DestroyCaret — удаляет каретку.

Кроме этих, еще имеется функция получения положения каретки (GetCaretPos) и функции установки и получения частоты мигания каретки (GetCaretBlinkTime) и (SetCaretBlinkTime).

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

Вы не можете просто создать каретку при обработке сообщения WM_CREATE и удалить ее при обработке сообщения WM_DESTROY. Каретка — это то, что называется "общесистемным ресурсом" (systemwide resource). Это означает, что в системе имеется только одна каретка. И, как результат, программа при необходимости вывода каретки на экран своего окна "заимствует" ее у системы.

Серьезно ли это необычное ограничение? Конечно нет. Подумайте: появление каретки в окне имеет смысл только в том случае, если окно имеет фокус ввода. Каретка показывает пользователю, что он может вводить в программу текст. В каждый конкретный момент времени только одно окно имеет фокус ввода, поэтому для всей системы и нужна только одна каретка.

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

Основное правило использования каретки выглядит просто: оконная процедура вызывает функцию CreateCaret при обработке сообщения WM_SETFOCUS и функцию DestroyCaret при обработке сообщения WM_KILLFOCUS.

Имеется несколько других правил: каретка создается скрытой. После вызова функции CreateCaret, оконная процедура должна вызвать функцию ShowCaret, чтобы сделать каретку видимой. В дополнение к этому, оконная процедура, когда она рисует в своем окне при обработке сообщения, отличного от WM_PAINT, должна скрыть каретку, вызвав функцию HideCaret. После того как оконная процедура закончит рисовать в своем окне, она вызывает функцию ShowCaret, чтобы снова вывести каретку на экран. Функция HideCaret имеет дополнительный эффект: если вы несколько раз вызываете HideCaret, не вызывая при этом ShowCaret, то чтобы каретка снова стала видимой, вам придется такое же количество раз вызвать функцию ShowCaret.

Программа TYPER Программа TYPER, представленная на рис. 5.5, объединяет вместе многое из того, о чем мы узнали в этой главе.

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

Для простоты в программе TYPER используется фиксированный шрифт. Создание текстового редактора для пропорционального шрифта является, как вы могли бы сообразить, гораздо более трудным делом. Программа получает контекст устройства в нескольких местах: при обработке сообщений WM_CREATE, WM_KEYDOWN, WM_CHAR и WM_PAINT. Каждый раз при этом для выбора фиксированного шрифта вызываются функции GetStockObject и SelectObject.

При обработке сообщения WM_SIZE программа TYPER рассчитывает ширину и высоту окна в символах и хранит эти значения в переменных cxBuffer и cyBuffer. Затем она использует функцию malloc, чтобы выделить буфер для хранения всех символов, которые могут быть напечатаны в окне. В переменных xCaret и yCaret сохраняется положение каретки в символах.

При обработке сообщения WM_SETFOCUS программа TYPER вызывает функцию CreateCaret для создания каретки, имеющей ширину и высоту символа, функцию SetCaretPos для установки положения каретки и функцию ShowCaret, чтобы сделать каретку видимой. При обработке сообщения WM_KILLFOCUS программа вызывает функции HideCaret и DestroyCaret.

TYPER.MAK #--------------------- # TYPER.MAK make file #--------------------- typer.exe : typer.obj $(LINKER) $(GUIFLAGS) -OUT:typer.exe typer.obj $(GUILIBS) typer.obj : typer.c $(CC) $(CFLAGS) typer.c TYPER.C /*-------------------------------------- TYPER.C -- Typing Program (c) Charles Petzold, --------------------------------------*/ #include #include #define BUFFER(x,y) *(pBuffer + y * cxBuffer + x) LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static char szAppName[] = "Typer";

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, "Typing Program", 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 char *pBuffer = NULL;

static int cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer, xCaret, yCaret;

HDC hdc;

int x, y, i;

PAINTSTRUCT ps;

TEXTMETRIC tm;

switch(iMsg) { case WM_CREATE :

hdc = GetDC(hwnd);

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

GetTextMetrics(hdc, &tm);

cxChar = tm.tmAveCharWidth;

cyChar = tm.tmHeight;

ReleaseDC(hwnd, hdc);

return 0;

case WM_SIZE :

// obtain window size in pixels cxClient = LOWORD(lParam);

cyClient = HIWORD(lParam);

// calculate window size in characters cxBuffer = max(1, cxClient / cxChar);

cyBuffer = max(1, cyClient / cyChar);

// allocate memory for buffer and clear it if(pBuffer != NULL) free(pBuffer);

if((pBuffer =(char *) malloc(cxBuffer * cyBuffer)) == NULL) MessageBox(hwnd, "Window too large. Cannot " "allocate enough memory.", "Typer", MB_ICONEXCLAMATION | MB_OK);

else for(y = 0;

y < cyBuffer;

y++) for(x = 0;

x < cxBuffer;

x++) BUFFER(x,y) = ' ';

// set caret to upper left corner xCaret = 0;

yCaret = 0;

if(hwnd == GetFocus()) SetCaretPos(xCaret * cxChar, yCaret * cyChar);

return 0;

case WM_SETFOCUS :

// create and show the caret CreateCaret(hwnd, NULL, cxChar, cyChar);

SetCaretPos(xCaret * cxChar, yCaret * cyChar);

ShowCaret(hwnd);

return 0;

case WM_KILLFOCUS :

// hide and destroy the caret HideCaret(hwnd);

DestroyCaret();

return 0;

case WM_KEYDOWN :

switch(wParam) { case VK_HOME :

xCaret = 0;

break;

case VK_END :

xCaret = cxBuffer - 1;

break;

case VK_PRIOR :

yCaret = 0;

break;

case VK_NEXT :

yCaret = cyBuffer - 1;

break;

case VK_LEFT :

xCaret = max(xCaret - 1, 0);

break;

case VK_RIGHT :

xCaret = min(xCaret + 1, cxBuffer - 1);

break;

case VK_UP :

yCaret = max(yCaret - 1, 0);

break;

case VK_DOWN :

yCaret = min(yCaret + 1, cyBuffer - 1);

break;

case VK_DELETE :

for(x = xCaret;

x < cxBuffer - 1;

x++) BUFFER(x, yCaret) = BUFFER(x + 1, yCaret);

BUFFER(cxBuffer - 1, yCaret) = ' ';

HideCaret(hwnd);

hdc = GetDC(hwnd);

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

TextOut(hdc, xCaret * cxChar, yCaret * cyChar, & BUFFER(xCaret, yCaret), cxBuffer - xCaret);

ShowCaret(hwnd);

ReleaseDC(hwnd, hdc);

break;

} SetCaretPos(xCaret * cxChar, yCaret * cyChar);

return 0;

case WM_CHAR :

for(i = 0;

i <(int) LOWORD(lParam);

i++) { switch(wParam) { case '\b' : // backspace if(xCaret > 0) { xCaret--;

SendMessage(hwnd, WM_KEYDOWN, VK_DELETE, 1L);

} break;

case '\t' : // tab do { SendMessage(hwnd, WM_CHAR, ' ', 1L);

} while(xCaret % 8 != 0);

break;

case '\n' : // line feed if(++yCaret == cyBuffer) yCaret = 0;

break;

case '\r' : // carriage return xCaret = 0;

if(++yCaret == cyBuffer) yCaret = 0;

break;

case '\x1B' : // escape for(y = 0;

y < cyBuffer;

y++) for(x = 0;

x < cxBuffer;

x++) BUFFER(x, y) = ' ';

xCaret = 0;

yCaret = 0;

InvalidateRect(hwnd, NULL, FALSE);

break;

default : // character codes BUFFER(xCaret, yCaret) =(char) wParam;

HideCaret(hwnd);

hdc = GetDC(hwnd);

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

TextOut(hdc, xCaret * cxChar, yCaret * cyChar, & BUFFER(xCaret, yCaret), 1);

ShowCaret(hwnd);

ReleaseDC(hwnd, hdc);

if(++xCaret == cxBuffer) { xCaret = 0;

if(++yCaret == cyBuffer) yCaret = 0;

} break;

} } SetCaretPos(xCaret * cxChar, yCaret * cyChar);

return 0;

case WM_PAINT :

hdc = BeginPaint(hwnd, &ps);

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

for(y = 0;

y < cyBuffer;

y++) TextOut(hdc, 0, y * cyChar, & BUFFER(0,y), cxBuffer);

EndPaint(hwnd, &ps);

return 0;

case WM_DESTROY :

PostQuitMessage(0);

return 0;

} return DefWindowProc(hwnd, iMsg, wParam, lParam);

Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 12 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.