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

         

NMaxPage


Максимальное количество страниц, которое можно задать при помощи органов управления "From" и "To".



NMinPage


Минимальное количество страниц, которое можно задать при помощи органов управления "From" и "To".



NToPage


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

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



Область эллиптической формы


Область эллиптической формы (или, как частный случай, круглой формы) можно создать при помощи функции CreateEllipticRgn :

HRGN WINAPI CreateEllipticRgn( int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

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

Функция CreateEllipticRgnIndirect также используется для создания области в форме эллипса:

HRGN WINAPI CreateEllipticRgnIndirect(const RECT FAR* lprc);

В отличие от функции CreateEllipticRgn координаты прямоугольника задаются с помощью структуры типа RECT, указатель на которую передается через параметр lprc.





Область ограничения


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

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

Для работы с областями предназначены следующие функции: CreateEllipticRgn , CreateEllipticRgnIndirect , CreatePolygonRgn , CreatePolyPolygonRgn , CreateRectRgn , CreateRoundRectRgn , ExcludeClipRgn , IntersectClipRgn , OffsetClipRgn , SelectclipRgn .


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

int WINAPI SelectClipRgn(HDC hdc, HRGN hrgn);

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



Область в виде многоугольника


Можно создать область в виде произвольного многоугольника. Для этого следует воспользоваться функцией CreatePolygonRgn :

HRGN WINAPI CreatePolygonRgn( const POINT FAR* lppt, // адрес массива точек int cPoints, // размер массива int fnPolyFillMode); // режим заполнения

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

HRGN WINAPI CreatePolyPolygonRgn( const POINT FAR* lppt, // адрес массива точек int FAR* lpnPolyCounts, // адрес массива количества точек // в многоугольниках int cPolygons); // количество многоугольников

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



Области


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



Общий контекст отображения


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

Для получения общего контекста отображения приложение должно вызвать функцию BeginPaint (при обработке сообщения WM_PAINT ) или GetDC (при обработке других сообщений). При этом перед регистрацией класса окна в поле стиля класса окна в структуре WNDCLASS не должны использоваться значения CS_OWNDC , CS_PARENTDC или CS_CLASSDC :

wc.style = 0;

Функция BeginPaint возвращает контекст отображения для окна hwnd:

HDC WINAPI BeginPaint(HWND hwnd, PAINTSTRUCT FAR* lpps);

Перед этим она подготавливает указанное окно для рисования, заполняя структуру типа PAINTSTRUCT (адрес которой передается через параметр lpps) информацией, которую можно использовать в процессе рисования.

Структура PAINTSTRUCT и указатели на нее (различных типов) описаны в файле windows.h:

typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[16]; } PAINTSTRUCT; typedef PAINTSTRUCT* PPAINTSTRUCT; typedef PAINTSTRUCT NEAR* NPPAINTSTRUCT; typedef PAINTSTRUCT FAR* LPPAINTSTRUCT;

Рассмотрим назначение отдельных полей структуры PAINTSTRUCT.

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

Анализируя содержимое поля fErase, приложение может определить, нужно ли перерисовывать фон окна. Если в этом поле находится значение TRUE, фон окна должен быть перерисован. Такая необходимость может возникнуть в том случае, если в классе, на базе которого создано окно, при регистрации не была выбрана кисть для закрашивания фона (поле hbrBackground структуры WNDCLASS).

Поле rcPaint, которое представляет собой структуру типа RECT, содержит координаты верхнего левого и правого нижнего угла прямоугольника, внутри которого нужно рисовать.
Напомним вам формат структуры RECT , описанной в файле windows.h:

typedef struct tagRECT { int left; int top; int right; int bottom; } RECT;

Мы уже говорили вам в 11 томе "Библиотеки системного программиста", что при обработке сообщения WM_PAINT приложение должно суметь перерисовать все окно или любую его часть. Сообщение WM_PAINT может попасть в функцию окна в том случае, если все окно или его часть требуют перерисовки. Поле rcPaint в структуре PAINTSTRUCT содержит координаты прямоугольной области, расположенной в окне и требующей перерисовки.

Остальные поля зарезервированы для Windows и не используются приложениями.

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

void WINAPI EndPaint(HWND hwnd, const PAINTSTRUCT FAR* lpps);

Функции EndPaint передаются те же параметры, что и функции BeginPaint.

Обычно обработчик сообщения WM_PAINT выглядит следующим образом:

PAINTSTRUCT ps; HDC hdc; ........ case WM_PAINT: { // Получаем контекст отображения hdc = BeginPaint(hwnd, &ps);

// После получения контекста отображения // можно вызывать функции GDI TextOut(hdc, 0, 0, (LPSTR)"String", 6); . . // Освобождаем контекст отображения EndPaint(hwnd, &ps); break; }

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

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

Функция GetDC возвращает контекст отображения для окна с идентификатором hwnd:

HDC WINAPI GetDC(HWND hwnd);

Полученный таким образом контекст отображения можно использовать для рисования во внутренней области окна (window client region).

Функция ReleaseDC освобождает контекст отображения hdc, полученный для окна hwnd:

int WINAPI ReleaseDC(HWND hwnd, HDC hdc);

Мы еще раз обращаем ваше внимание на необходимость своевременного освобождения общего контекста отображения.

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


Определение логического шрифта


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

Приложение может получить идентификатор шрифта, указав его параметры (такие как размеры символов, семейство шрифта, наклон относительно горизонтальной оси и т. п.) функции CreateFont . Эта функция имеет 14 параметров, поэтому не слишком удобна в использовании. Вместо нее лучше пользоваться функцией CreateFontIndirect :

HFONT WINAPI CreateFontIndirect(const LOGFONT FAR* lplf);

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

В качестве параметра функции CreateFontIndirect передается указатель на структуру типа LOGFONT , определенную в файле windows.h:

typedef struct tagLOGFONT { int lfHeight; int lfWidth; int lfEscapement; int lfOrientation; int lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality; BYTE lfPitchAndFamily; char lfFaceName[LF_FACESIZE]; } LOGFONT; typedef LOGFONT* PLOGFONT; typedef LOGFONT NEAR* NPLOGFONT; typedef LOGFONT FAR* LPLOGFONT;

Перед вызовом функции CreateFontIndirect вы должны заполнить структуру LOGFONT нужными значениями, определяющими параметры шрифта. В неиспользованные поля следует записать нулевые значения. Можно записать нулевые значения во все поля, однако это едва ли имеет смысл.

Опишем назначение отдельных полей структуры LOGFONT. При этом мы будем пользоваться метриками шрифта, описанными в 11 томе "Библиотеки системного программиста" (стр. 144).



Определение метрик шрифта


Для удобства мы напомним вам методику определения метрик шрифта.

Метрику шрифта , выбранного в контекст отображения, можно определить с помощью функции GetTextMetrics :

BOOL WINAPI GetTextMetrics(HDC hdc, TEXTMETRIC FAR* lptm);

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

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

В случае успешного завершения функция возвращает значение TRUE, в противном случае - FALSE.

Структура TEXTMETRIC описана в файле windows.h следующим образом:

typedef struct tagTEXTMETRIC { int tmHeight; int tmAscent; int tmDescent; int tmInternalLeading; int tmExternalLeading; int tmAveCharWidth; int tmMaxCharWidth; int tmWeight; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmFirstChar; BYTE tmLastChar; BYTE tmDefaultChar; BYTE tmBreakChar; BYTE tmPitchAndFamily; BYTE tmCharSet; int tmOverhang; int tmDigitizedAspectX; int tmDigitizedAspectY; } TEXTMETRIC;

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

Рис. 5.5. Метрики шрифта

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

Общая высота букв находится в поле tmHeight структуры TEXTMETRIC. Эта высота складывается из двух компонент - tmAscent и tmDescent. Компонента tmAscent представляет собой высоту букв от базовой линии с учетом таких элементов, как тильда в букве "Й". Компонента tmDescent определяет пространство, занимаемое буквами ниже базовой линии. Сумма tmAscent и tmDescent в точности равна tmHeight.

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


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

Для ширины букв в структуре TEXTMETRIC есть два поля с именами tmAveCharWidth и tmMaxCharWidth. Поле tmAveCharWidth содержит среднее значение ширины строчных букв шрифта. Это значение приблизительно соответствует ширине латинской буквы "x". Поле tmMaxCharWidth определяет ширину самой широкой буквы в шрифте. Для шрифта с фиксированной шириной букв поля tmAveCharWidth и tmMaxCharWidth содержат одинаковые значения, которые зависят от самого шрифта.

Поле tmWeight определяет жирность шрифта. Может находиться в пределах от 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
Поля tmItalic, tmUnderlined, tmStruckOut определяют, соответственно, является ли шрифт наклонным, подчеркнутым или перечеркнутым.

Поля tmFirstChar и tmLastChar определяют, соответственно, коды первого и последнего символа, определенных в шрифте.

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

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

Поле tmPitchAndFamily содержит код семейства шрифта. В нем могут находится следующие флаги, соответствующие четырем младшим битам:

Значение Описание
TMPF_FIXED_PITCH Шрифт с фиксированной шириной букв
TMPF_VECTOR Векторный шрифт или масштабируемый шрифт True Type
TMPF_TRUETYPE Шрифт True Type
TMPF_DEVICE Шрифт устройства вывода, например, принтерный шрифт
<


Одновременно может быть установлено несколько флагов с префиксом имени TMPF.

Старшие четыре бита описывают семейство шрифта:

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

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

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


Основные определения


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

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

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

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



Основные понятия


1.1.

1.2.

Интерфейс графических устройств GDI операционной системы Microsoft Windows (в дальнейшем - просто GDI), как это можно предположить из названия, предназначен для взаимодействия приложений Windows с графическими устройствами, такими как видеомонитор, принтер или плоттер.

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

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

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

На первый взгляд, это обескураживает, однако такой механизм удобен для обеспечения аппаратной независимости.
Составляя программы для MS-DOS, вы работали с видеоадаптерами, указывая конкретные цвета и загружая в его память конкретные шрифты из отдельных файлов. Поэтому программы MS-DOS были крепко "привязаны" к аппаратуре. Для использования новых возможностей требовалось вносить изменения в программы. Приложения Windows способны работать в неизменном виде на любом оборудовании, лишь бы был соответствующий драйвер. Чем лучше используемая аппаратура, чем большими возможностями она обладает, тем ближе будут параметры полученного шрифта и цвета соответствовать запрошенным.

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

Интерфейс GDI - одна из наиболее сложных компонент Microsoft Windows версии 3.1. В Windows NT она получила дальнейшее развитие, однако все базовые понятия сохранились.

Из чего состоит интерфейс GDI с точки зрения приложения?

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

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

Перерисовка области


Вы можете отметить область как требующую перерисовки, вызвав функцию InvalidateRgn . В результате этого приложению будет передано сообщение WM_PAINT .

Приведем прототип функции InvalidateRgn:

void WINAPI InvalidateRgn(HWND hwnd, HRGN hrgn, BOOL fErase);

Через параметр hwnd следует передать идентификатор окна, содержащего обновленную область hrgn.

Параметр fErase определяет необходимость стирания фона окна перед перерисовкой. Если этот параметр имеет значение TRUE, фон стирается, если FALSE - нет.

Если ваше приложение обновило содержимое области, но не во время обработки сообщения WM_PAINT, оно может удалить область из списка областей, подлежащих перерисовке, вызвав функцию ValidateRgn :

void WINAPI ValidateRgn(HWND hwnd, HRGN hrgn);



Перья


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

Рис. 1.2. Линии разной ширины, нарисованные с использованием различных стилей

Как мы уже говорили, интерфейс GDI операционной системы Windows версии 3.1 позволяет рисовать сплошные линии различной ширины, а также пунктирные, штриховые и штрих-пунктирные линии шириной в 1 пиксел. Можно запросить любой цвет для рисования линии, однако вовсе не обязательно линия будет нарисована запрошенным цветом. Тонкости, связанные с цветом и цветовыми палитрами, мы рассмотрим позже.

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

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



Перо


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

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

Для работы с перьями приложение Windows должно использовать функции CreatePen , CreatePenIndirect , SelectObject .



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


Способы получения (и, соответственно, освобождения) контекста отображения разные для контекстов разного типа. Можно выделить следующие типы контекста отображения:

общий контекст отображения (common display context);

контекст отображения для класса окна (class display context);

личный контекст отображения (private display context);

родительский контекст отображения (parent display context);

контекст отображения для окна (window display context);

контекст физического устройства (device context);

информационный контекст (information context);

контекст для памяти (memory device context);

контекст для метафайла (metafile context).

Каждый из перечисленных выше контекстов имеет свои особенности и свое назначение.



Получение информации о шрифте


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



Преобразование DDB в DIB


Если перед вами встанет задача преобразования DDB в DIB (например, для последующей записи изображения в bmp-файл), вам не обойтись без функции GetDIBits :

int WINAPI GetDIBits( HDC hdc, // контекст отображения HBITMAP hbmp, // изображение DDB UINT uStartScan, // номер первой строки UINT uScanLines, // количество строк void FAR* lpvBits, // биты изображения BITMAPINFO FAR* lpbmi, // заголовок изображения UINT fuColorUse); // содержимое таблицы цветов

Параметры этой функции полностью аналогичны параметрам функции SetDIBits, однако действие прямо противоположное. Функция преобразует биты изображения в формат DIB и записывает их по адресу, заданному параметром lpvBits. Дополнительно заполняется заголовок lpbmi (если параметр lpvBits указан как NULL, функция ограничивается заполнением заголовка изображения)

Если вы собираетесь сохранить изображение DIB в bmp-файле, вы должны самостоятельно сформировать заголовок файла BITMAPFILEHEADER.



Преобразование координат


Теперь немного математики (не волнуйтесь, совсем немного).

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

В этих формулах используются следующие обозначения.

Переменные xWindow и yWindow обозначают, соответственно, логические координаты по оси X и Y. Физические координаты обозначаются как xViewport и yViewport. Таким образом, логические координаты (xWindow,yWindow) преобразуются в физические координаты (xViewport,yViewport).

С помощью переменных xViewOrg и yViewOrg можно изменить расположение начала физических координат. По умолчанию в контексте отображения значения атрибутов, соответствующих этим переменным, равны 0. Приложение может сместить начало координат, изменив значения переменных xViewOrg и yViewOrg.

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

Переменные xViewExt, yViewExt, xWinExt и yWinExt (вернее, их отношения) задают масштаб, который используется в процессе преобразования координат. Этот масштаб зависит от установленного режима отображения. Приложения могут изменить его только в режимах MM_ISOTROPIC и MM_ANISOTROPIC , для остальных режимов отображения используются фиксированные значения.

Обратные преобразования (из логических координат в физические) выполняются с использованием следующих формул:

Для выполнения подобных вычислений удобна функция MulDiv , определенная в программном интерфейсе Windows:

int WINAPI MulDiv( int nMultiplicand, // множимое int nMultiplier, // множитель int nDivisor); // делитель

Эта функция выполняет умножение 16-разрядного параметра nMultiplicand на 16-разрядный параметр nMultiplier, а затем делит полученное 32-разрядное произведение на 16-разрядный параметр nDivisor. В случае переполнения или при нулевом значении делителя функция возвращает отрицательное значение­32768.


Однако есть специальные функции, предназначенные для преобразования логических координат в физические и физических в логические. Это функции LPtoDP и DPtoLP.

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

BOOL WINAPI LPtoDP( HDC hdc, // идентификатор контекста отображения POINT FAR* lppt, // указатель на массив структур POINT int cPoints); // размер массива структур POINT

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

Через параметр lppt передается указатель на массив структур POINT, в котором находятся преобразуемые координаты. Размер массива определяется значением параметра cPoints. Структура POINT определена в файле windows.h следующим образом:

typedef struct tagPOINT { int x; int y; } POINT;

После успешного выполнения преобразования функция возвращает значение TRUE. При возникновении ошибки возвращается FALSE.

Обратное преобразование (физических координат в логические ) выполняется функцией DPtoLP :

BOOL WINAPI DPtoLP( HDC hdc, // идентификатор контекста отображения POINT FAR* lppt, // указатель на массив структур POINT int cPoints); // размер массива структур POINT

Назначение параметров функции DPtoLP и возвращаемое ей значение аналогично назначению параметров и возвращаемому значению функции LPtoDP.

Есть еще две функции, предназначенные для преобразования координат - ClientToScreen и ScreenToClient.

Функция ClientToScreen выполняет преобразование координат в системе координат, связанной с внутренней областью окна, в экранные координаты:

void WINAPI ClientToScreen(HWND hwnd, POINT FAR* lppt);

Параметр hwnd содержит идентификатор окна, для которого выполняется преобразование.

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

Функция ScreenToClient имеет аналогичные параметры, но выполняет обратное преобразование:

void WINAPI ScreenToClient(HWND hwnd, POINT FAR* lppt);


Приложение BMPINFO


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

Большинство приложений, тем не менее, нуждается в рисовании изображений DIB. Для иллюстрации методов работы с такими изображениями мы подготовили приложение BMPINFO. Это приложение умеет загружать bmp-файлы любых "легальных" форматов Windows и Presentation Manager версии 1.x, и выводит соответствующую информацию из заголовков этих файлов, однако оно способно рисовать только некомпрессованные изображения в формате Windows. Как мы уже говорили, в большинстве случаев это как раз именно то, что нужно.

В меню "File" есть строки "Open", "Info..." и, конечно, "Exit". С помощью строки "Open" вы можете выбрать bmp-файл. Если это некомпрессованный файл в формате Windows, он рисуется в окне приложения (рис. 4.7).

Рис. 4.7. Главное окно приложения BMPINFO

Выбрав из меню "File" строку "Info...", вы можете просмотреть информацию о файле, такую, как размер файла и заголовка, формат файла, размер изображения в пикселах и т. д. (рис. 4.8).

Рис. 4.8. Информация о bmp-файле

Если выбранный bmp-файл имеет формат Presentation Manager, на экране появится диалоговая панель, аналогичная изображенной на рис. 4.8.

Основной файл исходных текстов приложения BMPINFO приведен в листинге 4.6.

Листинг 4.6. Файл bmpinfo/bmpinfo.cpp

// ---------------------------------------- // Приложение BMPINFO // Просмотр и анализ bmp-файлов в формате DIB // ----------------------------------------


#define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h> #pragma hdrstop

#include "dib.hpp" #include "bmpinfo.hpp"

// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна char const szClassName[] = "BmpInfoClass";

// Заголовок окна char const szWindowTitle[] = "Bitmap Information";

// Размеры внутренней области окна short cxClient, cyClient;

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;

// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;

// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));

// Подключаем меню wc.lpszMenuName = "APP_MENU";

wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH); wc.lpszClassName = (LPSTR)szClassName;



// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================

LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;

static HFILE hfDIBFile; static HDIB hDib; static HPALETTE hPal, hOldPal;

// Размер файла static DWORD dwFileSize;

switch (msg) { case WM_CREATE: { hfDIBFile = NULL; hDib = NULL; hPal = NULL; return 0; }

// При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }

// Рисование в окне case WM_PAINT: { // Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);

// Если DIB был загружен, и он в формате // некомпрессованного bmp-файла для Windows, // рисуем его if((hDib != NULL) && (DIBType(hDib) == WINRGB_DIB)) { // Если при загрузке была создана палитра, // выбираем ее if(hPal) { hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc); }

// Рисуем DIB DIBPaint(hdc, 0, 0, hDib);

// Выбираем старую палитру if(hPal) { SelectPalette(hdc, hOldPal, FALSE); } }

// Для других форматов bmp-файлов выводим // информацию из заголовка файла else { if(hDib) DIBInfo(hDib, dwFileSize); }

// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }

// Обработка сообщений от меню case WM_COMMAND: { switch (wParam) { case CM_HELPABOUT: { MessageBox(hwnd, "Bitmap Information, v.1.0\n" "(C) Frolov A.V., 1994", "About BMPINFO", MB_OK | MB_ICONINFORMATION); return 0; }

// Загрузка bmp-файла case CM_FILEOPEN: { // Выбираем файл hfDIBFile = DIBSelectFile();

if(hfDIBFile != NULL) { // Читаем файл в память hDib = DIBReadFile(hfDIBFile, &dwFileSize);

// Если файл прочитан, создаем палитру на // базе таблицы цветов. Если таблицы цветов нет, // палитра не создается if((hDib != NULL) && (DIBType(hDib) == WINRGB_DIB)) { hPal = DIBCreatePalette(hDib); }



// Перерисовываем окно InvalidateRect(hwnd, NULL, TRUE); } return 0; }

// Выводим диалоговую панель с информацией из // заголовка bmp-файла case CM_FILEINFO: { if(hDib != NULL) DIBInfo(hDib, dwFileSize);

return 0; }

// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; }

default: return 0; } }

// Это сообщение приходит при изменении // системной палитры. Наше приложение в ответ // на это сообщение заново реализует свою логическую // палитру и при необходимости перерисовывает окно case WM_PALETTECHANGED: { // Если это не наше окно, передаем управление // обработчику сообщения WM_QUERYNEWPALETTE if (hwnd == (HWND) wParam) break; }

// В ответ на это сообщение приложение должно // реализовать свою логическую палитру и // обновить окно case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged;

// Выбираем логическую палитру в // контекст отображения hdc = GetDC(hwnd);

// При обработке сообщения WM_QUERYNEWPALETTE // палитра выбирается для активного окна, // а при обработке сообщения WM_PALETTECHANGED - // для фонового hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);

// Реализуем логическую палитру и выбираем // ее в контекст отображения nChanged = RealizePalette(hdc); SelectPalette(hdc, hOldPal, TRUE);

// Освобождаем контекст отображения ReleaseDC(hwnd, hdc);

// Если были изменения палитры, // перерисовываем окно if(nChanged) InvalidateRect(hwnd, NULL, TRUE);

return nChanged; }

case WM_DESTROY: {

// Удаляем логическую палитру

if(hPal)

DeletePalette(hPal);

PostQuitMessage(0); return 0; }

default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

Обработчик сообщения WM_CREATE сбрасывает содержимое переменных, в которых находятся идентификатор открытого bmp-файла, идентификатор загруженного изображения DIB, а также идентификатор палитры:

hfDIBFile = NULL; hDib = NULL; hPal = NULL;

Обработчик сообщения WM_PAINT, рисующий изображение DIB, достаточно прост:

hdc = BeginPaint(hwnd, &ps); if((hDib != NULL) && (DIBType(hDib) == WINRGB_DIB)) { if(hPal) { hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc); } DIBPaint(hdc, 0, 0, hDib); if(hPal) { SelectPalette(hdc, hOldPal, FALSE); } } else { if(hDib) DIBInfo(hDib, dwFileSize); } EndPaint(hwnd, &ps); return 0;



Если загружено изображение DIB, его идентификатор отличен от NULL. В этом случае вызывается функция DIBType, определенная в нашем приложении в файле dib.cpp (листинг 4.8). Эта функция выполняет все необходимые проверки полей структуры заголовка изображения и возвращает тип изображения. Для некомпрессованных изображений DIB в формате Windows возвращается значение WINRGB_DIB.

Если при загрузке bmp-файла выяснилось, что он содержит таблицу цветов, создается палитра, идентификатор которой записывается в переменную hPal. Если содержимое этой переменной отлично от NULL, перед рисованием обработчик сообщения WM_PAINT выбирает палитру в контекст отображения и реализует ее, вызывая функции SelectPalette и RealizePalette.

Далее вызывается функция DIBPaint, определенная в файле dib.cpp, которая рисует изображение DIB. В качестве параметров этой функции передается идентификатор контекста отображения, координаты (x,y) левого верхнего угла прямоугольной области, в которой нужно нарисовать изображение, и идентификатор загруженного изображения DIB.

После этого восстанавливается старая палитра (если были изменения палитры).

Если же было загружено изображение DIB в формате Presentation Manager, вызывается функция DIBInfo, которая выводит на экран диалоговую панель с параметрами этого изображения.

При выборе в меню "File" строки "Open" получает управление обработчик сообщения WM_COMMAND. Этот обработчик вызывает функцию DIBSelectFile, определенную в файле dib.cpp. Функция DIBSelectFile выводит на экран стандартную диалоговую панель "Open" и позволяет вам выбрать для загрузки любой файл. Идентификатор открытого файла записывается в переменную hfDIBFile.

Далее файл читается в память, для чего вызывается функция DIBReadFile, определенная в файле dib.cpp. Функция читает весь файл в заказанный ей глобальный блок памяти, причем перед возвратом управления отмечает этот блок как перемещаемый и возвращает идентификатор блока памяти. Дополнительно она записывает размер файла в байтах в переменную, адрес которой указан ей в качестве второго параметра.



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

Затем для перерисовки окна и отображения загруженного изображения вызывается функция InvalidateRect.

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

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

Идентификаторы строк меню описаны в файле bmpihfo.hpp (листинг 4.7).

Листинг 4.7. Файл bmpinfo/bmpinfo.hpp

#define CM_HELPABOUT 301 #define CM_FILEOPEN 302 #define CM_FILEINFO 303 #define CM_FILEEXIT 304

Все функции, предназначенные для работы с bmp-файлами и изображениями DIB, загруженными в оперативную память, мы вынесли в отдельный файл dib.cpp (листинг4.8).

Листинг 4.8. Файл bmpinfo/dib.cpp

// ----------------------------------------------------- // Функции для работы с файлами в формате DIB // -----------------------------------------------------

#define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h> #pragma hdrstop

#include "dib.hpp"

// ------------------------------- // Функция DIBSelectFile // Выбор DIB-файла // -------------------------------

HFILE DIBSelectFile(void) { // Структура для выбора файла OPENFILENAME ofn;

// Буфер для записи пути к выбранному файлу char szFile[256];

// Буфер для записи имени выбранного файла char szFileTitle[256];

// Фильтр расширений имени файлов char szFilter[256] = "Bitmap Files\0*.bmp;*.dib;*.rle\0Any Files\0*.*\0";

// Идентификатор открываемого файла HFILE hf;

// Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0';



// Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME));

// Инициализируем нужные нам поля

// Размер структуры ofn.lStructSize = sizeof(OPENFILENAME);

// Идентификатор окна ofn.hwndOwner = NULL;

// Адрес строки фильтра ofn.lpstrFilter = szFilter;

// Номер позиции выбора ofn.nFilterIndex = 1;

// Адрес буфера для записи пути // выбранного файла ofn.lpstrFile = szFile;

// Размер буфера для записи пути // выбранного файла ofn.nMaxFile = sizeof(szFile);

// Адрес буфера для записи имени // выбранного файла ofn.lpstrFileTitle = szFileTitle;

// Размер буфера для записи имени // выбранного файла ofn.nMaxFileTitle = sizeof(szFileTitle);

// В качестве начального каталога для // поиска выбираем текущий каталог ofn.lpstrInitialDir = NULL;

// Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) {

// Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ);

// Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; }

// ------------------------------- // Функция DIBReadFile // Чтение DIB-файла // -------------------------------

HDIB DIBReadFile(HFILE hfDIBFile, DWORD *dwFileSize) { // Идентификатор глобального блока // памяти, который будет использован для // чтения файла HDIB hDib;

HCURSOR hCursor; // идентификатор курсора

// Указатель на глобальный блок памяти LPDIB lpBuf;

// Курсор в виде песочных часов hCursor = SetCursor (LoadCursor(NULL, IDC_WAIT));

// Определяем размер файла. Для этого // устанавливаем текущую позицию на // конец файла *dwFileSize = _llseek(hfDIBFile, 0l, 2);

// Устанавливаем текущую позицию // на начало файла _llseek(hfDIBFile, 0l, 0);

// Заказываем глобальный блок памяти, // размер которого равен длине файла hDib = (HDIB)GlobalAlloc(GMEM_FIXED, *dwFileSize); lpBuf = (unsigned char huge *)GlobalLock(hDib);



// Если мало свободной памяти, // возвращаем признак ошибки if(lpBuf == NULL) return(NULL);

// Читаем файл в полученный блок памяти _hread(hfDIBFile, lpBuf, *dwFileSize);

// Восстанавливаем курсор SetCursor (hCursor);

// Расфиксируем память GlobalUnlock(hDib);

// Закрываем файл _lclose(hfDIBFile);

return hDib; }

// ------------------------------- // Функция DIBInfo // Вывод диалоговой панели с информацией // о DIB-файле // -------------------------------

BOOL DIBInfo(HDIB hDib, DWORD dwFileSize) { char szBuf[256], szBuf1[256]; DWORD biSize; LPBITMAPFILEHEADER lpDIBFileHeader; LPBITMAPINFOHEADER lpDIBInfoHeader; LPBITMAPCOREHEADER lpDIBCoreHeader; DWORD bfOffset; BOOL bWDIB; LPDIB lpDIBPtr; int nDIBType; WORD wNumColors;

// Определяем тип битового изображения nDIBType = DIBType(hDib);

if(!nDIBType) // если ошибка, выдаем сообщение { MessageBox(NULL, "Ошибка в формате DIB-файла", "Bitmap Info", MB_OK | MB_ICONHAND); return FALSE; }

// Фиксируем область памяти, в которую загружен DIB lpDIBPtr = (unsigned char huge *)GlobalLock(hDib); if(lpDIBPtr == NULL) return(FALSE);

lpDIBFileHeader = (LPBITMAPFILEHEADER)lpDIBPtr;

// Определяем смещение бит изображения // и размер заголовка bfOffset = lpDIBFileHeader->bfOffBits; biSize = (DWORD)(lpDIBPtr[sizeof(BITMAPFILEHEADER)]);

// Готовим текстовую строку для вывода wsprintf(szBuf, "Размер заголовка, байт:\t%ld\n", biSize); wsprintf(szBuf1, "Размер файла, байт: \t%ld\n", dwFileSize); lstrcat(szBuf, szBuf1); wsprintf(szBuf1, "Смещение изображения, байт:\t%ld\n", bfOffset); lstrcat(szBuf, szBuf1);

// В зависимости от формата DIB (PM или Windows) // выводим различную информацию из заголовка файла if((nDIBType == WINRGB_DIB) (nDIBType == WINRLE4_DIB) (nDIBType == WINRLE8_DIB)) { wsprintf(szBuf1, "\nBMP для Windows\n", biSize); lstrcat(szBuf, szBuf1); lpDIBInfoHeader = (LPBITMAPINFOHEADER)(lpDIBPtr + sizeof(BITMAPFILEHEADER));

wsprintf(szBuf1, "Размер:\t%ldx%ld\n", lpDIBInfoHeader->biWidth, lpDIBInfoHeader->biHeight); lstrcat(szBuf, szBuf1);



wsprintf(szBuf1, "Бит на пиксел:\t%d\n", lpDIBInfoHeader->biBitCount); lstrcat(szBuf, szBuf1);

wNumColors = DIBNumColors(lpDIBPtr); wsprintf(szBuf1, "Таблица цветов:\t%d\n", wNumColors); lstrcat(szBuf, szBuf1);

if(lpDIBInfoHeader->biCompression == BI_RGB) { lstrcat(szBuf, "Без компрессии\n"); } else if(lpDIBInfoHeader->biCompression == BI_RLE4) { lstrcat(szBuf, "Компрессия RLE4\n"); } else if(lpDIBInfoHeader->biCompression == BI_RLE8) { lstrcat(szBuf, "Компрессия RLE8\n"); } }

// Для файлов DIB в формате PM else { wsprintf(szBuf1, "\nBMP для Presentation Manager\n", biSize); lstrcat(szBuf, szBuf1); lpDIBCoreHeader = (LPBITMAPCOREHEADER)(lpDIBPtr + sizeof(BITMAPFILEHEADER));

wsprintf(szBuf1, "Размер:\t%dx%d\n", lpDIBCoreHeader->bcWidth, lpDIBCoreHeader->bcHeight); lstrcat(szBuf, szBuf1);

wsprintf(szBuf1, "Бит на пиксел:\t%d\n", lpDIBCoreHeader->bcBitCount); lstrcat(szBuf, szBuf1); }

MessageBox(NULL, (LPSTR)szBuf, "Bitmap Info", MB_OK | MB_ICONINFORMATION);

GlobalUnlock(hDib); return TRUE; }

// ------------------------------- // Функция DIBType // Определение и проверка формата DIB // -------------------------------

int DIBType(HDIB hDib) { LPBITMAPFILEHEADER lpDIBFileHeader; LPBITMAPINFOHEADER lpih; LPBITMAPCOREHEADER lpch;

DWORD biSize; LPDIB hDIBPtr; int nDIBType;

if(hDib == NULL) // Неправильный идентификатор DIB return(-2);

// Фиксируем память, в которой находится DIB hDIBPtr = (LPDIB)GlobalLock(hDib); if(hDIBPtr == NULL) return(-1);

lpDIBFileHeader = (LPBITMAPFILEHEADER)hDIBPtr;

// Проверяем тип файла if(lpDIBFileHeader->bfType != 0x4d42) { GlobalUnlock(hDib); return 0; }

// Проверяем размер заголовка biSize = (DWORD)(hDIBPtr[sizeof(BITMAPFILEHEADER)]);

if(biSize == sizeof(BITMAPINFOHEADER)) // 40 байт { // Это заголовок DIB в формате Windows lpih = (LPBITMAPINFOHEADER)(hDIBPtr + sizeof(BITMAPFILEHEADER));

// Проверяем основные поля заголовка DIB if((lpih->biPlanes == 1) && ((lpih->biBitCount == 1) (lpih->biBitCount == 4) (lpih->biBitCount == 8) (lpih->biBitCount == 24)) && ((lpih->biCompression == BI_RGB) (lpih->biCompression == BI_RLE4 && lpih->biBitCount == 4) (lpih->biCompression == BI_RLE8 && lpih->biBitCount == 8))) { // Определяем метод компрессии файла if(lpih->biCompression == BI_RGB) nDIBType = WINRGB_DIB; else if(lpih->biCompression == BI_RLE4) nDIBType = WINRLE4_DIB; else if(lpih->biCompression == BI_RLE8) nDIBType = WINRLE8_DIB; else nDIBType = 0; } else nDIBType = 0; }



else if(biSize == sizeof(BITMAPCOREHEADER)) // 12 байт { // Это заголовок DIB в формате Presentation Manager lpch = (LPBITMAPCOREHEADER)(hDIBPtr + sizeof(BITMAPFILEHEADER));

// Проверяем основные поля заголовка DIB if((lpch->bcPlanes == 1) && (lpch->bcBitCount == 1 lpch->bcBitCount == 4 lpch->bcBitCount == 8 lpch->bcBitCount == 24)) { nDIBType = PM_DIB; } else nDIBType = 0; }

else nDIBType = 0;

GlobalUnlock(hDib);

// Возвращаем тип файла или признак ошибки return nDIBType; }

// ------------------------------- // Функция DIBNumColors // Определение размера палитры // -------------------------------

WORD DIBNumColors(LPDIB lpDib) { DWORD dwColorUsed; LPBITMAPINFOHEADER lpih;

lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));

// Количество цветов dwColorUsed = lpih->biClrUsed;

// Если используется палитра уменьшенного размера, // возвращаем нужный размер if(dwColorUsed) return((WORD)dwColorUsed);

// Если количество использованных цветов не указано, // вычисляем стандартный размер палитры исходя из // количества бит, определяющих цвет пиксела switch(lpih->biBitCount) { case 1: return 2; case 4: return 16; case 8: return 256; default: return 0; // палитра не используется } }

// ------------------------------- // Функция DIBHeight // Определение высоты DIB в пикселах // -------------------------------

WORD DIBHeight(LPDIB lpDib) { LPBITMAPINFOHEADER lpih;

lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); return lpih->biHeight; }

// ------------------------------- // Функция DIBWidth // Определение ширины DIB в пикселах // -------------------------------

WORD DIBWidth(LPDIB lpDib) { LPBITMAPINFOHEADER lpih;

lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); return lpih->biWidth; }

