Графический интерфейс GDI в Microsoft Windows

         

Как указать цвет


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

HBRUSH WINAPI CreateSolidBrush(COLORREF clrref);

Тип COLORREF определен в файле windows.h следующим образом:

typedef DWORD COLORREF;

В простейшем случае цвет можно определить с помощью макрокоманды RGB , комбинирующей цвет из отдельных компонент:

#define RGB(r,g,b) \ ((COLORREF)(((BYTE)(r) | \ ((WORD)(g)<<8)) | \ (((DWORD)(BYTE)(b))<<16)))

Эта макрокоманда упаковывает отдельные цветовые компоненты в двойное слово, причем (что важно) старший байт этого слова должен быть равен нулю (рис. 3.2).

Рис. 3.2. Представление цвета, полученное с помощью макрокоманды RGB

В файле windows.h определены также макрокоманды, извлекающие из переменной типа COLORREF, упакованной с помощью макрокоманды RGB, отдельные цветовые компоненты:

#define GetRValue (rgb) ((BYTE)(rgb)) #define GetGValue (rgb) ((BYTE)(((WORD)(rgb)) >> 8)) #define GetBValue (rgb) ((BYTE)((rgb)>>16))

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

COLORREF WINAPI GetNearestColor(HDC hdc, COLORREF clrref);

Через параметр hdc необходимо передать идентификатор контекста отображения.



Кисть


Для закрашивания внутренней области окна приложения или замкнутой геометрической фигуры можно использовать не только различные цвета, но и графические изображения небольшого (8х8 пикселов) размера - кисти (brush ).

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

Для выбора кисти предназначены функции CreateSolidBrush , CreateHatchBrush , CreatePatternBrush , CreateDIBPatternBrush , CreateBrushIndirect , SelectObject .





Кисти


Внутренняя область окна и замкнутых геометрических фигур может быть закрашена при помощи кисти. С помощью функции GetStockObject можно выбрать черную, белую, темно- или светло-серую кисть или нулевую кисть (null brush). Можно выбрать одну из кистей, предназначенных для штриховки (рис. 1.4), для чего следует использовать функцию CreateHatchBrush. Все эти кисти встроены в операционную систему.

Рис. 1.4. Геометрические фигуры, закрашенные с использованием встроенных кистей

Если вас не устраивает ни одна из встроенных кистей, вы можете создать собственную, определив ее как изображение bitmap размером 8х8 пикселов. Такая кисть может иметь любой внешний вид и любой цвет.



Классификация шрифтов


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

Можно сказать, что шрифт состоит из изображений (рисунков) отдельных символов - глифов (glyph).

Для внутреннего представления глифа в файле шрифта True Type используются описания контуров, причем один глиф может содержать несколько контуров (рис. 5.1).

Рис. 5.1. Рисунки символов

Глифы могут иметь различный внешний вид (typeface). Операционная система Windows классифицирует шрифты на несколько типов, или семейств (font family). Эти типы называются Modern, Roman, Swiss, Script, Decorative.

Шрифты семейства Modern имеют одинаковую ширину букв. Таким шрифтом оформлены все листинги программ в нашей книге. Шрифты семейства Roman содержат буквы различной ширины, имеющие засечки. Семейство Swiss отличается тем, что при переменной ширине букв они не имеют засечек. Буквы в шрифтах семейства Script как бы написаны от руки. Семейство Decorative содержит глифы в виде небольших картинок (пиктограмм).

В следующей таблице мы привели примеры шрифтов различных семейств.

Семейство Название шрифта Пример текста
Modern Courier Шрифт в стиле Modern
Roman Times Шрифт в стиле Roman
Swiss Helvetica Шрифт в стиле Swiss
Script Script Cyrillic Шрифт в стиле Script
Decorative Wingdings Dm13m,0=;rative

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

Другая важная характеристика шрифта - это размер букв. Из 11 тома "Библиотеки системного программиста" вы знаете, что для описания вертикального размера букв шрифта используются несколько параметров. Не останавливаясь на тонкостях, отметим, что шрифты, содержащие буквы разного размера, являются разными шрифтами.

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

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

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

Вы знаете, что шрифты могут иметь нормальное (normal), жирное (bold) или наклонное (italic) начертание:

