Вывод текста с использованием Windows API функций
Автор: Олег Грабец
Вывод текста является одной из основных задач, которую приходится решать
в программе при организации вывода данных. Речь пойдет о выводе текста с
использованием Windows API функций. Данная статья, безусловно не претендует на
полноту обзора этой тематики, но о некоторых "подводных камнях" я все же
расскажу.
Функции, которые позволяют это выполнять, весьма разнообразны, и
использовать их, как Вы уже поняли, можно в разных ситуациях и случаях, и
собственно говоря, именно Вам и решать, какие использовать. Кроме функций,
которые непосредственно выводят текст, также существует внушительный список
"подсобных" функций. Давайте глянем на tCanvas. В этом классе реализовано
только две функции TextOut и TextRect. Когда задача стоит просто в выводе текста
на цветном фоне (например, combo box цветов), то функций этого класса
предостаточно, и в большинстве случаев я ими и пользуюсь. Но давайте заглянем за
ширму. Функция TextOut использует одноименную функцию Windows, а TextRect
использует функцию ExtTextOut, т.е. если на прямую вызывать функции Windows,
можно получить тот же результат. Кроме того, в этих функциях есть передаваемые
параметры, которые класс прячет, использование которых в определенных ситуациях
не может быть не нужным и полезным. Формат функций я буду приводить из модуля
windows.pas поставляемый с Delphi 3.
function TextOut(DC: HDC; X, Y: Integer; Str: PChar; Count:
Integer): BOOL;
Справочную информацию о передаваемых параметрах Вы сможете посмотреть в
файле справки, но давайте я первый раз все же расскажу. Все GDI функции вывода
имеют параметр DC - контекст устройства, на который нужно нарисовать.
Естественно, Canvas подставляет туда Canvas.Handle. Если Вы хотите нарисовать на
чем-то другом, то должны самостоятельно получить контекст устройства функциями,
типа GetDC. Не забывайте (!) освобождать ресурсы функцией ReleaseDC.
X, Y - привязочные координаты, относительно которых происходит
прорисовка. По умолчанию, привязка выполнена к левому верхнему углу. Я конечно
немного не правильно выразился - в Windows принято понятие "выравнивание", а не
"привязка". Оно настраивается через функцию SetTextAlign, но о ней чуть позже.
Str - переменная, которая содержит выводимый текст, а Count - длина этого
текста. В качестве переменной Str можно использовать переменную, типа array
[0..n] of Chat; можно передавать просто текст: 'Пример текста'. Если текст
находится у Вас в переменной типа string то нужно произвести приведение типов:
PChar(VarString).
function ExtTextOut(DC: HDC; X, Y: Integer; Options: Longint;
Rect: PRect; Str: PChar; Count: Longint; Dx: PInteger): BOOL;
Как видно уже с самого названия и предаваемых параметров, функция имеет
гораздо больше возможностей, нежели предыдущая. Прежде всего, текст выводится в
прямоугольнике, т.е. как бы сперва выводится прямоугольник, а затем в нем текст,
но гораздо быстрее, чем это производилось бы двумя функциями.
Если значение Options будет содержать ETO_CLIPPED, то текст будет виден
только в указанном прямоугольнике. Заметьте, что в функцию передается PRect. Это
означает, что параметр может быть пустым, т.е. nil. В этом случае, функция будет
работать аналогично TextOut. Если вы используете переменную r:tRect, то в
функцию должны передать @r.
Выравнивание также, устанавливается предварительно при помощи
SetTextAlign.
Наиболее интересен параметр DX. Это ссылка на массив, типа array [0..n]
of integer, где каждый элемент является шириной символа. Настоящая ширина может
отличатся от указанного значения. Это можно использовать, например, для имитации
моноширного шрифта, или для сжатия текста таким образом, чтоб он помещался в
отводимую область.
function DrawText(hDC: HDC; lpString: PChar; nCount: Integer;
var lpRect: TRect; uFormat: UINT): Integer;
В отличии от предыдущих функций, функция DrawText не имеет параметров X,
Y и использует для руководства координат параметр lpRect. Заметим, что этот
параметр является переменной, т.к. функция может модифицировать значения, если
ее вызвать с параметром uFormat равным DT_CALCRECT, при этом прорисовка не будет
осуществлятся. Параметр uFormat может включать в себя все значения выравнивания
по этому предварительной глобальной настройки при помощи SetTextAlign не
требуется.
По умолчанию текст, который не вместится в lpRect будет обрезаться. Чтобы
этого не происходило, в параметре uFormat используйте DT_NOCLIP.
Используйте DT_WORDBREAK, если необходимо отобразить текст в несколько
строк. Без этого параметра, даже если в строке есть символ #13 (перевод строки),
все равно текст будет выводится в одну строку. При использовании значения
DT_CALCRECT и DT_WORDBREAK изменятся будет lpRect.bottom, без DT_WORDBREAK -
lpRect.right.
По-прежнему есть проблема с вертикальным выравниванием по центру
(DT_VCENTER). Оно работает только для однострочного текста, т.е. для
многострочного текста сперва придется вычислить lpRect, а затем изменив
lpRect.top и lpRect.bottom, добиться желаемого результата.
Используйте DT_WORD_ELLIPSIS и (или) DT_PATH_ELLIPSIS если необходимо
вывести длинный текст, который не помещается в одну строку. Выглядит это
примерно так: надо вывести "C:\Program Files\Borland\Delphi3\Source\VCL", а
выведется "C:\Program Files\...\VCL". В этом случае, значение DT_MODIFYSTRING
обязательно.
DT_NOPREFIX позволяет выводить "&", а не подчерк под следующим по
тексту символом. Без этого значение в строке придется записывать "&&".
function DrawTextEx(DC: HDC; lpchText: PChar; cchText: Integer;
var p4: TRect; dwDTFormat: UINT; DTParams: PDrawTextParams): Integer;
Оговорюсь, что практически все функции, в названиях которых есть
окончание "Ex", являются расширением одноименных функций без "Ex". В файле
справки упоминается, что функции без "Ex" являются "пережитками" Windows 3.1, по
этому рекомендуют использовать расширенные функции. По моему личному опыту могу
судить, что разницы никакой, если Вам не нужны дополнительные параметры, и
хлопоты по их заполнению :).
В данном случае добавился дополнительный параметр DTParams. При помощи
него можно задать левый, правый отступ текста, величину табуляции, а также
получить число символов или строк, которые не поместились в отведенный
прямоугольник.
function TabbedTextOut(hDC: HDC; X, Y: Integer; lpString: PChar; nCount,
nTabPositions: Integer;
var lpnTabStopPositions; nTabOrigin: Integer): Longint;
Имя функции говорит само за себя - выводится текст с использованием
фиксированной табуляции. В качестве lpnTabStopPositions нужно передать
переменную типа array [0.. nTabPositions-1] of integer. Параметр nTabOrigin
содержит х-координату начала для табуляции. Если в тексте встречается символ
табуляции #9, то дальнейший вывод начинается с указанной в lpnTabStopPositions
позиции.
Горизонтальное выравнивание не влияет на результат вывода.
function PolyTextOut(DC: HDC; const PolyTextArray; Strings: Integer): BOOL;
Вывод многострочного текста. В качестве параметра PolyTextArray нужно
передавать переменную типа array [0.. Strings-1] of TPolyText. Структура
TPolyText содержит все те же параметры, которые передаются функции ExtTextOut.
Под Windows 95/98 не поддерживается.
Примечание: Кстати, советую всегда, перед
начало освоения нового "камня преткновения" обращать на этот пункт. Лично у
меня был случай, когда пишешь, пишешь, приносишь клиенту - не работает.
Почему?! Читаешь MSDN: "Windows 95/98: Unsupported".
Должен огорчить всех любителей "красоты" - в Windows нет стандартной
поддержки выравнивания текста по ширине. Теперь, думаю понятно, почему даже в
tRichEdit нет этого.
Теперь немного о "подсобных" функциях, вызов которых влияет на результат
вывода текста. Об одной из них упоминал выше - SetTextAlign.
function SetTextAlign(DC: HDC; Flags: UINT): UINT;
Не пугайтесь по поводу типа UINT - это обычный тип Integer. Параметр
Flags должен указывать выравнивание текста как по вертикали, так и по
горизонтали (TA_NOUPDATECP, TA_UPDATECP, TA_LEFT, TA_RIGHT, TA_CENTER, TA_TOP,
TA_BOTTOM, TA_BASELINE). Хочу отметить два момента. Во-первых, если
горизонтальное выравнивание может быть TA_CENTER, то по вертикали такого нет, и
при необходимости такой реализации Вам придется соответствующим образом задавать
параметр Y. Во-вторых, использование TA_UPDATECP и TA_NOUPDATECP приводит к
тому, что текущая координата после вывода текста будет или не будет изменятся.
function SetTextColor(DC: HDC; Color: COLORREF): COLORREF;
Задает цвет тексту. Здесь COLORREF обычный DWORD. Я зачастую в качестве
этого параметра передаю функцию RGB(r, g, b: byte) или CMYK(c, m, y, k: Byte) -
это разные цветовые модели.
function SetTextCharacterExtra(DC: HDC; CharExtra: Integer): Integer;
Задает смещение каждого символа в строке относительно той точки, в
которой он, должен рисоваться, т.е. если CharExtra = 1 то второй символ
смещается в право на 1 пиксель, второй на 2 и т.д. Может быть отрицательным,
тогда символы "налезут" друг на друга. При этом искажения начертания нет.
function SetTextJustification(DC: HDC; BreakExtra, BreakCount: Integer):
Integer;
Удлиняет или укорачивает выводимый текст на величину BreakExtra за счет
изменения длины пробелов. Количество пробелов в строке указывается в BreakCount.
Ну и на последок пожалуй одна из самых основных функций, предназначенная
для задания большинства параметров, это:
function CreateFontIndirect(const p1: TLogFont): HFONT;
Результат создания передается в SelectObject, а после использования
желательно удалить вызвав DeleteObject. Структура TLogFont описана так:
TLogFont = record
lfHeight: Longint;
lfWidth: Longint;
lfEscapement: Longint;
lfOrientation: Longint;
lfWeight: Longint;
lfItalic: Byte;
lfUnderline: Byte;
lfStrikeOut: Byte;
lfCharSet: Byte;
lfOutPrecision: Byte;
lfClipPrecision: Byte;
lfQuality: Byte;
lfPitchAndFamily: Byte;
lfFaceName: array[0..LF_FACESIZE - 1] of AnsiChar;
end;
Впечатляет, правда? И так, по каждой составляющей:
- lfHeight
- - высота шрифта, то бишь, размер. Всегда задается в точках и ни каких там
поинтов. Для перевода из поинтов используйте рекомендуемую во всех хелпах
формулу:
- lfHeight
- - MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
- lfWidth
- - коэффициент сжатия по горизонтали. Значения 0 и 100 ни к чему не
приводят. Меньше 100 ужимают всю строку искажая начертание, больше 100 -
растягивает. При выводе на экран наблюдается дискретность равная 10, т.е. к
примеру, 53 и 50 дают одинаковый результат.
- lfEscapement
- - угол поворота всей строки в десятых градуса против часовой стрелки.
- lfOrientation
- - угол поворота отдельных символов в строке в десятых градуса против
часовой стрелки.
- lfWeight
- - плотность шрифта. Задается в диапазоне 0..1000. Я проверял - работает
только 400 как обычный и 700 как жирный.
- lfItalic, lfUnderline, lfStrikeOut
- - ненулевое значение задает соответственно курсив, подчеркивание,
перечеркивание.
- lfCharSet
- - набор символов. Чтобы не читать иероглифы, задайте как RUSSIAN_CHARSET.
- lfOutPrecision
- - точность вывода. Рекомендуют задавать OUT_TT_ONLY_PRECIS
- lfClipPrecision
- - точность отсечения символов. Рекомендуют задавать CLIP_DEFAULT_PRECIS.
- lfQuality
- - качество вывода шрифта. Ставьте PROOF_QUALITY - не помешает. Ну а про
использование ANTIALIASED_QUALITY Вы в курсе - работает только начиная с
Win98.
- lfPitchAndFamily
- - в двух младших разрядах указывается тип шрифта, а в четырех старших -
семейство. Этот параметр можно не задавать, если указать имя существующего
шрифта.
- lfFaceName
- - имя шрифта.
Остальные функции, предназначены для получения
различной информации о шрифтах, размерах и т.п. (GetTextExtentPoint,
EnumFontFamiliesEx, GetFontData, GetCharABCWidths, GetCharWidthFloat и т.д.), но
это уже другая тема.
|