// ------------------------------- // Функция DIBFindBits // Определение адреса массива бит изображения // -------------------------------

LPSTR DIBFindBits(LPDIB lpDib) { LPBITMAPFILEHEADER lpfh; LPBITMAPINFOHEADER lpih;



lpfh = (LPBITMAPFILEHEADER)lpDib;

// Используем значение, указанное в заголовке // файла ( если оно не равно нулю) if(lpfh->bfOffBits) return((LPSTR)lpfh + lpfh->bfOffBits);

// Вычисляем адрес исходя из размеров заголовков и // таблицы цветов lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));

return((LPSTR)lpih + lpih->biSize + (DWORD)(DIBNumColors(lpDib) * sizeof(RGBQUAD))); }

// ------------------------------- // Функция DIBPaint // Рисование DIB при помощи функции StretchDIBits // -------------------------------

BOOL DIBPaint(HDC hdc, int x, int y, HDIB hDib) { HBITMAP hbmp; HDC hMemDC; WORD wHeight, wWidth; LPDIB lpDib; LPBITMAPINFOHEADER lpih;

lpDib = (LPDIB)GlobalLock(hDib); if(lpDib == NULL) return(-1);

lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));

// Определяем размеры DIB wHeight = lpih->biHeight; wWidth = lpih->biWidth;

// Рисуем DIB без масштабирования StretchDIBits(hdc, x, y, wWidth, wHeight, 0, 0, wWidth, wHeight, DIBFindBits(lpDib), (LPBITMAPINFO)lpih, DIB_RGB_COLORS, SRCCOPY);

GlobalUnlock(hDib);

return TRUE; }

// ------------------------------- // Функция DIBPaintBlt // Рисование DIB при помощи функции BitBlt // -------------------------------

BOOL DIBPaintBlt(HDC hdc, int x, int y, HDIB hDib) { HBITMAP hbmp; HDC hMemDC; WORD wHeight, wWidth; LPDIB lpDib; LPBITMAPINFOHEADER lpih;

lpDib = (LPDIB)GlobalLock(hDib); if(lpDib == NULL) return(-1);

lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));

wHeight = lpih->biHeight; wWidth = lpih->biWidth;

// Создаем совместимое битовое изображение hbmp = CreateCompatibleBitmap(hdc, wWidth, wHeight);

// Создаем совместимый контекст памяти hMemDC = CreateCompatibleDC(hdc);