Начертание Образец шрифта
Normal AaBbCcDdEeFfGgHhIiJjKkLl АаБбВвГгДдЕеЖжЗзИиКкЛлМмНн
Bold AaBbCcDdEeFfGgHhIiJjKkLl АаБбВвГгДдЕеЖжЗзИиКкЛлМмНн
Italic AaBbCcDdEeFfGgHhIiJjKkLl АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРр
GDI получает жирное и наклонное начертание растровых шрифтов из нормального при помощи соответствующих алгоритмов утолщения и наклона шрифта. Такие алгоритмы могут быть использованы и для масштабируемых шрифтов True Type, однако лучших результатов можно достигнуть при использовании отдельных файлов шрифтов True Type для нормального, жирного и наклонного начертания.

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

Текст с подчеркиванием

Иногда используется шрифт с перечеркнутыми буквами.

GDI выполняет подчеркивание самостоятельно, файлы шрифтов не содержат глифы с подчеркиванием.

Растровые и векторные шрифты хранятся в системном каталоге Windows в файлах с расширением имени fon.

Глифы масштабируемых шрифтов True Type находятся в файлах с расширением имени ttf, причем сами эти файлы могут располагаться в любом каталоге. В процессе регистрации масштабируемого шрифта Windows создает в своем системном каталоге файлы с расширением имени fot, которые содержат ссылки на соответствующие ttf-файлы.

С помощью приложения Control Panel вы можете добавлять или удалять любые шрифты.Следует, однако, учитывать ограничение: в системе можно одновременно использовать не более 253, к тому же для представления жирного и наклонного начертания используются отдельные масштабируемые шрифты. Чрезмерное количество установленных шрифтов может привести к снижению производительности системы.


Комбинирование областей


Функция CombineRegion позволяет вам изменить существующую область, скомбинировав ее из двух других:

int WINAPI CombineRgn( HRGN hrgnDest, // новая область HRGN hrgnSrc1, // первая исходная область HRGN hrgnSrc2, // вторая исходная область int fnCombineMode); // режим комбинирования

Перед вызовом функции вам надо определить все три области. Функция объединит области hrgnSrc1 и hrgnSrc2, изменив соответствующим образом область hrgnDest.

Способ комбинирования областей зависит от значения параметра fnCombineMode:

Значение параметра fnCombineMode Способ образования области hrgnDest
RGN_AND Пересечение областей hrgnSrc1 и hrgnSrc2
RGN_OR Объединение областей hrgnSrc1 и hrgnSrc2
RGN_XOR Объединение областей hrgnSrc1 и hrgnSrc2 с исключением перекрывающихся областей
RGN_DIFF Область hrgnSrc1, которая не входит в область hrgnSrc2
RGN_COPY Область hrgnSrc1

В зависимости от результата выполнения операции функция CombineRegion может вернуть одно из следующих значений:

Значение Описание
ERROR Ошибка
NULLREGION Новая область пустая
SIMPLEREGION Новая область не является самопересекающейся (т. е. граница созданной области не пересекает саму себя)
COMPLEXREGION Создана самопересекающаяся область

Для облегчения комбинирования областей в файле windowsx.h определены макрокоманды, предназначенные для копирования, пересечения, объединения и вычитания областей. Все они созданы на базе только что описанной функции CombineRegion :

#define CopyRgn (hrgnDst, hrgnSrc) \ CombineRgn(hrgnDst, hrgnSrc, 0, RGN_COPY)

#define IntersectRgn (hrgnResult, hrgnA, hrgnB) \ CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_AND)

#define SubtractRgn (hrgnResult, hrgnA, hrgnB) \ CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_DIFF)

#define UnionRgn (hrgnResult, hrgnA, hrgnB) \ CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_OR)

#define XorRgn (hrgnResult, hrgnA, hrgnB) \ CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_XOR)



Контекст для метафайла


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

Для создания контекста метефайла используется функция CreateMetaFile :

HDC WINAPI CreateMetaFile(LPCSTR lpszFileName);

Параметр lpszFileName должен указывать на строку, содержащую путь к имени файла, в который будут записаны команды GDI, или NULL. В последнем случае создается метафайл в оперативной памяти.

После выполнения рисования в контексте метафайла следует закрыть метафайл, вызвав функцию CloseMetaFile :

HMETAFILE WINAPI CloseMetaFile(HDC hdc);

Эта функция закрывает метафайл для контекста hdc и возвращает идентификатор метафайла. Идентификатор закрытого метафайла использовать нельзя, так как он не содержит никакой полезной информации.

Что можно сделать с полученным идентификатором метафайла?

Можно скопировать метафайл в обычный дисковый файл, вызвав функцию CopyMetaFile :

HMETAFILE WINAPI CopyMetaFile(HMETAFILE hmf, LPCSTR lpszFileName);

Параметр hmf определяет метафайл, параметр lpszFileName содержит путь к имени файла, в который будет записан метафайл .

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

BOOL WINAPI PlayMetaFile(HDC hdc, HMETAFILE hmf);

Наконец, при помощи функции DeleteMetaFile можно удалить метафайл:

BOOL WINAPI DeleteMetaFile(HMETAFILE hmf);

Удаление метафайла с помощью функции DeleteMetaFile делает недействительным идентификатор метафайла hmf и освобождает оперативную память, занятую метафайлом. Если метафайл был создан как обычный дисковый файл, функция DeleteMetaFile не удаляет его с диска.

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

HMETAFILE WINAPI GetMetaFile(LPCSTR lpszFileName);



Контекст для памяти


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

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

HDC WINAPI CreateCompatibleDC(HDC hdc);

Созданный таким образом контекст памяти удаляется при помощи функции DeleteDC.

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



Контекст для устройства DISPLAY


В некоторых случаях требуется получить контекст отображения, позволяющий приложению рисовать в любом месте экрана дисплея. Такой контекст можно создать при помощи функции CreateDC, указав в качестве имени драйвера строку "DISPLAY ", а в качестве остальных параметров - значение NULL:

hdc = CreateDC("DISPLAY", NULL, NULL, NULL);

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

После использования созданный таким образом контекст отображения следует удалить, вызвав функцию DeleteDC.

Есть еще два способа получения контекста для экрана.

Приложение может получить контекст отображения для всего экрана при помощи функции GetDC, указав в качестве параметра значение NULL:

hdc = GetDC(NULL);

Полученный таким образом контекст следует освободить после использования при помощи функции ReleaseDC, передав ей вместо идентификатора окна значение NULL:

ReleaseDC(NULL, hdc);

Еще один способ связан с использованием функции GetDCEx, описание которой будет приведено ниже.



Контекст физического устройства


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

В отличие от контекста отображения, контекст физического устройства не получается, а создается, для чего используется функция CreateDC :

HDC WINAPI CreateDC( LPCSTR lpszDriver, // имя драйвера LPCSTR lpszDevice, // имя устройства LPCSTR lpszOutput, // имя файла или порта вывода const void FAR* lpvInitData); // данные для инициализации

Параметр lpszDriver является указателем на строку символов, содержащую имя драйвера, обслуживающего физическое устройство. Имя драйвера совпадает с именем файла *.drv, содержащего сам драйвер и расположенного в системном каталоге Windows.

Имя устройства lpszDevice - это название самого устройства, описанное, например, в файле win.ini в разделе [devices].

Параметр lpszOutput указывает на структуру данных типа DEVMODE, используемую при инициализации устройства вывода. Если при работе с устройством нужно использовать параметры, установленные при помощи приложения Control Panel, параметр lpszOutput следует указать как NULL.

Более подробно вопросы работы с принтером будут рассмотрены в отдельной главе этого тома.

В приведенном ниже примере создается контекст устройства для лазерного принтера HP Laserjet III, подключенного к порту LPT1:, причем в системном каталоге Windows для этого принтера установлен драйвер hppcl5a.drv:

hdc = CreateDC("hppcl5a", "HP LaserJet III", "LPT1:", NULL);

Аналогично, для принтера Epson FX-850, подключенного к порту LPT2:, и работающему через драйвер epson9.drv:

hdc = CreateDC("epson9", "Epson FX-850", "LPT2:", NULL);

Созданный при помощи функции CreateDC контекст устройства следует удалить (но не освободить), вызвав функцию DeleteDC:

BOOL WINAPI DeleteDC(HDC hdc);

Эта функция возвращает TRUE при нормальном завершении и FALSE при возникновении ошибки.



Контекст отображения


Итак, займемся описанием "листа бумаги", на "поверхности" которого выполняется рисование графических изображений и текста - контекста отображения.

Прежде всего уточним понятия контекста отображения и контекста устройства .

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

Рис. 1.1. Вывод данных через контекст устройства

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



Контекст отображения для класса окна


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

wc.style = CS_CLASSDC;

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

В отличие от общего контекста отображения, приложения, однажды получив контекст отображения для класса окна, могут не освобождать его. То есть для контекста этого типа после функций BeginPaint и GetDC можно не вызывать функции EndPaint и ReleaseDC. Если же приложение вызовет функцию EndPaint или ReleaseDC, они не будут ничего делать и сразу вернут управление. Для уменьшения вероятности ошибки мы рекомендуем вам всегда освобождать контекст отображения, даже если это и не требуется для данного типа контекста.

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

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

Зачем используется контекст отображения класса окна?

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



Контекст отображения для окна


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

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

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

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



Контекст отображения для принтера


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

HDC WINAPI CreateDC( LPCSTR lpszDriver, // имя драйвера LPCSTR lpszDevice, // имя устройства LPCSTR lpszOutput, // имя файла или порта вывода const void FAR* lpvInitData); // данные для инициализации

Созданный при помощи функции CreateDC контекст устройства следует удалить после использования, вызвав функцию DeleteDC :

BOOL WINAPI DeleteDC(HDC hdc);

Как мы уже говорили, параметр lpszDriver является указателем на строку символов, содержащую имя драйвера, обслуживающего физическое устройство. Имя драйвера совпадает с именем файла *.drv, содержащего драйвер. Этот драйвер находится в системном каталоге Windows.

Имя устройства lpszDevice - это название устройства.

Параметр lpszOutput указывает на структуру данных типа DEVMODE, используемую при инициализации устройства вывода. Если при работе с устройством нужно использовать параметры, установленные при помощи приложения Control Panel, параметр lpszOutput следует указать как NULL.

Однако как определить эти параметры?

Текущий принтер описан в файле win.ini в разделе [windows]:

[windows] ... ... device=HP LaserJet III,hppcl5a,LPT1: ...

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

hdc = CreateDC("hppcl5a", "HP LaserJet III", "LPT1:", NULL);

Однако к компьютеру может быть подключено несколько принтеров. Например, к порту LPT1: может быть подключен лазерный принтер, а к порту LPT2: - матричный или струйный принтер.

Список подключенных принтеров, имена драйверов и портов ввода/вывода можно найти в разделе [devices] файла win.ini:

[devices] Epson FX-850=EPSON9,LPT1: HP LaserJet III=hppcl5a,LPT1:

Ваше приложение может получить параметры принтера, используемого по умолчанию, а также параметры всех установленных принтеров непосредственно из файла win.ini.
Это можно легко сделать при помощи функции GetProfileString:

int GetProfileString( LPCSTR lpszSection; // адрес раздела LPCSTR lpszEntry; // адрес элемента раздела LPCSTR lpszDefault; // адрес строки по умолчанию LPSTR lpszReturnBuffer; // адрес буфера для записи int cbReturnBuffer; // размер буфера

Параметр lpszSection должен указывать на имя раздела, в нашем случае на строку "windows". Через параметр lpszEntry передается адрес текстовой строки, содержащий имя элемента раздела, в нашем случае это адрес строки "device".

Если указанного элемента или раздела нет в файле win.ini, используется строка по умолчанию, адрес которой передается через параметр lpszDefault.

Найденная строка (или строка по умолчанию) будет записана в буфер, адрес которого передается через параметр lpszReturnBuffer. Размер буфера должен быть указан в параметре cbReturnBuffer.

Для получения контекста отображения текущего принтера обычно используется такая функция:

HDC PASCAL GetPrinterDC() { char msgbuf[128]; LPSTR pch; LPSTR pchFile; LPSTR pchPort;

// Определяем текущий принтер из файла win.ini if(!GetProfileString("windows", "device", "", msgbuf, sizeof(msgbuf))) return NULL;

// Выполняем разбор строки для выделения имени драйвера, // имени устройства и имени порта ввода/вывода for(pch=msgbuf; *pch && *pch != ','; pch=AnsiNext(pch));

if(*pch) *pch++ = 0;

// Пропускаем управляющие символы и символ табуляции while(*pch && *pch <= ' ') pch=AnsiNext(pch); pchFile = pch;

while(*pch && *pch != ',' && *pch > ' ') pch = AnsiNext(pch);

if(*pch) *pch++ = 0;

while(*pch && (*pch <= ' ' *pch == ',')) pch = AnsiNext(pch);

pchPort = pch;

while(*pch && *pch > ' ') pch = AnsiNext(pch);

*pch = 0;

// Возвращаем контекст отображения для принтера return CreateDC(pchFile, msgbuf, pchPort, NULL); }

Приведенная выше функция способна работать с двухбайтовыми кодами символов, так как для сканирования строки используется функция AnsiNext.



Ситуация, однако, усложняется при необходимости сделать выбор между одним из установленных принтеров. Хорошо спроектированное приложение должно позволять пользователю выбирать любой из установленных принтеров, а не только тот, который используется по умолчанию.

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

Для получения списка установленных принтеров вы можете воспользоваться все той же функцией GetProfileString , указав в качестве первого параметра адрес строки "devices", а в качестве второго - значение NULL:

GetProfileString("devices", NULL, "", msgbuf, sizeof(msgbuf));

В этом случае в буфер будут переписаны все строки из раздела [devices], каждая строка будет закрыта двоичным нулем, последняя строка будет закрыта двумя двоичными нулями.

Однако есть еще одна задача, которую должно уметь решать ваше приложение.

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

Заметим, что эти функции экспортируются драйвером как обычной DLL-библиотекой (драйвер принтера и есть DLL-библиотека). Для вызова одной из этих функций вы должны загрузить драйвер явным образом, вызвав функцию LoadLibrary , а затем получить адрес точки входа при помощи функции GetProcAddress . DLL-библиотеки и перечисленные выше функции мы описали в 13 томе "Библиотеки системного программиста".

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

К счастью, DLL-библиотека commdlg.dll содержит функцию PrintDlg, способную решить все перечисленные выше задачи и вдобавок получить для выбранного принтера контекст отображения, который можно использовать для рисования на принтере (или печати, если вам так больше нравится).


LCustData


Произвольные данные, которые приложение может передать функции фильтра.



LfCharSet


Набор символов.

Можно использовать одну из следующих констант, определенных в файле windows.h:

Константа Значение Описание
ANSI_CHARSET 0 Набор символов в кодировке ANSI
DEFAULT_CHARSET 1 Не используется при отображении шрифтов. Определяется при необходимости запросить шрифт с заданным именем и размером шрифта. Следует использовать с осторожностью, так как если указанного шрифта нет, GDI может выделить шрифт с любым набором символов
SYMBOL_CHARSET 2 Символьный шрифт, такой как, например, Wingdings
SHIFTJIS_CHARSET 128 Шрифт, в котором для представления символов используется двухбайтовая кодировка. Нужен для работы с японской версией Windows
OEM_CHARSET 255 Набор символов в кодировке OEM



LfClipPrecision


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

Можно использовать следующие константы: CLIP_DEFAULT_PRECIS , CLIP_CHARACTER_PRECIS , CLIP_STROKE_PRECIS , CLIP_MASK , CLIP_LH_ANGLES , CLIP_TT_ALWAYS , CLIP_EMBEDDED .

Если указана константа CLIP_LH_ANGLES , направление вращения текста зависит от установленного режима отображения.



LfEscapement


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

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



LfFaceName


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

Вы можете указать, что вам нужен, например, шрифт "Arial", однако это вовсе не гарантирует, что именно этот шрифт будет предоставлен в распоряжение приложения.



LfHeight


Высота шрифта в логических единицах (зависят от установленного режима отображения).

Можно указывать положительные и отрицательные значения, а также нуль. Если указано нулевое значение, выбирается шрифт размером в 12 пунктов (значение по умолчанию).

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

Абсолютная величина отрицательного значения определяет высоту символов, т.е. tmHeight - tmInternalLeading.



LfItalic


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



LfOrientation


Это поле определяет ориентацию символов шрифта. К сожалению, операционная система Windows версии 3.1 игнорирует поле lfOrientation.



LfOutPrecision


Требуемая степень соответствия параметров шрифта.

Это поле используется для того, чтобы указать GDI способ выбора между двумя шрифтами, имеющими одинаковое название, но разный тип. Например, для удовлетворения запроса можно использовать растровый или масштабируемый шрифт с названием OddType. Если в поле lfOutPrecision указать константу OUT_TT_PRECIS, будет выбран масштабируемый шрифт.

Можно указывать одну из следующих констант:

Константа Значение Описание
OUT_DEFAULT_PRECIS 0 Используется точность, заданная по умолчанию
OUT_STRING_PRECIS 1 Выбирается шрифт, для которого соблюдается наибольшее соответствие в размерах символов
OUT_CHARACTER_PRECIS 2 Аналогично OUT_STRING_PRECIS
OUT_STROKE_PRECIS 3 Требуется точное соответствие между запрошенными атрибутами и атрибутами полученного шрифта
OUT_TT_PRECIS 4 Выбирается масштабируемый шрифт True Type, даже если есть подходящий растровый или векторный шрифт
OUT_DEVICE_PRECIS 5 Выбирается шрифт устройства вывода
OUT_RASTER_PRECIS 6 Выбирается растровый шрифт
OUT_TT_ONLY_PRECIS 7 Используются только шрифты True Type



LfPitchAndFamily


С помощью этого поля можно определить, нужна ли фиксированная или переменна ширина символов. Кроме этого, можно определить семейство, к которому должен принадлежать полученный шрифт.

Фиксированная или переменная ширина символов задается при помощи следующих констант:

Константа Описание
DEFAULT_PITCH Не имеет значения, будет ли шрифт иметь фиксированную или переменную ширину символов
FIXED_PITCH Нужен шрифт с фиксированной шириной символов
VARIABLE_PITCH Нужен шрифт с переменной шириной символов

Вы можете объединить при помощи логической операции ИЛИ эти константы с константами, соответствующими семейству шрифта:

Константа Описание
FF_DECORATIVE Шрифт, содержащий маленькие рисунки (пиктограммы). Примером такого шрифта может послужить шрифт Wingdings, поставляемый в составе Windows
FF_DONTCARE Семейство шрифта не имеет значения
FF_MODERN Семейство Modern. Фиксированная ширина символов, могут быть засечки (но могут и не быть)
FF_ROMAN Семейство Roman. Переменная ширина букв, есть засечки
FF_SCRIPT Семейство Script. Рукописный шрифт
FF_SWISS Семейство Swiss. Переменная ширина букв, нет засечек



LfQuality


Качество шрифта, полученного при отображении.

Можно указывать одну из следующих констант:

Константа Описание
DEFAULT_QUALITY Качество не имеет значения
DRAFT_QUALITY Низкое качество. Допустимо масштабирование шрифтов, синтезирование наклонных, жирных, перечеркнутых и подчеркнутых символов
PROOF_QUALITY Высокое качество. Масштабирование шрифтов не допускается. При этом могут быть получены символы, имеющие размер, немного меньший запрошенного



LfStrikeOut


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



LfUnderline


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



LfWeight


Вес шрифта. Определяет жирность символов шрифта и может находиться в пределах от 0 до 1000. Файл windows.h содержит определение символических констант для этого поля:

Константа Значение
FW_DONTCARE 0
FW_THIN 100
FW_EXTRALIGHT 200
FW_ULTRALIGHT 200
FW_LIGHT 300
FW_NORMAL 400
FW_REGULAR 400
FW_MEDIUM 500
FW_SEMIBOLD 600
FW_DEMIBOLD 600
FW_BOLD 700
FW_EXTRABOLD 800
FW_ULTRABOLD 800
FW_BLACK 900
FW_HEAVY 900

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



LfWidth


Ширина символов в логических единицах.

Если указано нулевое значение, используется значение по умолчанию, которое зависит от высоты шрифта и отношения масштабов по осям координат (aspect ratio) для шрифта и устройства вывода.



Личный контекст отображения


Указав в стиле класса окна значение CS_OWNDC , можно добиться того, что для каждого окна, созданного на базе такого класса, Windows создаст отдельную структуру контекста отображения:

wc.style = CS_OWNDC;

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

Однако не будет ошибкой, если приложение будет использовать личный контекст отображения как общий, то есть каждый раз при обработке сообщения WM_PAINT (или другого сообщения, обработчик которого занимается рисованием) оно будет получать контекст и затем отдавать его. Соответствующие функции (BeginPaint, EndPaint, GetDC, ReleaseDC) будут возвращать управление, не выполняя никаких действий.

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



Литература


Фролов, Г. В. Фролов. Библиотека системного программиста. Тома 11-13. Операционная система Microsoft Windows 3.1 для программиста. Часть 1-3. Москва, "Диалог-МИФИ", 1994

Charles Petzold. Programming Windows 3.1. Microsoft Press. One Microsoft Way. Redmont, Washington, 1992

Brent Rector. Developing Windows 3.1 Applications with Microsoft C/C++. SAMS, 1992

Daniel A. Norton. Writing Windows Device Drivers. Addison-Wesley Publishing Company, 1992

Фролов, Г. В. Фролов. Библиотека системного программиста. Том 6. Защищенный режим процессоров Intel 80286/80386/80486. Москва, "Диалог-МИФИ", 1993

Windows. Справочник для программистов. Версия 3.0. Часть 1, 2. Москва, "Научный центр", 1991

Гладков, Г. В. Фролов. Программирование в Microsoft Windows. Часть 2. Москва, "Диалог-МИФИ", 1992

Эллис, Б. Строуструп. Справочное руководство по языку программирования C++ с комментариями. Москва, МИР, 1992

Фролов, Г. В. Фролов. Персональный компьютер. Шаг за шагом.. Том 2. Операционная система Microsoft Windows 3.1. Москва, "Диалог-МИФИ", 1994

Фролов, Г. В. Фролов. Библиотека системного программиста. Том 10. Компьютер IBM PC/AT, MS-DOS и Windows. Вопросы и ответы. Москва, "Диалог-МИФИ", 1994



Логическая система координат


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

Рис. 2.2. Одна из возможных систем координат

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

int WINAPI SetMapMode(HDC hdc, int nMapMode);

Для контекста отображения hdc эта функция устанавливает новый режим отображения, заданный параметром nMapMode, возвращая номер режима отображения, который был установлен раньше.

Параметр nMapMode может принимать одно из следующих значений.

Режим отображения Направление оси X Направление оси Y Размер одной логической единицы
MM_TEXT Вправо Вниз 1 пиксел
MM_LOMETRIC Вправо Вверх 0,1 мм
MM_HIMETRIC Вправо Вверх 0,01 мм
MM_LOENGLISH Вправо Вверх 0,01 дюйм
MM_HIENGLISH Вправо Вверх 0,001 дюйм
MM_TWIPS Вправо Вверх 1/1440 дюйма
MM_ISOTROPIC Можно выбирать Можно выбирать Произвольный, одинаковый для осей X и Y
MM_ANISOTROPIC Можно выбирать Можно выбирать Произвольный, может быть разный для осей X и Y

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

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

В режимах MM_LOMETRIC, MM_HIMETRIC, MM_LOENGLISH, MM_HIENGLISH, MM_TWIPS используется более привычное направление осей координат и единицы длины, не зависящие от аппаратного обеспечения устройства вывода.

В режиме MM_ISOTROPIC вы можете выбирать произвольное направление осей координат и произвольный (но одинаковый) масштаб для осей X и Y. Заметим, что произвольное направление координат в нашем случае не подразумевает их произвольного расположения относительно вертикальной и горизонтальной осей - ось X может располагаться только горизонтально, ось Y - только вертикально.

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

Во всех режимах отображения, кроме MM_TEXT и MM_ANISOTROPIC с разным масштабом для осей X и Y, приложение может не заботиться о "квадратуре пиксела", так как масштаб по осям координат одинаковый и не зависит от особенностей устройства вывода.

Сделаем небольшое пояснение относительно режима отображения MM_TWIPS. В этом режиме используется единица длины twip (от twentieth of a point - двадцатая часть точки, или, в терминах полиграфии, двадцатая часть пункта). Размер одного пункта приблизительно равен 1/72 дюйма, следовательно размер единицы длины twip равен 1/1440 дюйма.

С помощью функции GetMapMode приложение может в любой момент времени определить номер режима отображения, выбранный в контекст отображения hdc:

int WINAPI GetMapMode(HDC hdc);


LpfnPrintHook


Адрес функции фильтра для диалоговой панели "Print".



LpfnSetupHook


Адрес функции фильтра для диалоговой панели "Print Setup".



LpPrintTemplateName


Адрес текстовой строки, закрытой нулем, содержащей имя ресурса для шаблона диалоговой панели "Print". Для использования этого поля необходимо указать флаг PD_ENABLEPRINTTEMPLATE.



LpSetupTemplateName


Адрес текстовой строки, закрытой нулем, содержащей имя ресурса для шаблона диалоговой панели "Print Setup". Для использования этого поля необходимо указать флаг PD_ENABLESETUPTEMPLATE.



LStructSize


Размер структуры PRINTDLG в байтах. Это поле следует заполнить перед вызовом функции PrintDlg.



Масштаб осей для окна


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

По умолчанию используется значение (1,1), т. е. используется масштаб 1:1. Приложение может изменить масштаб осей для окна, вызвав функцию SetWindowExt .



Масштаб осей физических координат


Контекст отображения содержит масштаб осей для физического устройства (viewport extent ), который вместе с масштабом осей в окне используется в процессе преобразования координат.

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



Механизм реализации логической палитры


В самом начале этой главы мы рассказали вам о системной цветовой палитре. Вы знаете, что в системной палитре, состоящей из 256 ячеек, 20 ячеек зарезервированы для статических цветов. Остальные 236 ячеек доступны для приложений. Что же приложения могут с ними сделать?

Любое приложение может создать свою собственную палитру цветов в виде массива размером до 256 элементов, содержащего данные типа PALETTEENTRY, которые могут хранить RGB-цвета или номера цветов в системной палитре.

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

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

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

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

Активное приложение имеет больший приоритет в использовании свободных ячеек системной палитры. Как правило, запрос активного приложения (точнее говоря, активного окна) на реализацию логической палитры выполняется полностью, так как перед реализацией все ячейки системной палитры (кроме 20 зарезервированных) отмечаются как свободные.


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

На рис. 3.6 показан процесс реализации логической палитры для активного окна.



Рис. 3.6. Реализация логической палитры для активного окна

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

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



Рис. 3.7. Реализация логической палитры для фонового окна

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

Однако в нашей логической палитре есть цвета, которым нет точного соответствия в системной палитре, причем свободных ячеек тоже не осталось. В этом случае GDI отображает эти цвета на близкие из системной палитры (на рис. 3.7 такое отображение показано пунктирной линией).

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


Метрические режимы отображения


Режим MM_LOMETRIC , наряду с режимами MM_HIMETRIC , MM_LOENGLISH , MM_HIENGLISH и MM_TWIPS, относится к метрическим режимам. Эти режимы отображения позволяют использовать привычные единицы измерения, такие как миллиметры и дюймы.

В метрических режимах отображения используются полные формулы преобразования координат, приведенные выше в разделе "Преобразование координат". В этих формулах приложение может изменять переменные, определяющие смещение начала физической или логической системы координат xViewOrg, yViewOrg, xWinOrg и yWinOrg.

Приложение не может изменить значения переменных xViewExt, yViewExt, xWinExt и yWinExt, от которых зависит масштаб по осям координат. Отношения xViewExt/xWinExt и yViewExt/yWinExt имеют фиксированное значение для каждого из метрических режимов отображения.

Заметим, что для этих режимов отношение yViewExt/yWinExt имеет отрицательный знак, в результате чего ось Y оказывается направленной снизу вверх.

Обратим ваше внимание на одно важное обстоятельство, связанное с использованием метрических режимов отображения.

Сразу после переключения в метрический режим отображения система координат примет достаточно странный вид (рис. 2.4).

Рис. 2.4. Ориентация осей сразу после переключения в метрический режим отображения

Ось X, как и следовало ожидать, окажется направленной слева направо, а ось Y - снизу вверх. Точка с координатами (0,0) будет находиться в верхнем левом углу экрана, поэтому для того чтобы нарисовать что-нибудь в такой системе координат, вам придется для y-координаты графических объектов использовать отрицательные числа. Для того чтобы система координат приняла более удобный вид, можно переместить начало физических координат в нижний левый угол окна или в центр окна.

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

static short cxClient, cyClient; .... case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); ....
return 0; }

Для того чтобы расположить начало координат в левом нижнем углу окна, следует вызвать функцию SetViewportOrg, передав ей новые координаты начала физической системы координат (0,cyClient):

SetViewportOrg(hdc, 0, cyClient);

Полученная в результате система координат показана на рис. 2.5.



Рис. 2.5. Метрическая система координат, начало координат находится в левом нижнем углу окна

Аналогичным образом можно расположить начало системы координат в середине окна (рис. 2.6), обеспечив возможность использования положительных и отрицательных координат вдоль оси X и Y:

SetViewportOrg(hdc, cxClient/2, cyClient/2);



Рис. 2.6. Метрическая система координат, начало координат находится в центре окна


Начальные координаты кисти


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

Приложение может изменить начальные координаты кисти при помощи функций SetBrushOrg и UnrealizeObject .



Начало системы физических координат


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

По умолчанию начало системы физических координат установлено в точку (0,0). Для перемещения начала системы координат окна можно использовать функцию SetViewportOrg .



Начало системы координат для окна


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

По умолчанию начало системы координат для окна (window origin ) установлено в точку (0,0). Для перемещения начала системы координат окна можно использовать функцию SetWindowOrg .



Настройка атрибутов контекста отображения для рисования линий


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



NCopies


Значение для инициализации органа управления "Copies", если поле hDevMode содержит значение NULL.



NFromPage


Начальное значение для инициализации органа управления "From" диалоговой панели "Print". Используется только в том случае, если в поле Flags указан флаг PD_PAGENUMS. Максимальное значение для поля nFromPage составляет 0xfffe.

После возвращения из функции PrintDlg это поле содержит номер страницы документа, с которой должна начинаться печать.