// Преобразуем DIB в DDB SetDIBits(hdc, hbmp, 0, wHeight, DIBFindBits(lpDib), (LPBITMAPINFO)lpih, DIB_RGB_COLORS);

// Выбираем DDB в контекст отображения hbmp = (HBITMAP)SelectObject(hMemDC, hbmp);

// Рисуем DDB BitBlt(hdc, x, y, wWidth, wHeight, hMemDC, 0, 0, SRCCOPY);



// Удаляем контекст памяти DeleteObject(SelectObject(hMemDC, hbmp)); DeleteDC(hMemDC);

GlobalUnlock(hDib); return TRUE; }

// ------------------------------- // Функция DIBCreatePalette // Создаем палитру на базе таблицы цветов DIB // -------------------------------

HPALETTE DIBCreatePalette(HDIB hDib) { LPLOGPALETTE lpPal; HPALETTE hPal = NULL; HANDLE hLogPal; int i, wNumColors; LPSTR lpbi; LPBITMAPINFO lpbmi;

if (!hDib) return NULL;

lpbi = (LPSTR)GlobalLock(hDib); lpbmi = (LPBITMAPINFO)(lpbi + sizeof(BITMAPFILEHEADER));

// Определяем размер таблицы цветов wNumColors = DIBNumColors(lpbi);

// Если в DIB есть таблица цветов, создаем палитру if (wNumColors) { // Заказываем память для палитры hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * wNumColors);

if (!hLogPal) { GlobalUnlock(hDib); return NULL; }

// Получаем указатель на палитру lpPal = (LPLOGPALETTE)GlobalLock(hLogPal);

// Заполняем заголовок lpPal->palVersion = 0x300; lpPal->palNumEntries = wNumColors;

// Заполняем палитру for (i = 0; i < wNumColors; i++) { lpPal->palPalEntry[i].peRed = lpbmi->bmiColors[i].rgbRed;

lpPal->palPalEntry[i].peGreen = lpbmi->bmiColors[i].rgbGreen;

lpPal->palPalEntry[i].peBlue = lpbmi->bmiColors[i].rgbBlue;

lpPal->palPalEntry[i].peFlags = 0; }

// Создаем палитру hPal = CreatePalette(lpPal); if (!hPal) { GlobalUnlock(hLogPal); GlobalFree(hLogPal); return NULL; }

GlobalUnlock(hLogPal); GlobalFree(hLogPal); }

GlobalUnlock(hDib);

// Возвращаем идентификатор созданной палитры return hPal; }

Опишем функции, определенные в файле dib.cpp.


Приложение BMPLOGO


Приведем пример приложения BMPLOGO, которое демонстрирует работу с битовыми изображениями в формате DDB. Это приложение создает одно битовое изображение в памяти и рисует его в верхнем левом углу окна, а также (что на наш взгляд, самое интересное), рисует текстовую строку с оттенением (рис. 4.3).

Рис. 4.3. Приложение BMPLOGO

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

Исходный текст главного модуля приложения представлен в листинге 4.1.

Листинг 4.1. Файл bmplogo/bmplogo.cpp

// ---------------------------------------- // Приложение BMPLOGO // Демонстрация различных способов рисования // битовых изображений DDB // ----------------------------------------

#define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h>

// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); void DrawBitmapRop(HDC hDC, int x, int y, HBITMAP hBitmap, DWORD dwRop);

// Имя класса окна char const szClassName[] = "BmpLogoClass";

// Заголовок окна char const szWindowTitle[] = "Bitmap Logo";

// Размеры внутренней области окна short cxClient, cyClient;

// Идентификатор копии приложения HINSTANCE hInst;

// Битовое изображение BYTE bBytes[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

// Структура, описывающая битовое изображение BITMAP bmp = { 0, 64, 9, 8, 1, 1, NULL };


// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;

// Сохраняем идентификатор приложения hInst = hInstance;

// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;

// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));

// Устанавливаем системный цвет для фона окна wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);

wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = (LPSTR)szClassName;

// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================



LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;

switch (msg) { case WM_CREATE: { return 0; }

// При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }

// Рисование в окне case WM_PAINT: { HBITMAP bmLogo1, bmLogo2;

// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);

// Загружаем изображение из ресурсов приложения bmLogo1 = LoadBitmap(hInst, "Logo1");

// Выводим изображение два раза со смещением, // используя разные коды растровых операций. // Это дает эффект тени DrawBitmapRop(hdc, 20, 20, bmLogo1, SRCAND); DrawBitmapRop(hdc, 15, 15, bmLogo1, MERGEPAINT);

// Завершаем формирование структуры bmp bmp.bmBits = (LPSTR)bBytes;

// Создаем битовое изображение из массива // данных, расположенных в памяти bmLogo2 = CreateBitmapIndirect(&bmp);

// Рисуем это изображение DrawBitmapRop(hdc, 0, 0, bmLogo2, SRCCOPY);

// Удаляем изображения DeleteBitmap(bmLogo1); DeleteBitmap(bmLogo2);

// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }

case WM_DESTROY: { PostQuitMessage(0); return 0; }

default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

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

Изображение слова "Bitmap" находится в ресурсах и имеет идентификатор Logo1. Для загрузки его в памяти вызывается функция LoadBitmap:

bmLogo1 = LoadBitmap(hInst, "Logo1");

Далее изображение выводится в первый раз, при этом используется растровая операция SRCAND:

DrawBitmapRop(hdc, 20, 20, bmLogo1, SRCAND);

Затем то же самое изображение выводится еще раз, но с небольшим смещением и с использованием другой растровой операции:

DrawBitmapRop(hdc, 15, 15, bmLogo1, MERGEPAINT);

Функция DrawBitmapRop аналогична описанной нами ранее функции DrawBitmap, однако она имеет дополнительный параметр, позволяющий выбрать растровую операцию.


Мы привели исходный текст этой функции в листинге 4.2.

Затем обработчик сообщения WM_PAINT создает в памяти и выводит на экран еще одно монохромное битовое изображение:

bmp.bmBits = (LPSTR)bBytes; bmLogo2 = CreateBitmapIndirect(&bmp); DrawBitmapRop(hdc, 0, 0, bmLogo2, SRCCOPY);

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

Перед возвратом управления обработчик удаляет оба созданных им битовых изображения:

DeleteBitmap(bmLogo1); DeleteBitmap(bmLogo2);

Листинг 4.2. Файл bmplogo/drawbmp.cpp

// ---------------------------------------- // Рисование изображения типа bitmap // с использованием различных растровых операций // ----------------------------------------

#define STRICT #include <windows.h>

void DrawBitmapRop(HDC hDC, int x, int y, HBITMAP hBitmap, DWORD dwRop) { HBITMAP hbm, hOldbm; HDC hMemDC; BITMAP bm; POINT ptSize, ptOrg;

// Создаем контекст памяти, совместимый // с контекстом отображения hMemDC = CreateCompatibleDC(hDC);

// Выбираем изображение bitmap в контекст памяти hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);

// Если не было ошибок, продолжаем работу if (hOldbm) { // Для контекста памяти устанавливаем тот же // режим отображения, что используется в // контексте отображения SetMapMode(hMemDC, GetMapMode(hDC));

// Определяем размеры изображения GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);

ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота

// Преобразуем координаты устройства в логические // для устройства вывода DPtoLP(hDC, &ptSize, 1);

ptOrg.x = 0; ptOrg.y = 0;

// Преобразуем координаты устройства в логические // для контекста памяти DPtoLP(hMemDC, &ptOrg, 1);

// Рисуем изображение bitmap BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, dwRop);

// Восстанавливаем контекст памяти SelectObject(hMemDC, hOldbm); }

// Удаляем контекст памяти DeleteDC(hMemDC); }

Файл описания ресурсов приложения (листинг 4.3) содержит только одну строку, которая ссылается на файл битового изображения logo1.bmp.

Листинг 4.3. Файл bmplogo/bmplogo.rc

Logo1 BITMAP logo1.bmp

В листинге 4.4 показано битовое изображение, содержащее слово "Bitmap".

Листинг 4.4. Файл bmplogo/logo1.bmp



Файл определения модуля для приложения BMPLOGO приведен в листинге 4.5.

Листинг 4.5. Файл bmplogo/bmplogo.def

; ============================= ; Файл определения модуля ; ============================= NAME BMPLOGO DESCRIPTION 'Приложение BMPLOGO, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение DASHLINE


Приложение DASHLINE демонстрирует использование функции LineDDA для рисования пунктирных линий увеличенной толщины (рис. 2.28). Напомним, что вы не можете создать перо для рисования таких линий обычными средствами.

Рис. 2.28. Пунктирные линии увеличенной толщины

Основной файл исходного текста приложения приведен в листинге 2.8.

Листинг 2.8. Файл dashline/dashline.cpp

// ---------------------------------------- // Приложение DASHLINE // Демонстрация использования функции LineDDA // ----------------------------------------

#define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h>

// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

void CALLBACK _export LineProc(int xPos, int yPos, LPSTR lphdc);

// Имя класса окна char const szClassName[] = "DashLineClass";

// Заголовок окна char const szWindowTitle[] = "Dash Line";

// Идентификатор копии приложения HINSTANCE hInst;

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;

// Сохраняем идентификатор копии приложения hInst = hInstance;

// Создаем главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;

// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }


// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));

wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;

// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static FARPROC lpfnLineProc; HPEN hpen, hpenOldPen;

switch (msg) { case WM_CREATE: { // Создаем переходник для функции LineProc lpfnLineProc = MakeProcInstance((FARPROC)LineProc, hInst); return 0; }

// Рисование в окне case WM_PAINT: { RECT rc;

// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);

// Создаем перо толщиной 3 пиксела и выбираем // его в контекст отображения hpen = CreatePen(PS_SOLID, 3, RGB(0, 0, 0)); hpenOldPen = SelectPen(hdc, hpen);

// Рисуем несколько штриховых линий, // используя выбранное перо

LineDDA(50, 50, 300, 50, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);

LineDDA(50, 50, 300, 100, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);

LineDDA(50, 50, 50, 100, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);

LineDDA(50, 100, 300, 100, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);

LineDDA(300, 50, 300, 100, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);

// Выбираем старое перо и удаляем созданное SelectPen(hdc, hpenOldPen); DeletePen(hpen);



// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }

case WM_DESTROY: { // Освобождаем переходник функции LineProc FreeProcInstance(lpfnLineProc);

PostQuitMessage(0); return 0; }

default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

// -------------------------------------------------- // Функция LineProc вызывается для каждой точки линии // -------------------------------------------------- void CALLBACK _export LineProc(int xPos, int yPos, LPSTR lphdc) { // Счетчик точек static short cSpaces = 1;

// Для каждой первой точки устанавливаем текущую // позицию пера if(cSpaces == 1) { MoveToEx(*(HDC FAR*) lphdc, xPos, yPos, NULL); cSpaces++; }

// Для каждой десятой точки рисуем линию else if(cSpaces == 10) { LineTo(*(HDC FAR*) lphdc, xPos, yPos); cSpaces++; }

// Для каждой двадцатой точки устанавливаем // текущую позицию пера и сбрасываем счетчик else if(cSpaces == 20) { MoveToEx(*(HDC FAR*) lphdc, xPos, yPos, NULL); cSpaces = 1; } else cSpaces++; }

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

lpfnLineProc = MakeProcInstance ((FARPROC)LineProc, hInst);

Рисование линий выполняется обработчиком сообщения WM_PAINT.

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

hpen = CreatePen(PS_SOLID, 3, RGB(0, 0, 0)); hpenOldPen = SelectPen(hdc, hpen);

Далее приложение рисует несколько пунктирных линий, вызывая функцию LineDDA:

LineDDA(50, 50, 300, 50, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);

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

В функции рисования есть статический счетчик cSpaces, с помощью которого организуется цикл рисования штриховых линий. Первоначальное значение этого счетчика равно 1.

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

Файл определения модуля приложения DASHLINE приведен в листинге 2.9.

Листинг 2.9. Файл dashline/dashline.def

; ============================= ; Файл определения модуля ; ============================= NAME DASHLINE DESCRIPTION 'Приложение DASHLINE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение FONTVIEW


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

Меню "Font" приложения FONTVIEW позволяет вам выбрать шрифт двумя способами - вы можете указать семейство шрифта или выбрать конкретный шрифт при помощи диалоговой панели "Font".

Меню "Orientation" позволяет задать угол поворота текстовой строки, выводя ее с наклоном (рис. 5.3) или даже перевернутой "вверх ногами" (рис. 5.4).

Рис. 5.3. Вывод текста с наклоном

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

Рис. 5.4. Вывод перевернутого текста

Исходный текст приложения приведен в листинге 5.1.

Листинг 5.1. Файл fontview/fontview.cpp

// ---------------------------------------- // Приложение FONTVIEW // Просмотр шрифтов // ----------------------------------------

#define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h> #pragma hdrstop

#include "fontview.hpp"

// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL GetFont(HWND hWnd, LOGFONT *lf, CHOOSEFONT *cf);

// Имя класса окна char const szClassName[] = "FontViewClass";

// Заголовок окна char const szWindowTitle[] = "Font Viewer";

// Размеры внутренней области окна short cxClient, cyClient;

// Идентификатор копии приложения HINSTANCE hInst;

// Строка для вывода char szChars[] = ": AaBbCcDdEeFfGg АаБбВвГгДдЕе"; char szBuf[256];

// Угол наклона строки при выводе int nOrientation = 0;

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;


hInst = hInstance;

// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;

// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна

// Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));

// Подключаем меню wc.lpszMenuName = "APP_MENU";

wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;

// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static CHOOSEFONT cf; static LOGFONT lf; static HFONT hfont, hfOldFont;;

switch (msg) { // При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }

// Рисование в окне case WM_PAINT: { // Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);



// Устанавливаем угол наклона строки lf.lfOrientation = lf.lfEscapement = nOrientation;

// Создаем шрифт на базе заполненной // структуры LOGFONT hfont = CreateFontIndirect(&lf);

if(hfont) { // Выбираем шрифт в контекст отображения hfOldFont = SelectFont(hdc, hfont);

// Определяем название шрифта GetTextFace(hdc, 80, szBuf);

// Добавляем к нему текстовую строку lstrcat(szBuf, szChars);

// Устанавливаем цвет текста SetTextColor(hdc, cf.rgbColors);

// Выводим текст, пользуясь выбранным шрифтом TextOut(hdc, cxClient/2, cyClient/2, szBuf, lstrlen(szBuf));

// Выбираем старый шрифт SelectFont(hdc, hfOldFont);

// Удаляем созданный нами шрифт DeleteFont(hfont); }

// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }

// Обработка сообщений от меню case WM_COMMAND: { switch (wParam) { // Выбор шрифта при помощи диалоговой панели case CM_FONTSEL: { // Записываем во все поля структуры типа // LOGFONT нулевые значения memset(&lf, 0, sizeof(LOGFONT));

// Выбираем шрифт if(GetFont(hwnd, &lf, &cf)) { // Перерисовываем окно InvalidateRect(hwnd, NULL, TRUE); } return 0; }

// Выбираем шрифт, указывая семейство case CM_FDECOR: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_DECORATIVE; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FMODERN: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_MODERN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FROMAN: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_ROMAN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FSCRIPT: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_SCRIPT; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FSWISS: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_SWISS; InvalidateRect(hwnd, NULL, TRUE); return 0; }

// Выбираем угол поворота строки case CM_FONT00: { nOrientation = 0; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT30: { nOrientation = 300; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT45: { nOrientation = 450; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT90: { nOrientation = 900; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT180: { nOrientation = 1800; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT270: { nOrientation = 2700; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT360: { nOrientation = 3600; InvalidateRect(hwnd, NULL, TRUE); return 0; }



case CM_HELPABOUT: { MessageBox(hwnd, "Font Viewer, v.1.0\n" "(C) Frolov A.V., 1994", "About FONTVIEW", MB_OK | MB_ICONINFORMATION); return 0; }

// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; }

default: return 0; } }

case WM_DESTROY: { PostQuitMessage(0); return 0; }

default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

// ===================================== // Функция GetFont // =====================================

BOOL GetFont(HWND hWnd, LOGFONT *lf, CHOOSEFONT *cf) { LPSTR szFontStyle[LF_FACESIZE];

// Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора шрифта memset(cf, 0, sizeof(CHOOSEFONT));

// Размер структуры cf->lStructSize = sizeof(CHOOSEFONT);

// Идентификатор окна cf->hwndOwner = hWnd;

// Указатель на структуру LOGFONT cf->lpLogFont = lf;

// Флаги, определяющие внешний вид диалоговой панели cf->Flags = CF_SCREENFONTS | CF_USESTYLE | CF_EFFECTS;

// Дополнительные данные cf->lCustData = 0L;

// Цвет текста cf->rgbColors = RGB(0,0,0);

// Адрес функции фильтра cf->lpfnHook = (FARPROC)NULL;

// Адрес шаблона диалоговой панели cf->lpTemplateName = (LPSTR)NULL;

// Идентификатор копии приложения cf->hInstance = hInst;

// Стиль шрифта cf->lpszStyle = (LPSTR)szFontStyle;

// Тип шрифта cf->nFontType = SCREEN_FONTTYPE;

// Ограничения на минимальный и максимальный // размер шрифта cf->nSizeMin = 0; cf->nSizeMax = 0;

// Вызываем функцию выбора шрифта return ChooseFont(cf); }

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

Обработчик сообщения WM_PAINT пользуется структурой lf типа LOGFONT, подготовленной при выборе шрифта.

Перед созданием шрифта обработчик устанавливает нужный угол наклона строки:

lf.lfOrientation = lf.lfEscapement = nOrientation;



Затем создается шрифт:

hfont = CreateFontIndirect(&lf);

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

hfOldFont = SelectFont(hdc, hfont);

Идентификатор шрифта, который был выбран в контекст отображения раньше, сохраняется в переменной hfOldFont.

Затем обработчик вызывает функцию GetTextFace, которая копирует в буфер szBuf текстовую строку с названием шрифта, выбранного в контекст отображения. Эта строка затем дописывается ко строке szChars и выводится на экран.

Перед выводом устанавливается цвет текста, который берется из заполненной на этапе выбора шрифта структуры CHOOSEFONT:

SetTextColor(hdc, cf.rgbColors);

Для вывода текста мы используем функцию TextOut, которая была подробно описана в 11 томе "Библиотеки системного программиста".

Перед возвратом управления обработчик сообщения WM_PAINT выбирает в контекст отображения старый шрифт и удаляет созданный шрифт:

SelectFont(hdc, hfOldFont); DeleteFont(hfont);

Когда вы выберите строку "Fonts..." из меню "Font", получит управление обработчик сообщения WM_COMMAND. Он запишет во все поля структуры lf типа LOGFONT нулевые значения и вызовет функцию GetFont, определенную в нашем приложении. После этого он вызовет функцию InvalidateRect для перерисовки окна приложения.

Функция GetFont инициализирует нулевыми значениями структуру cf типа CHOOSEFONT, а затем заполняет в этой структуре нужные поля и вызывает функцию ChooseFont:

memset(cf, 0, sizeof(CHOOSEFONT)); cf->lStructSize = sizeof(CHOOSEFONT); cf->hwndOwner = hWnd; cf->lpLogFont = lf; cf->Flags = CF_SCREENFONTS | CF_USESTYLE | CF_EFFECTS; cf->lCustData = 0L; cf->rgbColors = RGB(0,0,0); cf->lpfnHook = (FARPROC)NULL; cf->lpTemplateName = (LPSTR)NULL; cf->hInstance = hInst; cf->lpszStyle = (LPSTR)szFontStyle; cf->nFontType = SCREEN_FONTTYPE; cf->nSizeMin = 0; cf->nSizeMax = 0; return ChooseFont(cf);

Если вы выбираете из меню "Font" одно из семейств шрифтов, структура lf инициализируется нулевыми значениями, а затем в ней устанавливается поле lfPitchAndFamily:



memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_DECORATIVE;

Затем вызывается функция InvalidateRect, что приводит к перерисовке окна приложения.

Установка угла наклона выполняется достаточно просто и заключается в изменении значения переменной nOrientation с последующей перерисовкой окна:

case CM_FONT30: { nOrientation = 300; InvalidateRect(hwnd, NULL, TRUE); return 0; }

Все константы, которые используются для работы с меню, описаны в файле fontview.hpp (листинг 5.2).

Листинг 5.2. Файл fontview/fontview.hpp

#define CM_HELPABOUT 301 #define CM_FONTSEL 302 #define CM_FILEEXIT 303

#define CM_FONT30 304 #define CM_FONT45 305 #define CM_FONT90 306 #define CM_FONT180 307 #define CM_FONT270 308 #define CM_FONT360 309 #define CM_FONT00 310

#define CM_FDECOR 311 #define CM_FMODERN 312 #define CM_FROMAN 313 #define CM_FSCRIPT 314 #define CM_FSWISS 315

Меню определено в файле ресурсов приложения (листинг 5.3).

Листинг 5.3. Файл fontview/fontview.rc

#include "fontview.hpp"

APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END

POPUP "F&ont" BEGIN MENUITEM "FF_DECORATIVE", CM_FDECOR MENUITEM "FF_MODERN", CM_FMODERN MENUITEM "FF_ROMAN", CM_FROMAN MENUITEM "FF_SCRIPT", CM_FSCRIPT MENUITEM "FF_SWISS", CM_FSWISS MENUITEM SEPARATOR MENUITEM "&Select Font...",CM_FONTSEL END

POPUP "&Orientation" BEGIN MENUITEM "0", CM_FONT00 MENUITEM "30",CM_FONT30 MENUITEM "45",CM_FONT45 MENUITEM "90",CM_FONT90 MENUITEM "180",CM_FONT180 MENUITEM "270",CM_FONT270 MENUITEM "360",CM_FONT360 END

POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END

Файл определения модуля приложения FONTVIEW приведен в листинге 5.4.

Листинг 5.4. Файл fontview/fontview.def

; ============================= ; Файл определения модуля ; ============================= NAME FONTVIEW DESCRIPTION 'Приложение FONTVIEW, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение GETCOLOR


Приложение GETCOLOR демонстрирует использование системных цветов и только что описанной функции ChooseColor. Оно также обрабатывает сообщение WM_SYSCOLORCHANGE.

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

Рис. 3.5. Главное окно приложения GETCOLOR

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

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

Так как функция ChooseColor не поддерживает работу с цветовыми палитрами, в режиме со средним цветовым разрешением (256 цветов) вам будет доступен тот же самый набор цветов, что и в режиме с низким цветовым разрешением.

Если у вас есть такая возможность, запустите приложение GETCOLOR в режиме высокого цветового разрешения True Color. Убедитесь, что в этом случае вы можете выбрать для пера любой цвет. Все цвета будут чистыми, так как в этом режиме смешанные цвета не используются.

Исходный текст приложения приведен в листинге 3.1.

Листинг 3.1. Файл getcolor/getcolor.cpp

// ---------------------------------------- // Приложение GETCOLOR // Выбор цвета, работа с системными цветами // ----------------------------------------

#define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h>

// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL GetPenColor(HWND, COLORREF *);


// Имя класса окна char const szClassName[] = "GetColorClass";

// Заголовок окна char const szWindowTitle[] = "Get Color";

// Идентификатор копии приложения HINSTANCE hInst;

// Идентификаторы кистей и перьев HBRUSH hbrush, hbrushOldBrush; HPEN hpen, hpenOldPen;

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;

// Сохраняем идентификатор копии приложения hInst = hInstance;

// Создаем главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;

// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));

// Устанавливаем системный цвет для фона окна wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);

wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = (LPSTR)szClassName;



// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================

LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;

switch (msg) { case WM_CREATE: { // Создаем кисть и перо для рисования прямоугольника hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); hpen = CreatePen(PS_SOLID, 10, RGB(0,0,0)); return 0; }

case WM_SYSCOLORCHANGE: { // Если кисть была создана, удаляем ее // и затем создаем заново if(hbrush) { DeleteBrush(hbrush); hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); } return 0; }

// Рисование в окне case WM_PAINT: { RECT rc;

// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);

// Выбираем новую кисть и новое перо hbrushOldBrush = SelectBrush(hdc, hbrush); hpenOldPen = SelectPen(hdc, hpen);

// Рисуем прямоугольник Rectangle(hdc, 10, 20, 300, 50);

// Выбираем старую кисть и старое перо SelectBrush(hdc, hbrushOldBrush); SelectPen(hdc, hpenOldPen);

// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }

case WM_LBUTTONDOWN: { COLORREF clrref;

// Выбираем новый цвет для пера if(GetPenColor(hwnd, &clrref)) { // Если выбрали новый цвет, удаляем перо, // а затем создаем новое DeletePen(hpen); hpen = CreatePen(PS_SOLID, 10, clrref);

// Перерисовываем окно InvalidateRect(hwnd, NULL, TRUE); } return 0; }

case WM_DESTROY: { // Удаляем созданные нами кисть и перо DeleteBrush(hbrush); DeletePen(hpen);

PostQuitMessage(0); return 0; }

default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

// --------------------------------------------------- // Функция GetPenColor // Выбор цвета пера // ---------------------------------------------------

BOOL GetPenColor(HWND hwnd, COLORREF *clrref) { CHOOSECOLOR cc; COLORREF aclrCust[16]; int i;

// Подготавливаем массив цветов // "Custom Colors" for (i = 0; i < 16; i++) aclrCust[i] = RGB(255, 255, 255);



// Записываем нулевые значения во все // неиспользуемые поля структуры CHOOSECOLOR memset(&cc, 0, sizeof(CHOOSECOLOR));

// Заполняем необходимые поля cc.lStructSize = sizeof(CHOOSECOLOR); cc.hwndOwner = hwnd; cc.rgbResult = RGB(0, 0, 0); cc.lpCustColors = aclrCust; cc.Flags = 0;

// Выбираем цвет и возвращаем результат if (ChooseColor(&cc)) { *clrref = cc.rgbResult; return TRUE; } else return FALSE; }

Обратите внимание, что в в файл исходного текста приложения включается файл commdlg.h:

#include <commdlg.h>

В этом файле описана функция ChoseColor, необходимые для нее константы и структура данных.

При регистрации класса окна для фона окна устанавливается системный цвет COLOR_APPWORKSPACE, соответствующей цвету окна MDI-приложений (примером такого приложения является Program Manager):

wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);

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

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

hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); hpen = CreatePen(PS_SOLID, 10, RGB(0,0,0));

Перед завершением работы приложения обработчик сообщения WM_DESTROY удаляет созданные перо и кисть:

DeleteBrush(hbrush); DeletePen(hpen);

Задача обработчика сообщения WM_PAINT заключается в том, чтобы выбрать в контекст отображения перо и кисть, нарисовать прямоугольник, а затем выбрать в контекст отображения старые перо и кисть:

hbrushOldBrush = SelectBrush(hdc, hbrush); hpenOldPen = SelectPen(hdc, hpen); Rectangle(hdc, 10, 20, 300, 50); SelectBrush(hdc, hbrushOldBrush); SelectPen(hdc, hpenOldPen);

Что происходит, когда вы с помощью приложения Control Panel изменяете системные цвета?

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



case WM_SYSCOLORCHANGE: { if(hbrush) { DeleteBrush(hbrush); hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); } return 0; }

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

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

Когда вы щелкаете левой клавишей мыши в окне приложения, обработчик сообщения WM_LBUTTONDOWN вызывает функцию GetPenColor, определенную в нашем приложении. Эта функция вызывает функцию ChooseColor, и, если вы выбрали с ее помощью цвет для пера, записывает этот цвет в переменную clrref. Затем обработчик сообщения от мыши удаляет существующее перо и создает новое, используя выбранный вами цвет. После этого он вызывает функцию InvalidateRect, что вызывает перерисовку окна приложения.

Функция GetPenColor подготавливает массив из 16 переменных типа COLORREF, содержащих белый цвет. Этот массив будет использован для инициализации цветов в области "Custom Colors" диалоговой панели выбора цветов.

Затем она инициализирует нужные поля структуры CHOOSECOLOR, записывая в остальные поля нулевые значения, после чего вызывает функцию ChooseColor.

Файл определения модуля для приложения GETCOLOR приведен в листинге 3.2.

Листинг 3.2. Файл getcolor/getcolor.def

; ============================= ; Файл определения модуля ; ============================= NAME GETCOLOR DESCRIPTION 'Приложение GETCOLOR, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение LINER


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

Рис. 2.23. Приложение LINER

Меню "Draw" предназначено для выбора фигур, отображаемых в окне приложения. Вы можете выбрать прямые линии (строка "Lines"), ломаную линию (строка "Polyline"), дугу эллипса ("Arc"), прямоугольники ("Rectangles"), многоугольники ("Polygon"), эллипс ("Ellipse"), окружность ("Circle"), сегмент эллипса ("Chord") или сектор эллипса ("Pie").

Запустите приложение LINER (готовый загрузочный модуль и исходные тексты приложения есть на дискете, которую можно купить вместе с книгой) и выберите из меню "Draw" строку "Rectangles". В главном окне будет нарисовано несколько прямоугольников, в том числе один прямоугольник будет нарисован по периметру внутренней области окна.

По умолчанию сразу после запуска приложения используется режим фона OPAQUE. Смените его на TRANSPARENT, выбрав соответствующую строку из меню "Background Mode". Режим фона изменится на прозрачный (рис. 2.24).

Рис. 2.24. Прозрачный режим фона

Посмотрите, как отражается изменение фона на рисовании других фигур, например, прямых линий (рис. 2.25).

Рис. 2.25. Рисование прямых линий различной толщины и стиля

Меню "ROP" позволит вам провести эксперименты с различными растровыми операциями.

Выбрав из меню "Draw" строку "Polygon", вы сможете посмотреть, как закрашиваются самопересекающиеся многоугольники в режимах ALTERNATE и WINDING (рис. 2.26).

Рис. 2.26. Режимы закрашивания многоугольников

Главный файл приложения LINER приведен в листинге 2.1.

Листинг 2.1. Файл liner/liner.cpp

// ---------------------------------------- // Приложение LINER // Демонстрация использования функций // рисования графических изображений // ----------------------------------------


#define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h> #pragma hdrstop

#include "liner.hpp"

// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); void DrawLines(HDC); void DrawPolyline(HDC); void DrawArc(HDC); void DrawRectangles(HDC); void DrawPolygon(HDC); void DrawEllipse(HDC); void DrawCircle(HDC); void DrawPie(HDC); void DrawChord(HDC);

// Имя класса окна char const szClassName[] = "LinerClass";

// Заголовок окна char const szWindowTitle[] = "Liner";

// Размеры внутренней области окна short cxClient, cyClient;

// Код выбранной строки меню "Draw" int nFigures = 0;

// Режим фона int nBkMode = OPAQUE;

// Код растровой операции int nROP2 = R2_COPYPEN;

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;

// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;

// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна



// Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));

// Подключаем меню wc.lpszMenuName = "APP_MENU";

wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH); wc.lpszClassName = (LPSTR)szClassName;

// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;

switch (msg) { // При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }

// Рисование в окне case WM_PAINT: { HBRUSH hbrush, hbrushOldBrush; int nCapHeight, nFrameHeight;

// Рисуем в области заголовка окна

// Получаем контекст отображения для // всего окна hdc = GetWindowDC(hwnd);

// Определяем высоту заголовка окна nCapHeight = GetSystemMetrics(SM_CYCAPTION);

// Определяем толщину рамки окна nFrameHeight = GetSystemMetrics(SM_CYFRAME);

// Создаем кисть зеленого цвета hbrush = CreateSolidBrush(RGB(0,0xff,0));

// Выбираем кисть в контекст отображения, сохраняя // идентификатор старой кисти hbrushOldBrush = SelectBrush(hdc, hbrush);

// Рисуем зеленый прямоугольник в левой части // заголовка окна Rectangle(hdc, 2*nCapHeight, nFrameHeight, 4*nCapHeight, nCapHeight);

// Создаем и выбираем зеленую кисть hbrush = CreateSolidBrush(RGB(0xff,0,0)); SelectBrush(hdc, hbrush);

// Вписываем в прямоугольник эллипс // красного цвета Ellipse(hdc, 2*nCapHeight, nFrameHeight, 4*nCapHeight, nCapHeight);

// Выбираем старую кисть SelectBrush(hdc, hbrushOldBrush);

// Освобождаем контекст отображения, // позволяющий рисовать во всем окне ReleaseDC(hwnd, hdc);



// Рисуем во внутренней области окна

// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);

// Устанавливаем метрическую систему // координат с началом координат в // левом нижнем углу внутренней области // окна SetMapMode(hdc, MM_LOMETRIC); SetViewportOrg(hdc, 0, cyClient);

// Устанавливаем режим отображения и // растровую операцию SetBkMode(hdc, nBkMode); SetROP2(hdc, nROP2);

// В зависимости от содержимого переменной // nFigures вызываем одну из функций рисования if(nFigures == CM_LINES) { DrawLines(hdc); // прямые линии } else if(nFigures == CM_ARC) { DrawArc(hdc); // дуга окружности } else if(nFigures == CM_RECT) { DrawRectangles(hdc); // прямоугольники } else if(nFigures == CM_POLYGON) { DrawPolygon(hdc); // многоугольники } else if(nFigures == CM_ELLIPSE) { DrawEllipse(hdc); // эллипс } else if(nFigures == CM_CIRCLE) { DrawCircle(hdc); // окружность } else if(nFigures == CM_PIE) { DrawPie(hdc); // сектор эллипса } else if(nFigures == CM_CHORD) { DrawChord(hdc); // сегмент эллипса } else if(nFigures == CM_POLYLINE) { DrawPolyline(hdc); // ломаная линия }

// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }

// Обработка сообщений от меню case WM_COMMAND: { switch (wParam) { case CM_HELPABOUT: { MessageBox(hwnd, "Liner, v.1.0\n" "Drawing Graphics Demo\n" "(C) Frolov A.V., 1994", "About Liner", MB_OK | MB_ICONINFORMATION); return 0; }

// Режим непрозрачного фона case CM_OPAQUE: { // Записываем код режима nBkMode = OPAQUE;

// Перерисовываем окно приложения InvalidateRect(hwnd, NULL, TRUE); return 0; }

// Режим прозрачного фона case CM_TRANSPARENT: { nBkMode = TRANSPARENT; InvalidateRect(hwnd, NULL, TRUE); return 0; }

// Выбор растровой операции case CM_R2_BLACK: { // Записываем код растровой операции nROP2 = R2_BLACK;

// Перерисовываем окно приложения InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOTMERGEPEN: { nROP2 = R2_NOTMERGEPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MASKNOTPEN: { nROP2 = R2_MASKNOTPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOTCOPYPEN: { nROP2 = R2_NOTCOPYPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MASKPENNOT: { nROP2 = R2_MASKPENNOT; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOT: { nROP2 = R2_NOT; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_XORPEN: { nROP2 = R2_XORPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOTMASKPEN: { nROP2 = R2_NOTMASKPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MASKPEN: { nROP2 = R2_MASKPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOTXORPEN: { nROP2 = R2_NOTXORPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOP: { nROP2 = R2_NOP; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MERGENOTPEN: { nROP2 = R2_MERGENOTPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_COPYPEN: { nROP2 = R2_COPYPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MERGEPENNOT: { nROP2 = R2_MERGEPENNOT; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MERGEPEN: { nROP2 = R2_MERGEPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_WHITE: { nROP2 = R2_WHITE; InvalidateRect(hwnd, NULL, TRUE); return 0; }



// Выбор из меню "Draw" case CM_LINES: { nFigures = CM_LINES; InvalidateRect(hwnd, NULL, TRUE); return 0; }

case CM_POLYLINE: { nFigures = CM_POLYLINE; InvalidateRect(hwnd, NULL, TRUE); return 0; }

case CM_ARC: { nFigures = CM_ARC; InvalidateRect(hwnd, NULL, TRUE); return 0; }

case CM_RECT: { nFigures = CM_RECT; InvalidateRect(hwnd, NULL, TRUE); return 0; }

case CM_POLYGON: { nFigures = CM_POLYGON; InvalidateRect(hwnd, NULL, TRUE); return 0; }

case CM_ELLIPSE: { nFigures = CM_ELLIPSE; InvalidateRect(hwnd, NULL, TRUE); return 0; }

case CM_CIRCLE: { nFigures = CM_CIRCLE; InvalidateRect(hwnd, NULL, TRUE); return 0; }

case CM_PIE: { nFigures = CM_PIE; InvalidateRect(hwnd, NULL, TRUE); return 0; }

case CM_CHORD: { nFigures = CM_CHORD; InvalidateRect(hwnd, NULL, TRUE); return 0; }

// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; }

default: return 0; } }

case WM_DESTROY: { PostQuitMessage(0); return 0; }

default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

Исходные тексты обильно снабжены комментариями, поэтому мы сделаем только самые необходимые пояснения.

При регистрации класса окна указываются стили CS_HREDRAW и CS_VREDRAW, в результате чего окно перерисовывается при изменении его высоты или ширины:

wc.style = CS_HREDRAW | CS_VREDRAW;

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

Обработчик сообщения WM_PAINT два раза получает контекст отображения. Вначале он получает контекст отображения, позволяющий рисовать во всем окне приложения:

hdc = GetWindowDC(hwnd);

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

После этого указанный контекст отображения освобождается:

ReleaseDC(hwnd, hdc);

Далее обработчик сообщения WM_PAINT получает общий контекст отображения, связанный с внутренней областью окна, вызывая функцию BeginPaint.


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

SetMapMode(hdc, MM_LOMETRIC); SetViewportOrg(hdc, 0, cyClient);

Далее в соответствии с содержимым глобальной переменной nBkMode устанавливается режим фона:

SetBkMode(hdc, nBkMode);

Первоначально в этой переменной находится значение OPAQUE. Вы можете изменить содержимое переменной nBkMode с помощью меню "Background Mode" на TRANSPARENT.

Аналогично устанавливается растровая операция, используемая для рисования:

SetROP2(hdc, nROP2);

Далее обработчик сообщения WM_PAINT анализирует содержимое переменной nFigures, которое сразу после запуска приложения равно 0. Когда вы выбираете строку из меню "Draw", в эту переменную записывается идентификатор выбранной из меню строки. При обработке сообщения WM_PAINT в зависимости от содержимого переменной вызывается одна из нескольких функций, рисующих различные фигуры. Исходные тексты этих функций вынесены в отдельный файл (листинг 2.2).

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

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

case CM_TRANSPARENT: { nBkMode = TRANSPARENT; InvalidateRect(hwnd, NULL, TRUE); return 0; }

Функции, рисующие изображения, мы вынесли в отдельный файл (листинг 2.2).

Листинг 2.2. Файл liner/drawfn.cpp

// ---------------------------------------- // Функции для приложения LINER // ---------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h> #include "liner.hpp"

// Прототипы функций void DrawLines(HDC); void DrawPolyline(HDC); void DrawArc(HDC); void DrawRectangles(HDC); void DrawPolygon(HDC); void DrawEllipse(HDC); void DrawCircle(HDC); void DrawPie(HDC); void DrawChord(HDC);



// Размеры внутренней области окна extern short cxClient, cyClient;

// ------------------------------------------- // DrawLines // Рисование линий различной толщины и стиля // ------------------------------------------- void DrawLines(HDC hdc) { HPEN hpenW10, hpenW50; HPEN hpenDot, hpenDash, hpenDashDot, hpenOldPen;

// Создаем несколько перьев: два пера толщиной // 1 мм и 5 мм, пунктирное, штриховое и // штрих-пунктирное hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenW50 = CreatePen(PS_SOLID, 50, RGB(0,0,0)); hpenDot = CreatePen(PS_DOT, 0, RGB(0,0,0)); hpenDash = CreatePen(PS_DASH, 0, RGB(0,0,0)); hpenDashDot = CreatePen(PS_DASHDOT, 0, RGB(0,0,0));

// Рисуем тонкую линию пером, выбранным в контекст // отображения по умолчанию, и подписываем эту линию MoveToEx(hdc, 100, 100, NULL); LineTo(hdc, 600, 100); TextOut(hdc, 700, 100, "PS_SOLID, 1 pixel", 17);

// Выбираем перо толщиной 1 мм hpenOldPen = SelectPen(hdc, hpenW10);

// Рисуем линию выбранным пером MoveToEx(hdc, 100, 200, NULL); LineTo(hdc, 600, 200); TextOut(hdc, 700, 200, "PS_SOLID, 1 mm", 14);

// Перебираем остальные перья

SelectPen(hdc, hpenW50);

MoveToEx(hdc, 100, 300, NULL); LineTo(hdc, 600, 300); TextOut(hdc, 700, 300, "PS_SOLID, 5 mm", 14);

SelectPen(hdc, hpenDot);

MoveToEx(hdc, 100, 400, NULL); LineTo(hdc, 600, 400); TextOut(hdc, 700, 400, "PS_DOT", 6);

SelectPen(hdc, hpenDash);

MoveToEx(hdc, 100, 500, NULL); LineTo(hdc, 600, 500); TextOut(hdc, 700, 500, "PS_DASH", 7);

SelectPen(hdc, hpenDashDot);

MoveToEx(hdc, 100, 600, NULL); LineTo(hdc, 600, 600); TextOut(hdc, 700, 600, "PS_DASHDOT", 10);

// Выбираем старое перо SelectPen(hdc, hpenOldPen);

// Удаляем созданные нами перья DeletePen(hpenW10); DeletePen(hpenW50); DeletePen(hpenDot); DeletePen(hpenDash); DeletePen(hpenDashDot); }

// ------------------------------------------- // DrawPolyline // Рисование ломаной линии // ------------------------------------------- void DrawPolyline(HDC hdc) { // Массив координат точек излома POINT ptPoints[] = { {10, 10}, {100, 310}, {40, 300}, {300, 15}, {135, 340}, {113, 125}, {250, 137}, {300, 300} };



// Рисуем ломаную линию Polyline(hdc, ptPoints, sizeof ptPoints / sizeof ptPoints[0]); }

// ------------------------------------------- // DrawArc // Рисование дуги эллипса // ------------------------------------------- void DrawArc(HDC hdc) { HPEN hpenW10, hpenOldPen;

// Создаем перо толщиной 1 мм и выбираем его hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenOldPen = SelectPen(hdc, hpenW10);

// Рисуем дугу Arc(hdc, 100, 600, // верхний левый угол прямоугольника 800, 100, // нижний правый угол прямоугольника 650, 650, // начало 750, 0); // конец

// Выбираем старое перо SelectPen(hdc, hpenOldPen);

// Удаляем созданное перо DeletePen(hpenW10); }

// ------------------------------------------- // DrawRectangles // Рисование прямоугольников // ------------------------------------------- void DrawRectangles(HDC hdc) { HPEN hpenW10, hpenOldPen; HBRUSH hbrush, hbrushOldBrush; POINT pt[2]; RECT rc = {350, 500, 500, 400};

// Рисуем прямоугольник вокруг внутренней области окна // Так как установлен метрический режим отображения, // а размеры окна, передаваемые вместе с сообщением // WM_SIZE, выражены в пикселах, выполняем // преобразование физических координат в логические pt[0].x = 0; pt[0].y = cyClient; pt[1].x = cxClient; pt[1].y = 0; DPtoLP(hdc, pt, 2);

// Создаем перо толщиной 1 мм и выбираем его hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenOldPen = SelectPen(hdc, hpenW10);

// Рисуем прямоугольник Rectangle(hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y);

// Выбираем серую кисть hbrush = GetStockBrush(GRAY_BRUSH); hbrushOldBrush = SelectBrush(hdc, hbrush);

// Рисуем прямоугольник, закрашенный серым цветом Rectangle(hdc, 100, 500, 300, 50);

// Создаем и выбираем кисть для штриховки hbrush = CreateHatchBrush(HS_DIAGCROSS, RGB(0,0,0)); SelectBrush(hdc, hbrush);

// Рисуем заштрихованный прямоугольник Rectangle(hdc, 50, 300, 500, 100);

// Выбираем старое перо и кисть SelectPen(hdc, hpenOldPen); SelectBrush(hdc, hbrushOldBrush);

// Заштриховываем прямоугольную область кистью // hbrush, которая НЕ ВЫБРАНА в контекст FillRect(hdc, &rc, hbrush);



// Рисуем прямоугольник со скругленными углами RoundRect(hdc, 550, 200, 800, 100, 50, 50);

// Удаляем созданные нами перо и кисть DeletePen(hpenW10); DeleteBrush(hbrush);}

// ------------------------------------------- // DrawPolygon // Рисование многоугольника // ------------------------------------------- void DrawPolygon(HDC hdc) { HBRUSH hbrush, hbrushOldBrush; int nOldPolyFillMode;

// Координаты вершин первого многоугольника POINT ptPoints1[] = { {10, 10}, {100, 310}, {40, 300}, {300, 15}, {135, 340}, {113, 125}, {250, 137}, {300, 300} };

// Координаты вершин второго многоугольника POINT ptPoints2[] = { {310, 10}, {400, 310}, {340, 300}, {600, 15}, {435, 340}, {413, 125}, {550, 137}, {600, 300} };

// Выбираем встроенную серую кисть hbrush = GetStockBrush(GRAY_BRUSH); hbrushOldBrush = SelectBrush(hdc, hbrush);

// Рисуем первый многоугольник в режиме // заполнения ALTERNATE, установленном // по умолчанию Polygon(hdc, ptPoints1, sizeof ptPoints1 / sizeof ptPoints1[0]);

// Устанавливаем режим заполнения WINDING nOldPolyFillMode = SetPolyFillMode(hdc, WINDING);

// Рисуем второй многоугольник Polygon(hdc, ptPoints2, sizeof ptPoints2 / sizeof ptPoints2[0]);

// Восстанавливаем старый режим заполнения SetPolyFillMode(hdc, nOldPolyFillMode); SelectBrush(hdc, hbrushOldBrush); }

// ------------------------------------------- // DrawEllipse // Рисование эллипса // ------------------------------------------- void DrawEllipse(HDC hdc) { POINT pt[2];

// Эллипс будет вписан во внутреннюю область // окна, поэтому определяем координаты углов // в текущей (метрической) системе координат, // выполняя преобразование pt[0].x = 0; pt[0].y = cyClient; pt[1].x = cxClient; pt[1].y = 0; DPtoLP(hdc, pt, 2);

// Рисуем эллипс Ellipse(hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y); }

// ------------------------------------------- // DrawCircle // Рисование окружности // ------------------------------------------- void DrawCircle(HDC hdc) { // Рисуем эллипс, вписанный в квадрат Ellipse(hdc, 100, 600, 600, 100); }



// ------------------------------------------- // DrawPie // Рисование сектора круга // ------------------------------------------- void DrawPie(HDC hdc) { HPEN hpenW10, hpenOldPen;

// Создаем перо и выбираем его hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenOldPen = SelectPen(hdc, hpenW10);

// Рисуем сектор круга Pie(hdc, 100, 600, 800, 100, 650, 650, 750, 0);

// Выбираем старое перо и удаляем созданное SelectPen(hdc, hpenOldPen); DeletePen(hpenW10); }

// ------------------------------------------- // DrawChord // Рисование сегмента круга // ------------------------------------------- void DrawChord(HDC hdc) { HPEN hpenW10, hpenOldPen;

hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenOldPen = SelectPen(hdc, hpenW10);

// Рисуем сегмент круга Chord(hdc, 100, 600, 800, 100, 650, 650, 750, 0);

SelectPen(hdc, hpenOldPen); DeletePen(hpenW10); }

Символические имена констант, используемые для идентификации строк меню, описаны в файле liner.hpp (листинг 2.3).

Листинг 2.3. Файл liner/liner.hpp

#define CM_HELPABOUT 301 #define CM_OPAQUE 302 #define CM_TRANSPARENT 303 #define CM_FILEEXIT 304 #define CM_POLYGON 305 #define CM_PIE 306 #define CM_CHORD 307 #define CM_ELLIPSE 308 #define CM_RECT 309 #define CM_ARC 310 #define CM_LINES 311 #define CM_POLYLINE 312 #define CM_CIRCLE 313

#define CM_R2_BLACK 201 #define CM_R2_NOTMERGEPEN 202 #define CM_R2_MASKNOTPEN 203 #define CM_R2_NOTCOPYPEN 204 #define CM_R2_MASKPENNOT 205 #define CM_R2_NOT 206 #define CM_R2_XORPEN 207 #define CM_R2_NOTMASKPEN 208 #define CM_R2_MASKPEN 209 #define CM_R2_NOTXORPEN 210 #define CM_R2_NOP 211 #define CM_R2_MERGENOTPEN 212 #define CM_R2_COPYPEN 213 #define CM_R2_MERGEPENNOT 214 #define CM_R2_MERGEPEN 215 #define CM_R2_WHITE 216

Меню приложения определено в файле ресурсов liner.rc (листинг 2.4).

Листинг 2.4. Файл liner/liner.rc

#include "liner.hpp"

APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END

POPUP "&Draw" BEGIN MENUITEM "&Lines", CM_LINES MENUITEM "&Polyline", CM_POLYLINE MENUITEM "&Arc", CM_ARC MENUITEM "&Rectangles", CM_RECT MENUITEM "P&opygon", CM_POLYGON MENUITEM "&Ellipse", CM_ELLIPSE MENUITEM "C&ircle", CM_CIRCLE MENUITEM "&Chord", CM_CHORD MENUITEM "&Pie", CM_PIE END



POPUP "&Background Mode" BEGIN MENUITEM "&Opaque", CM_OPAQUE MENUITEM "&Transparent", CM_TRANSPARENT END

POPUP "&ROP" BEGIN MENUITEM "R2_BLACK", CM_R2_BLACK MENUITEM "R2_NOTMERGEPEN", CM_R2_NOTMERGEPEN MENUITEM "R2_MASKNOTPEN", CM_R2_MASKNOTPEN MENUITEM "R2_NOTCOPYPEN", CM_R2_NOTCOPYPEN MENUITEM "R2_MASKPENNOT", CM_R2_MASKPENNOT MENUITEM "R2_NOT", CM_R2_NOT MENUITEM "R2_XORPEN", CM_R2_XORPEN MENUITEM "R2_NOTMASKPEN", CM_R2_NOTMASKPEN MENUITEM "R2_MASKPEN", CM_R2_MASKPEN MENUITEM "R2_NOTXORPEN", CM_R2_NOTXORPEN MENUITEM "R2_NOP", CM_R2_NOP MENUITEM "R2_MERGENOTPEN", CM_R2_MERGENOTPEN MENUITEM "R2_COPYPEN", CM_R2_COPYPEN MENUITEM "R2_MERGEPENNOT", CM_R2_MERGEPENNOT MENUITEM "R2_MERGEPEN", CM_R2_MERGEPEN MENUITEM "R2_WHITE", CM_R2_WHITE END

POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END

Файл liner. def используется для определения модуля приложения LINER (листинг 2.5).

Листинг 2.5. Файл liner/liner.def

; ============================= ; Файл определения модуля ; ============================= NAME LINER DESCRIPTION 'Приложение LINER, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение PALETTE


Для демонстрации методов работы с цветовыми палитрами мы подготовили приложение PALETTE. Это приложение создает палитру из 256 градаций серого цвета и рисует в своем окне вертикальные прямоугольники с использованием палитры.

На рис. 3.8 изображен внешний вид окна приложения PALETTE. Из-за ограниченных возможностей типографии вместо плавных переходов оттенков серого на этом рисунке использованы смешанные цвета. Кстати, если вы запустите приложение PALETTE в стандартном режиме VGA с использованием 16 цветов, внешний вид окна на экране монитора будет полностью соответствовать рис. 3.8.

Рис. 3.8. Окно приложения PALETTE

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

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

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

На дискете, которая продается вместе с книгой, в каталоге palette есть несколько bmp-файлов, содержащих 256-цветные изображения. Загружая эти изображения в графический редактор Paint Brush, вы сможете изменить системную палитру и посмотреть, как это отразится на окне приложения PALETTE.

Запустите приложения PALETTE и Paint Brush. Измените размеры окон этих приложений так, чтобы окна были видны одновременно. Затем загрузите в приложение Paint Brush изображение sky.bmp. Вы увидите, что внешний вид окна приложения PALETTE изменился. Переключитесь на приложение PALETTE. Внешний вид его окна восстановится, но качество изображения в окне Paint Brush ухудшится.

Описанное явление возникает из за того, что размер системной палитры цветов равен 256.
Когда вы загружаете в Paint Brush файл sky.bmp, системная палитра изменяется так, чтобы наилучшим образом соответствовать цветам изображения. Если при этом было запущено приложение PALETTE (создающее свою собственную палитру из 256 градаций серого цвета), и оно находится в фоновом режиме, для него используется фоновый алгоритм реализации палитры.

В первую очередь будут удовлетворен запрос на системную палитру для активного приложения (Paint Brush), а затем уже фонового (PALETTE).

Проследите, как изменяется внешний вид окна приложения PALETTE при загрузке других bmp-файлов, имеющихся на дискете в каталоге palette. Обратите внимание, что при загрузке файла gray.bmp качество изображения в окне PALETTE практически не изменяется, так как изображение gray.bmp содержит только оттенки серого цвета.

Обратимся теперь к исходному тексту приложения PALETTE (листинг 3.3).

Листинг 3.3. Файл palette/palette.cpp

// ---------------------------------------- // Приложение PALETTE // Демонстрация использования цветовых палитр // ----------------------------------------

#define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h>

// Размер создаваемой логической палитры #define PALETTESIZE 256

// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); void PaletteInfo(void);

// Имя класса окна char const szClassName[] = "PaletteClass";

// Заголовок окна char const szWindowTitle[] = "Palette Demo";

// Размеры внутренней области окна short cxClient, cyClient;

// Идентификаторы палитр HPALETTE hPal, hOldPalette;

// Указатель на логическую палитру NPLOGPALETTE pLogPal;

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения



// Инициализируем приложение if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE;

// Выводим сведения о палитре PaletteInfo();

// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;

// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));

wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;

// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;

switch (msg) { case WM_CREATE: { int i;

// Получаем память для палитры pLogPal = (NPLOGPALETTE) LocalAlloc(LMEM_FIXED, (sizeof (LOGPALETTE) + (sizeof (PALETTEENTRY) * (PALETTESIZE))));

// Заполняем заголовок палитры pLogPal->palVersion = 0x300; pLogPal->palNumEntries = PALETTESIZE;



// Заполняем палитру градациями // серого цвета for (i=0; i < 256; i++) { pLogPal->palPalEntry[i].peRed = pLogPal->palPalEntry[i].peGreen = pLogPal->palPalEntry[i].peBlue = i; pLogPal->palPalEntry[i].peFlags = 0; }

// Создаем логическую палитру hPal = CreatePalette((LPLOGPALETTE) pLogPal); return 0; }

// При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }

// Рисование в окне case WM_PAINT: { RECT rc; int i, nWidth; HBRUSH hBrush;

// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);

// Выбираем палитру hOldPalette = SelectPalette(hdc, hPal, FALSE);

// Реализуем логическую палитру RealizePalette(hdc);

// Координаты первого прямоугольника nWidth = 2; rc.left = rc.top = 0; rc.right = nWidth; rc.bottom = cyClient;

// Рисуем 256 прямоугольников во внутренней // области окна for (i=0; i < 256; i++) { // Выбираем кисть. Вы можете использовать одну из // двух приведенных ниже строк, переместив символ // комментария

// Косвенная ссылка на палитру // hBrush = CreateSolidBrush(PALETTERGB(i, i, i));

// Прямая ссылка на палитру hBrush = CreateSolidBrush(PALETTEINDEX(i));

// Закрашиваем прямоугольную область FillRect(hdc, &rc, hBrush);

// Координаты следующего прямоугольника rc.left = rc.right; rc.right += nWidth;

// Удаляем кисть DeleteBrush(hBrush); }

// Выбираем старую палитру SelectPalette(hdc, hOldPalette, TRUE);

// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }

// Это сообщение приходит при изменении // системной палитры. Наше приложение в ответ // на это сообщение заново реализует свою логическую // палитру и при необходимости перерисовывает окно case WM_PALETTECHANGED: { // Если это не наше окно, передаем управление // обработчику сообщения WM_QUERYNEWPALETTE if (hwnd == (HWND) wParam) break; }

// В ответ на это сообщение приложение должно // реализовать свою логическую палитру и // обновить окно case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged;



// Выбираем логическую палитру в // контекст отображения hdc = GetDC(hwnd);

// При обработке сообщения WM_QUERYNEWPALETTE // палитра выбирается для активного окна, // а при обработке сообщения WM_PALETTECHANGED - // для фонового hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);

// Реализуем логическую палитру и выбираем // ее в контекст отображения nChanged = RealizePalette(hdc); SelectPalette(hdc, hOldPal, TRUE);

// Освобождаем контекст отображения ReleaseDC(hwnd, hdc);

// Если были изменения палитры, // перерисовываем окно if(nChanged) InvalidateRect(hwnd, NULL, TRUE);

return nChanged; }

case WM_DESTROY: { // Удаляем созданную нами // логическую палитру DeletePalette(hPal);

// Освобождаем память, выделенную для палитры LocalFree(pLogPal);

PostQuitMessage(0); return 0; }

default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

// -------------------------------------------------------- // Функция PaletteInfo // Вывод некоторых сведений о палитре // --------------------------------------------------------

void PaletteInfo(void) { HDC hdc; int iPalSize, iRasterCaps; char szMsg[256]; char szPal[20];

// Получаем контекст отображения для // всего экрана hdc = GetDC(NULL);

// Определяем размер палитры и слово, // описывающее возможности драйвера // видеоконтроллера как растрового устройства iPalSize = GetDeviceCaps(hdc, SIZEPALETTE); iRasterCaps = GetDeviceCaps(hdc, RASTERCAPS);

// Проверяем, используется ли механизм палитр if(iRasterCaps & RC_PALETTE) { iRasterCaps = TRUE; lstrcpy(szPal, "используются"); } else { iRasterCaps = FALSE; lstrcpy(szPal, "не используются"); }

// Освобождаем контекст отображения ReleaseDC(NULL, hdc);

// Выводим сведения о палитре wsprintf(szMsg, "Палитры %s\n" "Размер системной палитры: %d\n", (LPSTR)szPal, iPalSize);

MessageBox(NULL, szMsg, "Palette Demo", MB_OK); }

В начале файла определена константа PALETTESIZE, значение которой равно размеру создаваемой приложением логической палитры:



#define PALETTESIZE 256

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

if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE;

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

Получив контекст отображения для всего экрана видеомонитора, эта функция вызывает функцию GetDeviceCaps, определяя размер системной палитры и растровые возможности устройства вывода (в данном случае, драйвера видеомонитора):

iPalSize = GetDeviceCaps(hdc, SIZEPALETTE); iRasterCaps = GetDeviceCaps(hdc, RASTERCAPS);

Если используется цветовые палитры, в слове iRasterCaps должен быть установлен бит RC_PALETTE:

if(iRasterCaps & RC_PALETTE) { iRasterCaps = TRUE; lstrcpy(szPal, "используются"); } else { iRasterCaps = FALSE; lstrcpy(szPal, "не используются"); }

На обработчик сообщения WM_CREATE возложена задача создания палитры.

Прежде всего заказываем память для структуры, содержащей палитру:

pLogPal = (NPLOGPALETTE) LocalAlloc(LMEM_FIXED, (sizeof (LOGPALETTE) + (sizeof (PALETTEENTRY) * (PALETTESIZE))));

Размер нужного буфера равен размеру структуры LOGPALETTE (заголовок палитры), плюс размер самой палитры, равный количеству элементов (PALETTESIZE), умноженному на размер одного элемента (sizeof (PALETTEENTRY) ).

В заголовке палитры необходимо заполнить два поля - версию и размер палитры:

pLogPal->palVersion = 0x300; pLogPal->palNumEntries = PALETTESIZE;

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

for (i=0; i < 256; i++) { pLogPal->palPalEntry[i].peRed = pLogPal->palPalEntry[i].peGreen = pLogPal->palPalEntry[i].peBlue = i; pLogPal->palPalEntry[i].peFlags = 0; }

После заполнения структуры данных вызывается функция CreatePalette, создающая палитру:



hPal = CreatePalette((LPLOGPALETTE) pLogPal);

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

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

Рисование выполняется, как и следовало ожидать, при обработке сообщения WM_PAINT.

После получения контекста отображения приложение выбирает в него и реализует логическую палитру:

hOldPalette = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc);

Далее приложение в цикле рисует 256 прямоугольников шириной 2 пиксела. Высота этих прямоугольников равна высоте внутренней области окна приложения. Для каждого прямоугольника создается кисть, причем цвет кисти определяется как ссылка на элемент логической палитры с использованием макрокоманды PALETTEINDEX:

for (i=0; i < 256; i++) { hBrush = CreateSolidBrush(PALETTEINDEX(i)); FillRect(hdc, &rc, hBrush); rc.left = rc.right; rc.right += nWidth; DeleteBrush(hBrush); }

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

Вы можете попробовать создавать кисть при помощи макрокоманды PALETTERGB:

hBrush = CreateSolidBrush(PALETTERGB(i, i, i));

Перед возвращением управления обработчик сообщения WM_PAINT выбирает в контекст отображения старую палитру:

SelectPalette(hdc, hOldPalette, TRUE);

Рассмотрим теперь обработчики сообщений WM_PALETTECHANGED и WM_QUERYNEWPALETTE.

Обработчик сообщения WM_PALETTECHANGED получает управление, когда какое-либо приложение изменяет системную палитру. Так как наше приложение тоже может изменить системную палитру, оно также может выступать инициатором рассылки этого сообщения.

Параметр wParam сообщения WM_PALETTECHANGED содержит идентификатор окна приложения, изменившего палитру. Если этот параметр равен идентификатору нашего окна, ничего делать не надо, поэтому мы просто выходим из функции окна:

case WM_PALETTECHANGED: { if (hwnd == (HWND) wParam) break; }

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



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

Теперь займемся обработчиком сообщения WM_QUERYNEWPALETTE. Для нашего приложения он выполняет почти те же самые действия, что и обработчик сообщения WM_PALETTECHANGED, поэтому для экономии места мы объединили эти обработчики.

При получении сообщения WM_QUERYNEWPALETTE или WM_PALETTECHANGED (как результата изменения системной палитры другим приложением) наше приложение получает контекст отображения и выбирает в него логическую палитру:

hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);

Заметьте, что для сообщения WM_QUERYNEWPALETTE палитра выбирается как для активного окна, а для WM_PALETTECHANGED - как для фонового.

Затем палитра реализуется в контексте отображения:

nChanged = RealizePalette(hdc);

После этого мы выбираем в контекст отображения старую палитру и освобождаем контекст отображения:

SelectPalette(hdc, hOldPal, TRUE); ReleaseDC(hwnd, hdc);

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

if(nChanged) InvalidateRect(hwnd, NULL, TRUE);

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

DeletePalette(hPal); LocalFree(pLogPal);

Файл определения модуля для приложения PALETTE приведен в листинге 3.4.

Листинг 3.4. Файл palette/palette.def

; ============================= ; Файл определения модуля ; ============================= NAME PALETTE DESCRIPTION 'Приложение PALETTE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение PRNFILE


Для иллюстрации всего сказанного выше мы немного изменили приложение TEDIT, описанное в 12 томе "Библиотеки системного программиста", добавив в него возможность печати. В главном окне этого простейшего редактора текста появилась кнопка "Print", с помощью которой вы можете распечатать текст на любом установленном в системе принтере (рис. 6.5).

Рис. 6.5. Главное окно приложения PRNFILE

Исходный основного файла приложения приведен в листинге 6.1.

Листинг 6.1. Файл prnfile/prnfile.cpp

// ---------------------------------------- // Редактор текстовых файлов с возможностью печати // ----------------------------------------

#define STRICT #include <windows.h> #include <commdlg.h> #include <mem.h> #include <string.h> #include <stdlib.h>

// Идентификатор редактора текста #define ID_EDIT 1

// Идентификаторы кнопок #define ID_NEW 2 #define ID_OPEN 3 #define ID_SAVE 4 #define ID_PRINT 5 #define ID_EXIT 6

// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); HFILE OpenFile(void); HFILE OpenSaveFile(void); int PrintFile(HWND, NPSTR, WORD);

// Имя класса окна char const szClassName[] = "TEditAppClass";

// Заголовок окна char const szWindowTitle[] = "Text Editor";

// Идентификатор копии приложения HINSTANCE hInst;

// Флаг изменений в тексте BOOL bUpdate;

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;

// Сохраняем идентификатор копии приложения // в глобальной переменной hInst = hInstance;

// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем расположение и размеры CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, // CW_USEDEFAULT, // 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;


// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна

memset(&wc, 0, sizeof(wc));

wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;

// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Идентификатор редактора текста static HWND hEdit;

// Идентификаторы кнопок static HWND hButtNew; static HWND hButtOpen; static HWND hButtSave; static HWND hButtPrint; static HWND hButtExit;

// Идентификаторы файлов static HFILE hfSrcFile, hfDstFile;

switch (msg) { case WM_CREATE: { // Создаем редактор текста hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE, 0, 0, 0, 0, hwnd, (HMENU) ID_EDIT, hInst, NULL);

// Устанавливаем максимальную длину // редактируемого текста, равную 32000 байт SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L);

// Сбрасываем флаг обновления текста bUpdate = FALSE;

// Создаем кнопки hButtNew = CreateWindow("button", "New", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 80, 20, hwnd, (HMENU) ID_NEW, hInst, NULL);



hButtOpen = CreateWindow("button", "Open", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 80, 0, 80, 20, hwnd, (HMENU) ID_OPEN, hInst, NULL);

hButtSave = CreateWindow("button", "Save", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 160, 0, 80, 20, hwnd, (HMENU) ID_SAVE, hInst, NULL);

hButtPrint = CreateWindow("button", "Print", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 240, 0, 80, 20, hwnd, (HMENU) ID_PRINT, hInst, NULL);

hButtExit = CreateWindow("button", "Exit", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 320, 0, 80, 20, hwnd, (HMENU) ID_EXIT, hInst, NULL);

return 0; }

case WM_SIZE: { // Устанавливаем размер органа управления // (текстового редактора) в соответствии // с размерами главного окна приложения MoveWindow(hEdit, 0, 20, LOWORD(lParam), HIWORD(lParam) - 20, TRUE); return 0; }

// Когда главное окно приложения получает // фокус ввода, отдаем фокус редактору текста case WM_SETFOCUS: { SetFocus(hEdit); return 0; }

case WM_COMMAND: { // Обработка извещений текстового редактора if(wParam == ID_EDIT) { // Ошибка if(HIWORD(lParam) == EN_ERRSPACE) { MessageBox(hwnd, "Мало памяти", szWindowTitle, MB_OK); }

// Произошло изменение в редактируемом // тексте else if(HIWORD(lParam) == EN_UPDATE) { // Устанавливаем флаг обновления текста bUpdate = TRUE; } return 0; }

// Нажата кнопка сохранения текста else if(wParam == ID_SAVE) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer;

// Открываем выходной файл hfDstFile = OpenSaveFile(); if(!hfDstFile) return 0;

// Определяем размер текста wSize = GetWindowTextLength(hEdit);

// Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L);

// Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf);

// Записываем содержимое блока памяти в файл if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize)) { // При ошибке закрываем файл и выдаем сообщение _lclose(hfDstFile); MessageBox(hwnd, "Ошибка при записи файла", szWindowTitle, MB_OK); return 0; }



// Закрываем файл _lclose(hfDstFile);

// Расфиксируем блок памяти LocalUnlock(hTxtBuf);

// Так как файл был только что сохранен, // сбрасываем флаг обновления bUpdate = FALSE;

SetFocus(hEdit); return 0; }

// Создание нового файла else if(wParam == ID_NEW) { // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; }

// Сбрасываем содержимое текстового редактора SetWindowText(hEdit, "\0");

// Сбрасываем флаг обновления bUpdate = FALSE;

SetFocus(hEdit); return 0; }

// Загрузка файла для редактирования else if(wParam == ID_OPEN) { LPSTR lpTextBuffer; DWORD dwFileSize, dwCurrentPos;

// Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; }

// Открываем входной файл. hfSrcFile = OpenFile(); if(!hfSrcFile) return 0;

// Определяем размер файла dwCurrentPos = _llseek(hfSrcFile, 0L, 1); dwFileSize = _llseek(hfSrcFile, 0L, 2); _llseek(hfSrcFile, dwCurrentPos, 0);

// Размер файла не должен превосходить 32000 байт if(dwFileSize >= 32000) { _lclose(hfSrcFile); MessageBox(hwnd, "Размер файла больше 32000 байт", szWindowTitle, MB_OK); return 0; }

// Заказываем память для загрузки файла lpTextBuffer = (LPSTR)malloc(32000); if(lpTextBuffer == NULL) return 0;

// Загружаем текст из файла в буфер _lread(hfSrcFile, lpTextBuffer, dwFileSize);

// Закрываем буфер двоичным нулем lpTextBuffer[(WORD)dwFileSize] = '\0';

// Закрываем файл _lclose(hfSrcFile);

// Переносим содержимое буфера в // текстовый редактор SetWindowText(hEdit, lpTextBuffer);

// Освобождаем буфер free((void *)lpTextBuffer);

// сбрасываем флаг обновления bUpdate = FALSE;

SetFocus(hEdit); return 0; }

// ------------------------------------------ // Печать текста // ------------------------------------------ else if(wParam == ID_PRINT) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer;



// Определяем размер текста wSize = GetWindowTextLength(hEdit);

// Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L);

// Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf);

PrintFile(hwnd, npTextBuffer, wSize);

// Расфиксируем блок памяти LocalUnlock(hTxtBuf);

SetFocus(hEdit); return 0; }

else if(wParam == ID_EXIT) { // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; }

// Посылаем в функцию главного окна // сообщение WM_CLOSE SendMessage(hwnd, WM_CLOSE, 0, 0L); return 0; } return 0; }

case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

// ------------------------------- // Функция OpenFile // Сохранение файла // -------------------------------

HFILE OpenFile(void) { // Структура для выбора файла OPENFILENAME ofn;

// Буфер для записи пути к выбранному файлу char szFile[256];

// Буфер для записи имени выбранного файла char szFileTitle[256];

// Фильтр расширений имени файлов char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0";

// Идентификатор открываемого файла HFILE hf;

// Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0';

// Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME));

// Инициализируем нужные нам поля

// Размер структуры ofn.lStructSize = sizeof(OPENFILENAME);

// Идентификатор окна ofn.hwndOwner = NULL;

// Адрес строки фильтра ofn.lpstrFilter = szFilter;

// Номер позиции выбора ofn.nFilterIndex = 1;

// Адрес буфера для записи пути // выбранного файла ofn.lpstrFile = szFile;

// Размер буфера для записи пути // выбранного файла ofn.nMaxFile = sizeof(szFile);

// Адрес буфера для записи имени // выбранного файла ofn.lpstrFileTitle = szFileTitle;



// Размер буфера для записи имени // выбранного файла ofn.nMaxFileTitle = sizeof(szFileTitle);

// В качестве начального каталога для // поиска выбираем текущий каталог ofn.lpstrInitialDir = NULL;

// Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) {

// Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ);

// Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; }

// ------------------------------- // Функция OpenSaveFile // Выбор файла для редактирования // -------------------------------

HFILE OpenSaveFile(void) { OPENFILENAME ofn;

char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt\0Any Files\0*.*\0";

HFILE hf;

szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_HIDEREADONLY;

// Выбираем выходной файл if (GetSaveFileName(&ofn)) {

// При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0; }

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

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

wSize = GetWindowTextLength(hEdit);

Далее он получает адрес блока памяти, содержащий текст, фиксируя его:

hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); npTextBuffer = (NPSTR)LocalLock(hTxtBuf);

После этого вызывается функция печати PrintFile, определенная в файле print.cpp (листинг 6.2):

PrintFile(hwnd, npTextBuffer, wSize);



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

После выполнения печати буфер расфиксируется, после чего редактор текста получает фокус ввода:

LocalUnlock(hTxtBuf); SetFocus(hEdit); return 0;

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

Листинг 6.2. Файл prnfile/print.cpp

// ---------------------------------------------------- // Функции для работы с принтером // ---------------------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <string.h> #include "prnfile.hpp"

// Прототипы функций BOOL CALLBACK _export AbortDlgFunc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam);

BOOL CALLBACK _export AbortFunc(HDC hdc, int nCode); HDC GetPrinterDC(HWND);

// Внешние глобальные переменные extern HWND hdlgAbort; extern BOOL fAbort;

BOOL fAbort = FALSE; HWND hdlgAbort = 0; static PRINTDLG pd;

// ---------------------------------------------------- // Функция PrintFile // Печать файла // ----------------------------------------------------

BOOL PrintFile(HWND hwnd, NPSTR npBuff, WORD wSize) { HDC hdc; int cyPage; int cyChar, yPos, nLength; int i; WORD wCurPos = 0; TEXTMETRIC tm;

ABORTPROC lpAbortFunc; BOOL fDone; char abBuffer[256]; DOCINFO docinfo; DLGPROC lpAbortDlgFunc; HINSTANCE hInst; int rc;

// Получаем контекст устройства для принтера hdc = GetPrinterDC(hwnd);

// Определяем разрешение принтера по вертикали cyPage = GetDeviceCaps(hdc, VERTRES);

// Определяем метрики текста GetTextMetrics(hdc, &tm);

// Вычисляем высоту шрифта cyChar = tm.tmHeight + tm.tmExternalLeading;

// Создаем переходник для функции AbortFunc hInst = GetWindowInstance(hwnd); lpAbortFunc = (ABORTPROC)MakeProcInstance((FARPROC)AbortFunc, hInst);

// Устанавливаем функцию AbortProc rc = SetAbortProc(hdc, lpAbortFunc); if(rc <= 0) { DeleteDC(hdc); return FALSE; }



// Создаем переходник для функции диалога lpAbortDlgFunc = (DLGPROC)MakeProcInstance((FARPROC)AbortDlgFunc, hInst);

// Создаем диалог для отмены печати hdlgAbort = CreateDialogParam ( hInst, MAKEINTRESOURCE(IDD_ABORT), hwnd, lpAbortDlgFunc, NULL) ;

if(!hdlgAbort) { FreeProcInstance((FARPROC)lpAbortFunc); DeleteDC(hdc); return FALSE; }

// Отображаем созданную диалоговую панель ShowWindow(hdlgAbort, SW_SHOWNORMAL); UpdateWindow(hdlgAbort);

// Переводим окно приложения в неактивное // состояние EnableWindow(hwnd, FALSE);

// Заполняем структуру docinfo docinfo.cbSize = sizeof(docinfo); docinfo.lpszDocName = NULL; docinfo.lpszOutput = NULL;

// Начинаем печать документа rc = StartDoc(hdc, &docinfo); if(rc <= 0) { DestroyWindow(hdlgAbort); FreeProcInstance((FARPROC)lpAbortFunc); FreeProcInstance((FARPROC)lpAbortDlgFunc); DeleteDC(hdc); return FALSE; }

// Флаг завершения печати документа fDone = FALSE;

// Цикл печати страниц документа while(!fDone && !fAbort) { // Начинаем печать страницы документа StartPage(hdc);

// Начальная позиция по вертикали yPos = 0;

// Цикл по строкам страницы while(yPos + cyChar < cyPage) { // Проверка завершения печати страницы if(wCurPos > wSize) { fDone = TRUE; break; }

i=0; nLength = 0;

// Цикл по строке // Копируем строку в буфер abBuffer while((npBuff[wCurPos] != 0x0d) && (wCurPos < wSize)) { abBuffer[i] = npBuff[wCurPos]; i++; wCurPos++; nLength++; }

// Рисуем одну строку текста TextOut(hdc, 0, yPos, abBuffer, nLength);

// Переходим к следующей строке wCurPos += 2; yPos += cyChar ; }

// Инициируем печать страницы rc = EndPage(hdc); if(rc < 0) { fAbort = TRUE; break; } }

// При аварийном завершении печати вызываем // функцию AbortDoc, при нормальном - EndDoc if(fAbort) AbortDoc(hdc); else EndDoc(hdc);

// Активизируем главное окно приложения EnableWindow(hwnd, TRUE);

// Удаляем диалоговую панель DestroyWindow(hdlgAbort);

// Освобождаем ресурсы FreeProcInstance((FARPROC)lpAbortFunc); FreeProcInstance((FARPROC)lpAbortDlgFunc); DeleteDC(hdc);



return TRUE ; }

// ---------------------------------------------------- // Функция AbortDlgFunc // Функция диалога для диалоговой панели, // позволяющей прервать процесс печати // ---------------------------------------------------- #pragma argsused

BOOL CALLBACK _export AbortDlgFunc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { // Инициализируем флаги case WM_INITDIALOG: { fAbort = FALSE; hdlgAbort = hdlg; return TRUE; }

case WM_COMMAND: { // Устанавливаем флаг аварийного завершения печати if (wParam == IDOK wParam == IDCANCEL) { fAbort = TRUE; return TRUE; } return FALSE; }

case WM_DESTROY: { hdlgAbort = 0; return FALSE; } } return FALSE; }

// ---------------------------------------------------- // Функция AbortFunc // Обеспечивает возможность работы других // приложений во время печати // ---------------------------------------------------- #pragma argsused

BOOL CALLBACK _export AbortFunc(HDC hdc, int nCode) { MSG msg;

// Второй цикл обработки сообщений while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { if(!hdlgAbort !IsDialogMessage (hdlgAbort, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }

return(!fAbort); }

// ---------------------------------------------------- // Функция GetPrinterDC // Выводит на экран диалоговую панель "Print", // с помощью которой можно выбрать принтер. // Возвращает идентификатор контекста для // выбранного принтера // ----------------------------------------------------

HDC GetPrinterDC(HWND hwnd) { BOOL fResult;

// Инициализируем структуру PRINTDLG memset(&pd, 0, sizeof(PRINTDLG));

pd.lStructSize = sizeof(PRINTDLG); pd.hwndOwner = hwnd; pd.Flags = PD_RETURNDC;

// Отображаем диалоговую панель fResult = PrintDlg(&pd);

// При необходимости освобождаем память, полученную // функцией PrintDlg для структур DEVMODE и DEVNAMES if(pd.hDevMode != 0) GlobalFree (pd.hDevMode);

if(pd.hDevNames != 0) GlobalFree (pd.hDevNames);

// В случае успешного завершения возвращаем // контекст принтера if(fResult) return pd.hDC;



else return 0; }

Функция PrintFile выполняет печать файла, загруженного в текстовый редактор.

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

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

После этого функция PrintFile создает переходник для функции отмены печати и подключает последнюю, вызывая функцию SetAbortProc.

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

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

Перед началом печати заполняется структура DOCINFO и вызывается функция StartDoc.

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

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

Далее выполняется построчное копирование текста из буфера текстового редактора в буфер abBuffer с последующим выводом содержимого этого буфера на принтер функцией TextOut.

Печать страницы выполняется после вызова функции EndPage.

При нормальном завершении процесса печати вызывается функция EndDoc, а при аварийном - AbortDoc.

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

Идентификатор диалоговой панели определен в файле prnfile.hpp (листинг 6.3).



Листинг 6.3. Файл prnfile/prnfile.hpp

#define IDD_ABORT 25

Шаблон диалоговой панели отмены печати определен в файле описания ресурсов приложения (листинг 6.4).

Листинг 6.4. Файл prnfile/prnfile.rc

#include <g:\tcwin\include\windows.h> #include "prnfile.hpp"

IDD_ABORT DIALOG 50, 30, 89, 43 STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU CAPTION "Печать..." BEGIN CONTROL "Cancel", IDCANCEL, "BUTTON", WS_GROUP, 29, 23, 32, 14 CTEXT "Cancel - отмена печати", -1, -1, 8, 90, 8, WS_CHILD | WS_VISIBLE | WS_GROUP END

Файл определения модуля приведен в листинге 6.5.

Листинг 6.5. Файл prnfile/prnfile.def

; ============================= ; Файл определения модуля ; ============================= NAME PRNFILE DESCRIPTION 'Приложение PRNFILE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение REGIONS


Для демонстрации использования комбинированных областей мы подготовили приложение REGIONS. Это приложение создает три области: прямоугольную и две эллиптические разного размера. Прямоугольная область объединяется с первой эллиптической областью. Из полученного результата вырезается вторая эллиптическая область (меньших размеров).

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

Рис. 2.27. Вывод текста с использованием области ограничения

Исходный текст приложения приведен в листинге 2.6.

Листинг 2.6. Файл regions/regions.cpp

// ---------------------------------------- // Приложение REGIONS // Демонстрация использования области ограничения // ----------------------------------------

#define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h>

// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна char const szClassName[] = "RegionsClass";

// Заголовок окна char const szWindowTitle[] = "Regions";

// Размеры внутренней области окна short cxClient, cyClient;

// Размеры символов int cxChar, cyChar;

// Текст для вывода в окне приложения char const szText[] = "В интерфейсе GDI есть средства, позволяющие приложениям" " создавать области достаточно сложной формы из" " прямоугольных, многоугольных и эллиптических областей." " Такие области можно закрашивать или использовать" " в качестве маски при выводе графического изображения. " "В последнем случае область называется областью" " ограничения. Она должна быть выбрана в контекст" " отображения.";

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения


// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;

// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;

// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));

wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;

// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static TEXTMETRIC tm; static HRGN hrgn1, hrgn2, hrgn3, hrgnTemp, hrgnClip; HBRUSH hbrush;

switch (msg) { case WM_CREATE: { // Определяем метрику шрифта hdc = GetDC(hwnd); GetTextMetrics(hdc, &tm); ReleaseDC(hwnd, hdc);

// Высота и средняя ширина букв cyChar = tm.tmHeight + tm.tmExternalLeading; cxChar = tm.tmAveCharWidth;



// Область ограничения не задана hrgnClip = NULL;

return 0; }

// При изменении размеров окна сохраняем // новые значения для ширины и высоты, // а также определяем область ограничения case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam);

// Если область ограничения была определена раньше, // удаляем ее if(hrgnClip) DeleteRgn(hrgnClip);

// Формируем область ограничения hrgnClip = CreateEllipticRgn(0, 0, cxClient, cyClient);

// Временная область ограничения hrgnTemp = CreateEllipticRgn(0, 0, cxClient, cyClient);

// Первая эллиптическая область hrgn1 = CreateEllipticRgn(0, 0, cxClient, cyClient);

// Вторая эллиптическая область hrgn2 = CreateEllipticRgn(cxClient/3, cyClient/3, 2*(cxClient/3), 2*(cyClient/3));

// Прямоугольная область hrgn3 = CreateRectRgn(cxClient/20, cyClient/20, 19*(cxClient/20), 19*(cyClient/20));

// Комбинируем области UnionRgn(hrgnTemp, hrgn1, hrgn3); SubtractRgn(hrgnClip, hrgnTemp, hrgn2);

// Удаляем временные области DeleteRgn(hrgn1); DeleteRgn(hrgn2); DeleteRgn(hrgn3); DeleteRgn(hrgnTemp); return 0; }

// Рисование в окне case WM_PAINT: { RECT rc;

// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);

// Выбираем встроенную кисть зеленого цвета hbrush = CreateSolidBrush(RGB(0, 0xff, 0));

// Обводим границы области FrameRgn(hdc, hrgnClip, hbrush, 2, 5);

// Выбираем область ограничения в контекст // отображения SelectClipRgn(hdc, hrgnClip);

// Определяем координаты прямоугольной // области для вывода текста rc.left = cxChar; rc.top = 0; rc.right = cxClient - cxChar; rc.bottom = cyClient;

// Вывод текста DrawText(hdc, szText, lstrlen(szText), &rc, DT_LEFT | DT_WORDBREAK);

// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }

case WM_DESTROY: { // удаляем область ограничения DeleteRgn(hrgnClip);

PostQuitMessage(0); return 0; }

default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

Во время создания окна обработчик сообщения WM_CREATE определяет метрику шрифта и записывает значение NULL в переменную hrgnClip.


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

Область ограничения формируется каждый раз заново при изменении размеров окна. Обработчик сообщения WM_SIZE сохраняет ширину и высоту окна в переменных cxClient и cyClient.

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

После этого приложение создает области ограничения и комбинирует их в одну область hrgnClip, используя макрокоманды UnionRgn и SubtractRgn.

Далее все области, кроме hrgnClip, удаляются, так как они больше не нужны.

Приложение REGIONS рисует в окне во время обработки сообщения WM_PAINT.

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

hbrush = CreateSolidBrush(RGB(0, 0xff, 0)); FrameRgn(hdc, hrgnClip, hbrush, 2, 5);

Далее область hrgnClip выбирается в контекст отображения для использования в качестве маски при выводе текста:

SelectClipRgn(hdc, hrgnClip);

Вывод текста выполняется при помощи функции DrawText.

Перед завершением своей работы (при обработке сообщения WM_DESTROY) приложение удаляет область hrgnClip, вызывая макрокоманду DeleteRgn:

DeleteRgn(hrgnClip);

Файл определения модуля для приложения REGIONS приведен в листинге 2.7.

Листинг 2.7. Файл regions/regions.def

; ============================= ; Файл определения модуля ; ============================= NAME REGIONS DESCRIPTION 'Приложение REGIONS, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение SYSPAL


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

С помощью приложения SYSPAL вы сможете визуально проследить за изменениями системной палитры, например, при загрузке bmp-файлов в приложение Paint Brush, при запуске приложения PALETTE или других приложений, изменяющих системную палитру.

Это приложение во многом напоминает предыдущее, поэтому для экономии места мы сократили количество комментариев в его исходном тексте (листинг 3.5).

Листинг 3.5. Файл syspalet/syspal.cpp

// ---------------------------------------- // Приложение SYSPAL // Просмотр системной цветовой палитры // ----------------------------------------

#define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h>

#define PALETTESIZE 256

BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL PaletteInfo(void);

char const szClassName[] = "SysPalClass"; char const szWindowTitle[] = "System Palette";

short cxClient, cyClient; HPALETTE hPal, hOldPalette; NPLOGPALETTE pLogPal;

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE;

if(!PaletteInfo()) return FALSE;

hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

if(!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);


while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));

wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;

// Регистрация класса aWndClass = RegisterClass(&wc);

return (aWndClass != 0); }

// ===================================== // Функция WndProc // =====================================

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;

switch (msg) { case WM_CREATE: { int i;

// Получаем память для палитры pLogPal = (NPLOGPALETTE) LocalAlloc(LMEM_FIXED, (sizeof (LOGPALETTE) + (sizeof (PALETTEENTRY) * (PALETTESIZE))));

// Заполняем заголовок палитры pLogPal->palVersion = 0x300; pLogPal->palNumEntries = PALETTESIZE;

// Младшее слово структуры PALETTEENTRY содержит поля // peRed и peGreen, а старшее - peBlue и peFlags. // Отмечаем все элементы палитры флагом PC_EXPLICIT for (i = 0; i < PALETTESIZE; i++) { pLogPal->palPalEntry[i].peBlue = 0; *((PWORD)(&pLogPal->palPalEntry[i].peRed)) = i; pLogPal->palPalEntry[i].peFlags = PC_EXPLICIT; }

// Создаем логическую палитру hPal = CreatePalette((LPLOGPALETTE) pLogPal); return 0; }

// При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }

// Рисование в окне case WM_PAINT: { RECT rc; int i, nWidth; HBRUSH hBrush;



hdc = BeginPaint(hwnd, &ps); hOldPalette = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc);

nWidth = 2; rc.left = rc.top = 0; rc.right = nWidth; rc.bottom = cyClient;

for (i=0; i < 256; i++) { hBrush = CreateSolidBrush (PALETTEINDEX (i)); FillRect (hdc, &rc, hBrush); rc.left = rc.right; rc.right += nWidth; DeleteBrush(hBrush); }

SelectPalette(hdc, hOldPalette, TRUE); EndPaint(hwnd, &ps); return 0; }

case WM_PALETTECHANGED: { if (hwnd == (HWND) wParam) break; }

case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged;

hdc = GetDC(hwnd);

hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);

nChanged = RealizePalette(hdc); SelectPalette(hdc, hOldPal, TRUE);

ReleaseDC(hwnd, hdc);

if(nChanged) InvalidateRect(hwnd, NULL, TRUE);

return nChanged; }

case WM_DESTROY: { DeletePalette(hPal); LocalFree(pLogPal);

PostQuitMessage(0); return 0; }

default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

// -------------------------------------------------------- // Функция PaletteInfo // Проверка возможности работы с палитрами // --------------------------------------------------------

BOOL PaletteInfo(void) { HDC hdc; int iRasterCaps;

hdc = GetDC(NULL); iRasterCaps = GetDeviceCaps(hdc, RASTERCAPS); ReleaseDC(NULL, hdc);

// Проверяем, используется ли механизм палитр if(iRasterCaps & RC_PALETTE) return TRUE; else return FALSE; }

Так же, как и приложение PALETTE, приложение SYSPAL создает логическую палитру для 256 цветов. Однако палитра заполняется по другому:

for (i = 0; i < PALETTESIZE; i++) { pLogPal->palPalEntry[i].peBlue = 0; *((PWORD)(&pLogPal->palPalEntry[i].peRed)) = i; pLogPal->palPalEntry[i].peFlags = PC_EXPLICIT; } hPal = CreatePalette((LPLOGPALETTE) pLogPal);

Все элементы палитры отмечаются флагом PC_EXPLICIT. Это означает, что палитра содержит не цвета, а индексы цветов системной палитры. Точнее, младшее слово каждого элемента палитры содержит индекс цвета системной палитры.


Структура PALETTEENTRY описана в файле windows.h следующим образом:

typedef struct tagPALETTEENTRY { BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags; } PALETTEENTRY;

При этом младшее слово структуры PALETTEENTRY содержит поля peRed и peGreen, а старшее - peBlue и peFlags. Мы пользуемся этим обстоятельством, записывая в младшее слово значения от 0 до 255 (индекс в системной табице цветов), в поле peBlue - нулевое значение, а в поле peFlags - значение PC_EXPLICIT.

Созданная таким образом палитра используется также, как и в приложении PALETTE.

Файл определения модуля приложения SYSPAL приведен в листинге 3.6.

Листинг 3.6. Файл syspalet/syspal.def

; ============================= ; Файл определения модуля ; ============================= NAME SYSPAL DESCRIPTION 'Приложение SYSPAL, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Прямоугольная область


Для создания прямоугольной области предназначены функции CreateRectRgn и CreateRectRgnIndirect :

HRGN WINAPI CreateRectRgn( int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); HRGN WINAPI CreateRectRgnIndirect(const RECT FAR* lprc);

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

Заметьте, что область создается вне всякой связи с контекстом отображения. Область - это объект, принадлежащий GDI, поэтому его следует удалить после использования. Лучше всего для этого воспользоваться макрокомандой DeleteRgn , определенной в файле windowsx.h следующим образом:

#define DeleteRgn(hrgn) DeleteObject((HGDIOBJ)(HRGN)(hrgn))

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

HRGN WINAPI CreateRoundRectRgn( int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidth, int nHeight);

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



Проверка возможности использования палитры


Далеко не все устройства отображения способны работать с палитрами. Например, драйвер видеоадаптера VGA палитры не поддерживает, несмотря на то что аппаратура VGA может работать с палитрой. Последнее обстоятельство связано с тем, что размер палитры VGA составляет всего 64 ячейки, что явно недостаточно.

Самый лучший способ убедиться в том, что драйвер видеоадаптера способен работать с палитрами, заключается в вызове функции GetDeviceCaps с параметром RASTERCAPS (мы обсуждали эту функцию в 11 томе "Библиотеки системного программиста"). Если в возвращенном слове установлен бит RC_PALETTE , приложение может использовать цветовые палитры.

Стандартный размер системной палитры равен 256 ячеек, однако вы можете уточнить это значение, вызвав функцию GetDeviceCaps с параметром SIZEPALETTE . Если драйвер видеоконтроллера не работает с палитрами, размер палитры, определенный с помощью функции GetDeviceCaps, может быть равным 0.

Для определения количества системных цветов используйте эту же функцию с параметром NUMRESERVED .

С помощью функции GetDeviceCaps можно также определить цветовое разрешение устройства вывода. При этом следует учитывать, что некоторые устройства работают с цветовыми плоскостями. Количество этих плоскостей можно определить, пользуясь значением PLANES . Отметим, что количество цветов, которые могут быть представлены устройством с цветовыми плоскостями, равно 2n, где n - количество цветовых плоскостей.

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

nColors = 2(nPixel * nPlanes),

где nPixel - количество битов, используемых для представления цвета пиксела (значение BITSPIXEL ); nPlanes - количество цветовых плоскостей (значение PLANES).

Значение NUMCOLORS равно количеству статических цветов. Так как палитра может быть изменена (перезагружена), фактически вы можете использовать больше цветов, чем указано в NUMCOLORS.

Работа с функцией PrintDlg


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

memset(&pd, 0, sizeof(PRINTDLG)); pd.lStructSize = sizeof(PRINTDLG); pd.hwndOwner = hwnd; pd.Flags = PD_RETURNDC; fResult = PrintDlg(&pd);

Если перед вызовом функции PrintDlg в полях hDevMode и hDevNames было значение NULL, функция заказывает глобальные блоки памяти для структур DEVMODE и DEVNAMES, заполняя их перед возвратом управления.

Структура DEVMODE определена в файле print.h, который находится в каталоге include системы разработки Borland Turbo C++ for Windows:

typedef struct tagDEVMODE { char dmDeviceName[CCHDEVICENAME]; UINT dmSpecVersion; UINT dmDriverVersion; UINT dmSize; UINT dmDriverExtra; DWORD dmFields; int dmOrientation; int dmPaperSize; int dmPaperLength; int dmPaperWidth; int dmScale; int dmCopies; int dmDefaultSource; int dmPrintQuality; int dmColor; int dmDuplex; int dmYResolution; int dmTTOption; } DEVMODE; typedef DEVMODE* PDEVMODE, NEAR* NPDEVMODE, FAR* LPDEVMODE;

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

Поле Описание
dmDeviceName Имя драйвера принтера
dmSpecVersion Номер версии структуры DEVMODE. Для Windows версии 3.1 это поле содержит значение 0x30a
dmDriverVersion Версия драйвера
dmSize Размер структуры DEVMODE в байтах
dmDriverExtra Размер в байтах дополнительной структуры данных, которая может находиться в памяти сразу за структурой DEVMODE
dmFields Набор флагов, каждый из которых отвечает за свое поле структуры DEVMODE. Если флаг установлен, соответствующее поле инициализируется. возможны следующие значения: DM_ORIENTATION , DM_PAPERSIZE , DM_PAPERLENGTH , DM_PAPERWIDTH , DM_SCALE , DM_COPIES , DM_DEFAULTSOURCE , DM_PRINTQUALITY , DM_COLOR , DM_DUPLEX , DM_YRESOLUTION , DM_TTOPTION
dmOrientation Ориентация бумаги. Возможные значения:DMORIENT_PORTRAIT , DMORIENT_LANDSCAPE
dmPaperSize Код размера бумаги. Например, для бумаги формата A4 используется константа DMPAPIER_A4
dmPaperLength Длина листа бумаги в десятых долях миллиметра
dmPaperWidth Ширина листа бумаги в десятых долях миллиметра
dmScale Коэффициент масштабирования для печати
dmCopies Количество печатаемых копий
dmDefaultSource Код устройства подачи бумаги, используемого по умолчанию.
dmPrintQuality Код разрешения принтера: DMRES_HIGH , DMRES_LOW , DMRES_MEDIUM , DMRES_DRAFT или положительное число, равное количеству точек на дюйм
dmColor Режим печати для цветного принтера: DMCOLOR_COLOR - цветная печать, DMCOLOR_MONOCHROME - монохромная печать
dmDuplex Возможность печати с двух сторон бумажного листа
dmYResolution Разрешение принтера по вертикали в точках на дюйм
dmTTOption Способ печати шрифтов True Type:DMTT_BITMAP - печать в графическом режиме, обычно используется для матричных принтеров;DMTT_DOWNLOAD - загрузка шрифтов True Type в память принтера, используется для лазерных принтеров, совместимых с принтерами HP LaserJet;DMTT_SUBDEV - замена шрифтов на принтерные шрифты, используется для PostScript-принтеров
<
Структура DEVNAMES , как мы уже говорили, содержит имя драйвера, имя принтера и имя порта вывода, к которому подключен принтер:

typedef struct tagDEVNAMES { UINT wDriverOffset; UINT wDeviceOffset; UINT wOutputOffset; UINT wDefault; } DEVNAMES; typedef DEVNAMES FAR* LPDEVNAMES;

Первые три слова структуры содержат смещения текстовых строк с именами, соответственно, драйвера, принтера и порта вывода. Строки расположены в памяти непосредственно за структурой DEVNAMES. Поле wDefault может содержать флаг DN_DEFAULTPRN , в этом случае все три строки описывают принтер, выбранный по умолчанию.

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

После возврата из функции PrintDlg необходимо освободить эти блоки памяти, взяв их идентификаторы из полей hDevMode и hDevNames структуры PRINTDLG. Учтите, что функция PrintDlg может изменить значения последних двух полей, поэтому надо освобождать блоки памяти с идентификаторами, взятыми из структуры PRINTDLG после возврата из функции PrintDlg:

if(pd.hDevMode != 0) GlobalFree (pd.hDevMode); if(pd.hDevNames != 0) GlobalFree (pd.hDevNames);

Если перед вызовом функции PrintDlg вы указали флаги PD_RETURNDC или PD_RETURNIC, после возврата поле hDC будет содержать, соответственно, идентификатор контекста устройства или идентификатор информационного контекста:

if(fResult) return pd.hDC; else return NULL;


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


2.1.

2.2.

2.3.

2.4.

2.5.

2.6.

2.7.

2.8.

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

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

получение или создание контекста отображения;

установка необходимых атрибутов в контексте отображения;

выполнение операций рисования;

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

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

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

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

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



Работа с принтером


6.1.

6.2.

6.3.

6.4.

Приложения Windows работают с принтером совсем не так, как программы MS-DOS. Последние используют для печати BIOS (прерывание INT17h ) или функцию 05h прерывания MS-DOS INT 21h . Некоторые программы даже используют порты ввода/вывода параллельного или последовательного интерфейса, к которому подключен принтер. Мы описали все эти средства в первой части второго тома "Библиотеки системного программиста" (стр. 156).

Заметим, что создание программы MS-DOS, способной работать с любым принтером в символьном и графическом режимах, - далеко не простая задача. В мире созданы десятки различных моделей принтеров, каждая из которых имеет свою систему команд и другие особенности. Практически приложение должно иметь собственный набор драйверов для обеспечения возможности работы с любыми моделями принтеров. Текстовый процессор Microsoft Word for DOS версий 4.0 - 6.0 может работать со многими типами принтеров благодаря богатому набору принтерных драйверов, поставляющихся вместе с ним. Тем не менее этого набора иногда оказывается недостаточно.

Ситуация с принтерами в MS-DOS напоминает ситуацию с видеоконтроллерами - так как MS-DOS не имеет в своем составе драйверы видеоконтроллеров, каждая программа MS-DOS должна иметь собственные драйверы.

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

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

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

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

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

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


Расстояние между буквами


По умолчанию при выводе текста расстояние между буквами (intercharacter spacing ), заданное в контексте отображения, равно 0. Для вывода текста "вразрядку" приложение может вызвать функцию SetTextCharacterExtra , установив дополнительное расстояние между буквами.



Реализация палитры


Процедура реализации палитры заключается в вызове функции RealizePalette :

UINT WINAPI RealizePalette(HDC hdc);

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



Режим фона


Вы можете установить два режима фона (background mode ) - непрозрачный (OPAQUE ) и прозрачный (TRANSPARENT ), вызвав функцию SetBkMode. По умолчанию выбран режим непрозрачного отображения, при котором в процессе вывода цвет фона удаляется.

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

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



Режим MM_TEXT


Режим отображения MM_TEXT устанавливается в контексте отображения по умолчанию. Для этого режима формулы преобразования координат упрощаются:

xViewport = (xWindow - xWinOrg) + xViewOrg yViewport = (yWindow - yWinOrg) + yViewOrg

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

Рис. 2.3. Система координат в режиме отображения MM_TEXT

Так как в формуле преобразования не присутствуют переменные xViewExt, yViewExt, xWinExt и yWinExt, в данном режиме преобразования невозможно изменить масштаб осей координат. Поэтому логическая единица длины в режиме отображения MM_TEXT равна физической, т. е. одному пикселу.

Тем не менее, приложение может изменить смещение физической или логической системы координат, изменив, соответственно, значение пар переменных (xViewOrg, yViewOrg) и (xWinOrg,yWinOrg). Для установки смещения можно использовать функции SetViewportOrg и SetWindowOrg.

Функция SetViewportOrg устанавливает смещение физической системы координат:

DWORD WINAPI SetViewportOrg( HDC hdc, // контекст отображения int nXOrigin, // новое значение для xViewOrg int nYOrigin); // новое значение для yViewOrg

Для контекста отображения hdc эта функция устанавливает новое расположение начала физической системы координат. Младшее и старшее слово возвращаемого значения содержат, соответственно, предыдущие x- и y-координаты начала физической системы координат.

С помощью функции SetWindowOrg вы можете установить начало логической системы координат:

DWORD WINAPI SetWindowOrg( HDC hdc, // контекст отображения int nXOrigin, // новое значение для xWinOrg int nYOrigin); // новое значение для yWinOrg

По своему смыслу параметры nXOrigin и nYOrigin являются логическими x- и y-координатами верхнего угла окна, используемого для отображения (так как в формуле они используются со знаком минус).

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


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

BOOL WINAPI SetViewportOrgEx( HDC hdc, // контекст отображения int nXOrigin, // новое значение для xWinOrg int nYOrigin, // новое значение для yWinOrg POINT FAR* lppt); // указатель на структуру POINT

BOOL WINAPI SetWindowOrgEx( HDC hdc, // контекст отображения int nXOrigin, // новое значение для xWinOrg int nYOrigin, // новое значение для yWinOrg POINT FAR* lppt); // указатель на структуру POINT

В структуру, адрес которой передается через параметр lppt, записываются старые координаты начала системы координат. Обе функции возвращают TRUE в случае успеха и FALSE при возникновении ошибки.

В любой момент времени вы можете определить расположение начала физических или логических координат, если воспользуетесь функциями GetViewportOrg и GetWindowOrg (или их более новыми аналогами - GetViewportOrgEx и GetWindowOrgEx).

Функция GetViewportOrg возвращает x- и y-координаты начала физической системы координат для контекста отображения hdc:

DWORD WINAPI GetViewportOrg(HDC hdc);

Младшее и старшее слово возвращаемого значения содержат, соответственно, предыдущие x- и y-координаты начала физической системы координат.

Функция GetWindowOrg возвращает x- и y-координаты начала логической системы координат:

DWORD WINAPI GetWindowOrg(HDC hdc);

Новые функции, появившиеся в Windows версии 3.1, с именами GetViewportOrgEx и GetWindowExtEx записывают координаты начала координат в структуру, адрес которой передается через параметры lppt и lpSize:

BOOL WINAPI GetViewportOrgEx(HDC hdc, POINT FAR* lppt); BOOL WINAPI GetWindowExtEx(HDC hdc, SIZE FAR* lpSize);

Структура SIZE определена для Windows версии 3.1 и описана в файле windows.h следующим образом:

typedef struct tagSIZE { int cx; int cy; } SIZE;

Определены также разнообразные указатели на структуру SIZE:

typedef SIZE* PSIZE; typedef SIZE NEAR* NPSIZE; typedef SIZE FAR* LPSIZE;


Режим отображения


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

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

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

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

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

В операционной системе Windows NT можно выбрать и другие системы координат, например, с наклонным расположением осей.



Режим растяжения


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

Режим растяжения (stretching mode ) влияет на способ, с помощью которого происходит масштабирование изображения bitmap. По умолчанию используется режим BLACKONWHITE , при котором два или большее количество соседних пикселов преобразуются в один пиксел при помощи логической операции "ИЛИ". В результате такого преобразования получается черный пиксел, если в исходном изображении любой пиксел из преобразуемой группы имеет черный цвет. Для того чтобы получился белый пиксел, все пикселы исходного изображения в преобразуемой группе пикселов должны быть белого цвета. В режиме BLACKONWHITE черный цвет преобладает над белым цветом.

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

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



Режим рисования


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

Приложение Windows может выбрать и другие режимы рисования, например, рисование инвертированием цвета фона, рисование черным или белым цветом, рисование с использованием логической операции "ИСКЛЮЧАЮЩЕЕ ИЛИ" или выбрать десятки других вариантов. Например, при выделении участка графического изображения (которое может быть любого цвета) приложение может рисовать рамку, размеры которой определяются перемещениями мыши. Для того чтобы рамка была видна вне зависимости от цвета изображения, при рисовании рамки можно использовать инвертирование цвета фона или операцию "ИСКЛЮЧАЮЩЕЕ ИЛИ".

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



Режим закрашивания многоугольников


Существует два режима закрашивания сложных самопересекающихся многоугольников (polygon-filling mode ): альтернативный (ALTERNATE ), выбранный в контекст отображения по умолчанию, и режим заполнения (WINDING ). В режиме заполнения область самопересечения закрашивается. В альтернативном режиме закрашиваются только области между нечетными и четными сторонами многоугольника, в результате чего область пересечения может оказаться либо закрашенной, либо незакрашенной.

Для изменения режима закрашивания многоугольников предназначена функция SetPolyFillMode .



Режимы MM_ISOTROPIC и MM_ANISOTROPIC


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

Для изменения ориентации и масштаба осей предназначены функции SetViewportExt, SetViewportExtEx, SetWindowExt и SetWindowExtEx.

Функция SetWindowExt устанавливает для формулы преобразования координат значения переменных xWinExt и yWinExt:

DWORD WINAPI SetWindowExt( HDC hdc, // идентификатор контекста отображения int nXExtent, // значение для xWinExt int nYExtent); // значение для yWinExt

Функция SetViewportExt должна использоваться после функции SetWindowExt. Она устанавливает для формулы преобразования координат значения переменных xViewExt и yViewExt:

DWORD WINAPI SetViewportExt( HDC hdc, // идентификатор контекста отображения int nXExtent, // значение для xViewExt int nYExtent); // значение для yViewExt

Обе функции возвращают в младшем и старшем слове предыдущие значения соответствующих переменных для оси X и Y.

Приведенные выше формулы можно использовать для установки отношений xViewExt/xWinExt и yViewExt/yWinExt, определяющих масштаб и направление осей координат (направление осей координат зависит от знака этих отношений).

Функции SetWindowExt передаются значения, соответствующие логическому размеру логического окна, в которое будет выполняться вывод, а функции SetViewportExt - реальные ширина и высота реального окна.

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


Если требуется получить одинаковый масштаб по осям X и Y, нужно использовать изотропный режим отображения MM_ISOTROPIC.

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

SetMapMode(hdc, MM_ISOTROPIC); SetWindowExt(hdc, 32767, 32767); SetViewportExt(hdc, cxClient, -cyClient); SetViewportOrg(hdc, 0, cyClient);

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

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



Рис. 2.7. Изменение масштаба по горизонтали при увеличении ширины окна в изотропном режиме

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



Рис. 2.8. Изменение масштаба по горизонтали при увеличении высоты окна в изотропном режиме

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



Рис. 2.9. Изменение масштаба по горизонтали при увеличении ширины окна в анизотропном режиме



Рис. 2.10. Изменение масштаба по горизонтали при увеличении высоты окна в анизотропном режиме

В программном интерфейсе Windows версии 3.1 есть новые функции, предназначенные для изменения масштабов осей. Это функции SetViewportExtEx и SetWindowExtEx :

BOOL WINAPI SetViewportExtEx( HDC hdc, // идентификатор контекста отображения int nXExtent, // значение для xViewExt int nYExtent, // значение для yViewExt SIZE FAR* lpSize); // указатель на структуру SIZE

BOOL WINAPI SetWindowExtEx( HDC hdc, // идентификатор контекста отображения int nXExtent, // значение для xWinExt int nYExtent, // значение для yWinExt SIZE FAR* lpSize); // указатель на структуру SIZE

От функций SetViewportExt и SetWindowExt эти функции отличаются тем, что старые значения переменных, определяющих масштаб преобразования, записываются в структуру SIZE, указатель на которую передается через параметр lpSize.

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

Анизотропный режим удобен в тех случаях, когда изображение должно занимать всю внутреннюю поверхность окна при любом изменении размеров окна. Соотношение масштабов при этом не сохраняется (рис. 2.9 и 2.10).


Режимы отображения


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



Рисование DIB


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

hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc);

После этого вы можете нарисовать DIB одним из двух способов.

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

Для преобразования DIB в DDB вы должны использовать функцию SetDIBits :

int WINAPI SetDIBits( HDC hdc, // контекст отображения HBITMAP hbmp, // изображение DDB UINT uStartScan, // номер первой строки UINT uScanLines, // количество строк const void FAR* lpvBits, // биты изображения BITMAPINFO FAR* lpbmi, // заголовок изображения UINT fuColorUse); // содержимое таблицы цветов

Параметр hdc должен содержать идентификатор контекста отображения, в котором будет отображаться полученное изображение DDB.

Через параметр hbmp следует передать идентификатор битового изображения DDB, совместимого с контекстом hdc. Его можно создать при помощи функции CreateCompatibleBitmap. После преобразования это изображение можно будет использовать для рисования функциями BitBlt или StretchBlt.

Параметр uStartScan задает номер строки сканирования битового изображения, начиная с которого будет выполняться преобразование. Если вам нужно нарисовать все изображение целиком, для этого параметра следует задать значение0.

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

Через параметр lpvBits следует передать указатель на область памяти, содержащую биты изображения в формате DIB.

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


Последний параметр fuColorUse указывает функции на содержимое таблицы цветов, которая расположена сразу после структуры BITMAPINFOHEADER. Возможны два значения - DIB_RGB_COLORS и DIB_PAL_COLORS.

Если указано значение DIB_RGB_COLORS , таблица цветов содержит RGB-цвета, которые можно использовать для создания палитры. Если же указано значение DIB_PAL_COLORS , таблица цветов содержит 16-битовые ссылки на элементы системной палитры.

Если вы загрузили bmp-файл в память, таблица цветов обычно содержит именно RGB-цвета, поэтому для преобразования и последующего рисования изображения вы должны указать значение DIB_RGB_COLORS.

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

Поясним процесс рисования на простом примере.

Пусть мы загрузили изображение DIB с шириной wWidth и высотой wHeight. Создаем изображение DDB, совместимое с контекстом отображения hdc и имеющее те же размеры. Для этого воспользуемся функцией CreateCompatibleBitmap :

hbmp = CreateCompatibleBitmap(hdc, wWidth, wHeight);

Создадим также контекст памяти, совместимый с контекстом отображения:

hMemDC = CreateCompatibleDC(hdc);

Далее вызываем функцию SetDIBits, которая преобразует биты изображения DIB и запишет их в созданное нами изображение DDB с идентификатором hbmp:

SetDIBits(hdc, hbmp, 0, wHeight, lpDibBits, (LPBITMAPINFO)lpih, DIB_RGB_COLORS);

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

hbmp = (HBITMAP)SelectObject(hMemDC, hbmp); BitBlt(hdc, x, y, wWidth, wHeight, hMemDC, 0, 0, SRCCOPY);

Все! Изображение нарисовано. Теперь можно удалить контекст памяти, не забыв перед этим выбрать в него старое битовое изображение (размером 1х1 пиксел):

DeleteObject(SelectObject(hMemDC, hbmp)); DeleteDC(hMemDC);

Второй способ нарисовать DIB немного проще:

StretchDIBits(hdc, x, y, wWidth, wHeight, 0, 0, wWidth, wHeight, lpDibBits, (LPBITMAPINFO)lpih, DIB_RGB_COLORS, SRCCOPY);



Прототип функции StretchDIBits выглядит несколько громоздко, однако эта функция дополнительно позволяет масштабировать рисуемое изображение. Функция имеет параметры, аналогичные параметрам функций StretchBlt и SetDIBits:

BOOL WINAPI StretchDIBits( HDC hdc, // контекст для рисования int nXDest, // x-координата верхнего левого угла // области рисования int nYDest, // y-координата верхнего левого угла // области рисования int nWidthDest, // новая ширина изображения int nHeightDest, // новая высота изображения int nXSrc, // x-координата верхнего левого угла // исходной области int nYSrc, // y-координата верхнего левого угла // исходной области int nWidthSrc, // ширина исходного изображения int nHeightSrc, // высота исходного изображения const void FAR* lpvBits, // биты изображения BITMAPINFO FAR* lpbmi, // заголовок изображения UINT fuColorUse, // содержимое таблицы цветов DWORD dwRop); // код растровой операции

Возвращаемое значение равно количеству преобразованных строк сканирования или нулю при ошибке.


Рисование дуги эллипса


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

BOOL WINAPI Arc( HDC hdc, // идентификатор контекста отображения int nxLeft, int nyTop, // верхий левый угол int nxRight, int nyBottom, // правый нижний угол int nxStart, int nyStart, // начало дуги int nxEnd, int nyEnd); // конец дуги

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

Рис. 2.13. Рисование дуги эллипса

Параметры (nxLeft,nyTop) и (nxRight,nyBottom) задают координаты, соответственно, верхнего левого и правого нижнего углов воображаемого прямоугольника, в который вписан эллипс.

Начало дуги эллипса определяется пересечением эллипса с воображаемой прямой линией, проведенной из центра эллипса (xC,yC) в точку (xStart,yStart).

Конец дуги определяется аналогично - как пересечение эллипса с воображаемой прямой линии, проведенной из центра эллипса в точку (xEnd,yEnd).

Дуга рисуется в направлении против часовой стрелки.

Координаты центра эллипса (если это потребуется) можно вычислить следующим образом:

xC = (nxLeft + nxRight) / 2; yC = (nyTop + nyBottom) / 2;