Определение логического шрифта
Определение логического шрифта
Если приложение ограничивает себя только встроенными шрифтами, оно не сможет воспользоваться всем многообразием созданных масштабируемых шрифтов и даже просто не сможет изменить размер букв или сделать текст жирным или наклонным. Для полного использования шрифтовых возможностей операционной системы 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.
Основные определения
Основные определения
Прежде всего необходимо определить понятия "физические координаты " и "логические координаты ".
Физические координаты, как это следует из названия, имеют непосредственное отношение к физическому устройству вывода. В качестве единицы измерения длины в системе физических координат всегда используется пиксел. Если устройством вывода является экран монитора, физические координаты обычно называют экранными координатами.
Логические координаты передаются функциям 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 изображены линии, нарисованные с использованием перьев различной ширины и стиля.
Перо
Перо
Для того чтобы нарисовать линию или геометрическую фигуру, приложение Windows должно создать собственное перо (pen ) или воспользоваться пером, выбранным в контекст отображения по умолчанию (черное перо шириной в один пиксел).
Можно создать новое перо, изменив его ширину, цвет или стиль (сплошная, пунктирная, штриховая и штрих-пунктирная линии, а также линия, в которой на одну черточку приходится по две точки). К сожалению, вы можете изменить ширину только сплошной линии.
Для работы с перьями приложение Windows должно использовать функции CreatePen , CreatePenIndirect , SelectObject .
Преобразование DDB в DIB
Преобразование 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 преобразует их в физические с использованием следующих формул:
Прямоугольная область
Прямоугольная область
Для создания прямоугольной области предназначены функции 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, предназначены, соответственно, для определения размеров воображаемого эллипса, формирующего скругленные углы.
Работа с функцией PrintDlg
Работа с функцией 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. Из за ограниченного объема книги мы приведем краткое описание полей этой структуры.
Расстояние между буквами
Расстояние между буквами
По умолчанию при выводе текста расстояние между буквами (intercharacter spacing ), заданное в контексте отображения, равно 0. Для вывода текста "вразрядку" приложение может вызвать функцию SetTextCharacterExtra , установив дополнительное расстояние между буквами.
Режим фона
Режим фона
Вы можете установить два режима фона (background mode ) - непрозрачный (OPAQUE ) и прозрачный (TRANSPARENT ), вызвав функцию SetBkMode. По умолчанию выбран режим непрозрачного отображения, при котором в процессе вывода цвет фона удаляется.
Например, приложение создало окно с синим фоном и выводит в нем строку текста черного цвета. В этом случае в режиме OPAQUE вы увидите черные буквы внутри горизонтальной полосы белого цвета, имеющей высоту, равную высоте букв. Если в этом режиме нарисовать пунктирную линию черного цвета на синем фоне, то цвет промежутка между штрихами линии будет не синий, а белый.
В прозрачном режиме TRANSPARENT аналогия с листом бумаги синего цвета и черным карандашом будет полная. При выводе текста в описанной выше ситуации вы увидите черные буквы на синем фоне.
Режим MM_TEXT
Режим MM_TEXT
Режим отображения MM_TEXT устанавливается в контексте отображения по умолчанию. Для этого режима формулы преобразования координат упрощаются: xViewport = (xWindow - xWinOrg) + xViewOrg yViewport = (yWindow - yWinOrg) + yViewOrg
Соответствующая система координат представлена на Рисунок 2.3 (начало системы координат расположено точно в левом верхнем углу внутренней области окна, рисунок иллюстрирует только направление координатных осей).
Режим отображения
Режим отображения
Режим отображения, установленный в контексте отображения, влияет на систему координат. Устанавливая различные режимы отображения , приложение может изменять направление и масштаб координатных осей.
По умолчанию в контексте отображения установлен режим отображения 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
Режимы отображения 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).
Режимы отображения
Режимы отображения
Теперь, после того как мы рассказали о физических и логических координатах, а также о преобразованиях координат, займемся подробным описанием каждого режима отображения .
Вывод данных через контекст устройства
Рис 1.1. Вывод данных через контекст устройства
Если приложение получает или создает контекст для устройства отображения, такой контекст называется контекстом отображения. Поэтому когда, например, приложение получает контекст для отображения в одном из своих окон, такой контекст называется контекстом отображения. Если же ему требуется выполнять операцию вывода для устройства (для принтера или для экрана дисплея), приложение должно получить или создать контекст устройства. Однако следует понимать, что контексты устройства и отображения содержат описания одних и тех же характеристик и имеют одинаковую структуру. Название контекста определяется только тем, относится ли контекст к окну отображения или устройству вывода.
Линии разной ширины нарисованные с использованием различных стилей
Рис 1.2. Линии разной ширины, нарисованные с использованием различных стилей
Как мы уже говорили, интерфейс GDI операционной системы Windows версии 3.1 позволяет рисовать сплошные линии различной ширины, а также пунктирные, штриховые и штрих-пунктирные линии шириной в 1 пиксел. Можно запросить любой цвет для рисования линии, однако вовсе не обязательно линия будет нарисована запрошенным цветом. Тонкости, связанные с цветом и цветовыми палитрами, мы рассмотрим позже.
На Рисунок 1.3. показаны геометрические фигуры, нарисованные с использованием различных перьев, сплошных и пунктирных.
Геометрические фигуры нарисованные с использованием различных перьев
Рис 1.3. Геометрические фигуры, нарисованные с использованием различных перьев
Геометрические фигуры закрашенные с использованием встроенных кистей
Рис 1.4. Геометрические фигуры, закрашенные с использованием встроенных кистей
Если вас не устраивает ни одна из встроенных кистей, вы можете создать собственную, определив ее как изображение bitmap размером 8х8 пикселов. Такая кисть может иметь любой внешний вид и любой цвет.
иллюстрирует ухудшение
Рис 1.5 иллюстрирует ухудшение внешнего вида растрового и векторного шрифтов при увеличении размера букв до величины 40 пунктов. Внешний вид масштабируемого шрифта не ухудшился.
Растровый векторный и
Битовые изображения
Если вам нужно вывести на экран сложное изображение, полученное при помощи сканера и содержащее множество мелких деталей, едва ли имеет смысл рисовать его при помощи перьев и кистей. Хотя последнее и возможно, такая процедура будет выполняться слишком долго.
В битовом изображении bitmap каждый пиксел представлен своим цветом. Есть черно-белые и цветные изображения, изображения, рассчитанные на определенный тип устройства вывода (использовались в ранних версиях Windows) и не зависящие от типа устройства вывода (впервые появились в Windows версии 3.0).
Рисование битовых изображений выполняется путем копирования соответствующего массива графических данных в видеопамять. Разумеется, приложение не имеет непосредственного доступа к видеопамяти. Для выполнения операции копирования приложение обращается к GDI, вызывая соответствующую функцию. Как правило, копирование битовых изображений выполняется не GDI, а драйвером или даже аппаратурой видеоконтроллера, что благоприятно сказывается на скорости выполнения операции вывода.
Работа с битовыми изображениями - не самое простое из того, что может быть в программировании для Windows. Особенно это касается использования битовых изображений в формате, не зависящем от типа устройства вывода или содержащих цветовые палитры. Ситуация дополнительно усложняется отсутствием единого формата bmp-файлов, содержащих изображения и необходимостью (в профессиональных приложениях) распознавать bmp-файлы, подготовленные в операционной системе OS/2, а также контролировать формат bmp-файлов. Однако только битовые изображения дают возможность получить на экране компьютера красивые рисунки, приближающиеся (на хороших мониторах) по качеству к слайдам, а также работать с движущимися изображениями.
Физическая система координат для экрана видеомонитора
Рис 2.1. Физическая система координат для экрана видеомонитора
Начало этой системы координат располагается в левом верхнем углу экрана. Ось X направлена слева направо, ось Y - сверху вниз. В качестве единицы длины в данной системе координат используется пиксел.
Для того чтобы определить физическое разрешение устройства вывода (например, размер экрана в пикселах по вертикали и горизонтали), следует использовать функцию GetDeviceCaps , которую мы рассмотрели в 11 томе "Библиотеки системного программиста". Если передать в качестве второго параметра этой функции значения VERTRES и HORZRES , она в любом режиме отображения вернет, соответственно, размер экрана в пикселах по вертикали и по горизонтали. Параметр hdc должен указывать информационный контекст или контекст отображения, связанный с экраном монитора: HDC hdc; int iVertRes, iHorzRes; hdc = CreateDC("DISPLAY", NULL, NULL, NULL); iVertRes = GetDeviceCaps(hdc, VERTRES); iHorzRes = GetDeviceCaps(hdc, HORZRES); DeleteDC(hdc);
Физическая система координат "привязана" к физическому устройству вывода, поэтому при ее использовании для вывода изображения следует учитывать особенности видеоконтроллера. В 11 томе "Библиотеки системного программиста" в разделе, посвященном метрикам операционной системы Windows, мы подробно рассказали об использовании функции GetDeviceCaps для исследования пространственных характеристик монитора. Для удобства мы воспроизведем приведенную в этом томе таблицу некоторых метрик для видеомониторов различных типов.
Одна из возможных систем координат
Рис 2.2. Одна из возможных систем координат
Для установки режима отображения, непосредственно определяющего направление осей и размер логической единицы системы координат, используется функция SetMapMode : int WINAPI SetMapMode(HDC hdc, int nMapMode);
Для контекста отображения hdc эта функция устанавливает новый режим отображения, заданный параметром nMapMode, возвращая номер режима отображения, который был установлен раньше.
Параметр nMapMode может принимать одно из следующих значений.
Система координат в режиме отображения MM_TEXT
Рис 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;
Ориентация осей сразу после переключения в метрический режим отображения
Рис 2.4. Ориентация осей сразу после переключения в метрический режим отображения
Ось X, как и следовало ожидать, окажется направленной слева направо, а ось Y - снизу вверх. Точка с координатами (0,0) будет находиться в верхнем левом углу экрана, поэтому для того чтобы нарисовать что-нибудь в такой системе координат, вам придется для y-координаты графических объектов использовать отрицательные числа. Для того чтобы система координат приняла более удобный вид, можно переместить начало физических координат в нижний левый угол окна или в центр окна.
Прежде, чем выполнять перемещение начала координат, следует определить размеры внутренней области окна. Это можно сделать при обработке сообщения WM_SIZE : static short cxClient, cyClient; .... case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); .... return 0; }
Для того чтобы расположить начало координат в левом нижнем углу окна, следует вызвать функцию SetViewportOrg, передав ей новые координаты начала физической системы координат (0,cyClient): SetViewportOrg(hdc, 0, cyClient);
Полученная в результате система координат показана на Рисунок 2.5.
Метрическая система координат начало координат находится в левом нижнем углу окна
Рис 2.5. Метрическая система координат, начало координат находится в левом нижнем углу окна
Аналогичным образом можно расположить начало системы координат в середине окна (Рисунок 2.6), обеспечив возможность использования положительных и отрицательных координат вдоль оси X и Y: SetViewportOrg(hdc, cxClient/2, cyClient/2);
Метрическая система координат начало координат находится в центре окна
Рис 2.6. Метрическая система координат, начало координат находится в центре окна
Изменение масштаба по горизонтали при увеличении ширины окна в изотропном режиме
Рис 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).
Рисование прямой линии
Рис 2.11. Рисование прямой линии
Если вас не устраивает необходимость пользоваться двумя функциями для рисования линии, вы можете создать свою собственную, например такую: BOOL DrawLine(HDC hdc, int x1, int y1, int x2, int y1) { POINT pt; MoveToEx(hdc, x1, y1, &pt); return LineTo(hdc, x2, y2); }
Преимущества использования отдельных функций для установки текущей позиции и для рисования линии из текущей позиции в заданную точку с последующим изменением текущей позиции проявляются при рисовании ломаных линий. В этом случае вы можете только один раз установить текущую позицию пера на начало ломаной линии и в дальнейшем вызывать только функцию LineTo, передавая ей координаты точек излома линии. Однако для рисования ломаных линий (если известны координаты всех точек излома) больше подходит функция Polyline, которую мы рассмотрим в следующем разделе.
Рисование ломаной линии
Рис 2.12. Рисование ломаной линии
Рисование дуги эллипса
Рис 2.13. Рисование дуги эллипса
Параметры (nxLeft,nyTop) и (nxRight,nyBottom) задают координаты, соответственно, верхнего левого и правого нижнего углов воображаемого прямоугольника, в который вписан эллипс.
Начало дуги эллипса определяется пересечением эллипса с воображаемой прямой линией, проведенной из центра эллипса (xC,yC) в точку (xStart,yStart).
Конец дуги определяется аналогично - как пересечение эллипса с воображаемой прямой линии, проведенной из центра эллипса в точку (xEnd,yEnd).
Дуга рисуется в направлении против часовой стрелки.
Координаты центра эллипса (если это потребуется) можно вычислить следующим образом: xC = (nxLeft + nxRight) / 2; yC = (nyTop + nyBottom) / 2;
Использование стилей PS_SOLID и PS_INSIDEFRAME
Рис 2.14. Использование стилей PS_SOLID и PS_INSIDEFRAME
Еще одно отличие связано с использованием смешанных цветов (dithered color). Когда Windows не может в точности подобрать цвет, указанный для толстой линии стиля PS_INSIDEFRAME, он раскрашивает эту линию с использованием смешанного цвета, полученного из других цветов. В этом случае изображение линии формируется из отдельных точек разных цветов. Техника смешанных цветов может применяться и при закрашивании замкнутых фигур, о чем мы еще будем говорить.
При рисовании тонких линий, а также линий, имеющих другой стиль, используются только чистые цвета.
Небольшое замечание относительно концов толстых линий. Концы толстых линий закруглены (Рисунок 2.15).
Закругленные концы толстой линии
Рис 2.15. Закругленные концы толстой линии
Для изображения толстой линии с прямыми концами следует задать прямоугольную область ограничения (см. ниже раздел, посвященный области ограничения). Можно также нарисовать толстую линию как закрашенный прямоугольник с использованием тонкого пера.
Другая возможность создать перо - вызвать функцию CreatePenIndirect : HPEN WINAPI CreatePenIndirect(LOGPEN FAR* lplgpn);
Эта функция работает аналогично функции CreatePen, однако в качестве параметра ей необходимо передать указатель на структуру типа LOGPEN, в которой должны находиться характеристики создаваемого пера.
Структура LOGPEN и различные указатели на нее определены в файле windows.h: typedef struct tagLOGPEN { UINT lopnStyle; // стиль пера POINT lopnWidth; // ширина пера COLORREF lopnColor; // цвет пера } LOGPEN; typedef LOGPEN* PLOGPEN; typedef LOGPEN NEAR* NPLOGPEN; typedef LOGPEN FAR* LPLOGPEN;
Заметим, что ширина пера в данном случае находится в поле x структуры POINT. Поле y не используется.
Если вы создали перо, его можно выбрать в контекст отображения при помощи макрокоманды SelectPen. После этого можно рисовать линии обычным образом, вызывая функции MoveToEx и LineTo.
Созданные вашим приложением перья принадлежат GDI, соответствующие структуры данных располагаются в его сегменте данных. Поэтому если перо больше не нужно, его нужно удалить для освобождения памяти.
Прежде чем удалять созданное вами перо, следует выбрать в контекст отображения одно из встроенных перьев (например то, которое использовалось раньше). После этого для удаления вашего пера нужно вызвать макрокоманду DeleletePen , определенную в файле windowsx.h: #define DeletePen(hpen) DeleteObject((HGDIOBJ)(HPEN)(hpen))
В качестве параметра этой макрокоманде необходимо передать идентификатор удаляемого пера.
Нельзя удалять перо, если оно выбрано в контекст отображения. Нет никакого смысла в удалении встроенных перьев.
Режимы фона OPAQUE и TRANSPARENT
Рис 2.16. Режимы фона OPAQUE и TRANSPARENT
Для установки режима фона предназначена функция SetBkMode : int WINAPI SetBkMode(HDC hdc, int fnBkMode);
Эта функция устанавливает новый режим фона fnBkMode для контекста отображения hdc, возвращая в случае успеха код старого режима фона.
Для параметра fnBkMode вы можете использовать значения OPAQUE или TRANSPARENT, определенные в файле windows.h.
Приложение может определить текущий режим фона, вызвав функцию GetBkMode : int WINAPI GetBkMode(HDC hdc);
С помощью функций SetBkColor и GetBkColor вы можете, соответственно, установить и определить текущий цвет фона, который используется для закраски промежутков между штрихами и точками линий: COLORREF WINAPI SetBkColor(HDC hdc, COLORREF clrref); COLORREF WINAPI GetBkColor(HDC hdc);
Рисование прямоугольника
Рис 2.17. Рисование прямоугольника
Как видно из этого рисунка, последние четыре параметра функции задают координаты верхнего левого и нижнего правого угла прямоугольника.
В зависимости от стиля пера граница фигуры может находится полностью внутри прямоугольника, заданного координатами (nxTL, nyTL), (nxBR,nyBR) или выходить за его пределы (см. Рисунок 2.14). Если выбрать стиль пера PS_NULL, граница фигуры станет невидимой.
В зависимости от кисти, выбранной в контекст отображения, внутренность прямоугольника может быть закрашенной в тот или иной цвет, заштрихована одним из нескольких способов (как показано на Рисунок 2.16) или закрашена с помощью любого битового изображения размером 8х8 пикселов.
С помощью функции RoundRect можно нарисовать прямоугольник со скругленными углами (Рисунок 2.18).
Прямоугольник со скругленными углами
Рис 2.18. Прямоугольник со скругленными углами
По сравнению с функцией Rectangle функция RoundRect имеет два дополнительных параметра nxEllipse и nyEllipse, определяющих форму и радиус закругления: BOOL WINAPI RoundRect( HDC hdc, // идентификатор контекста отображения int nxTL, // координата x верхнего левого угла int nyTL, // координата y верхнего левого угла int nxBR, // координата x правого нижнего угла int nyBR, // координата y правого нижнего угла int nxEllipse, // ширина эллипса int nyEllipse); // высота эллипса
Есть и другие функции, которые можно использовать для рисования прямоугольников.
Функция FillRect закрашивает прямоугольную область окна заданной кистью: int WINAPI FillRect( HDC hdc, // идентификатор контекста отображения const RECT FAR* lprc, // указатель на структуру RECT HBRUSH hbrush); // идентификатор кисти для закрашивания
Параметр lprc должен указывать на структуру типа RECT, в которую следует записать координаты закрашиваемой прямоугольной области. Правая и нижняя граница указанной области не закрашивается.
Независимо от того, какая кисть выбрана в контекст отображения, функция FillRect будет использовать для закраски кисть, указанную параметром hbrush.
Учтите, что правильная работа функции FillRect гарантируется только в том случае, когда значение поля bottom структуры RECT больше значения поля top, а значение поля right больше значения поля left.
Для закрашивания границы прямоугольной области (т. е. для рисования прямоугольной рамки) можно использовать функцию FrameRect : int WINAPI FrameRect( HDC hdc, // идентификатор контекста отображения const RECT FAR* lprc, // указатель на структуру RECT HBRUSH hbrush); // идентификатор кисти для закрашивания
Параметры этой функции аналогичны параметрам функции FillRect.
Ширина пера, используемого для рисования рамки, всегда равна одной логической единице. Структура RECT должна быть подготовлена таким же образом, что и для функции FillRect, т. е. значение поля bottom структуры RECT должно быть больше значения поля top, а значение поля right - больше значения поля left.
Значение, возвращаемое функциями FillRect и FrameRect не используется, приложения должны его игнорировать.
Используя функцию InvertRect , вы можете инвертировать содержимое прямоугольной области, заданной параметром lprc: void WINAPI InvertRect(HDC hdc, const RECT FAR* lprc);
Есть еще одна интересная функция, предназначенная для рисования прямоугольников. Она имеет имя DrawFocusRect : void WINAPI DrawFocusRect(HDC hdc, const RECT FAR* lprc);
Эта функция рисует прямоугольную рамку, предназначенную для выделения окна, имеющего фокус ввода.
Функция DrawFocusRect имеет три интересные особенности.
Во-первых, для рисования используется растровая операция "ИСКЛЮЧАЮЩЕЕ ИЛИ". Это приводит к тому, что для удаления нарисованной таким образом рамки ее достаточно нарисовать еще раз на том же месте.
Вторая особенность заключается в том, что для использования этой функции не нужно выбирать перо, рисующее пунктирную линию. Функция DrawFocusRect рисует пунктирную линию с нестандартным, очень близким расположением точек.
Третья особенность заключается в том, что перед использованием этой функции необходимо установить режим отображения MM_TEXT.
Первые две особенности позволяют использовать ее для рисования рамки выделения произвольных участков изображения на экране монитора (при помощи мыши).
В заключение отметим, что в программном интерфейсе Windows нет функции для рисования квадрата и круга. Эти фигуры являются частными случаями, соответственно, прямоугольника и эллипса, поэтому для рисования, например, квадрата, вы должны использовать одну из только что описанных функций. Для сохранения пропорций проще всего использовать одну из метрических систем координат.
Рисование эллипса
Рис 2.19. Рисование эллипса
Рисование сегмента эллипса
Рис 2.20. Рисование сегмента эллипса
Рисование сектора эллипса
Рис 2.21. Рисование сектора эллипса
Рисование многоугольника
Рис 2.22. Рисование многоугольника
В массиве структур POINT, определяющих вершины многоугольника, каждая вершина должна быть указана один раз. Функция Polygon автоматически замыкает ломаную линию, образующую многоугольник.
С помощью функции PolyPolygon можно нарисовать одновременно несколько многоугольников: BOOL WINAPI PolyPolygon( HDC hdc, // идентификатор контекста отображения const POINT FAR*lppt, // указатель на массив структур POINT int FAR* lpnPolyCounts, // адрес массива количества точек // в многоугольниках int cPolygons); // количество многоугольников
Первый параметр hdc, как обычно, задает контекст отображения.
Параметр cPolygons определяет количество многоугольников, которые нужно нарисовать.
Параметр lppt должен содержать указатель на массив структур типа POINT, содержащий координаты вершин всех многоугольников.
И, наконец, через параметр lpnPolyCounts передается указатель на массив целых чисел. Каждое число в этом массиве определяет количество точек в соответствующем многоугольнике.
В отличие от функции Polygon, функция PolyPolygon не замыкает автоматически ломаную линию, образующую многоугольник.
В контексте отображения имеется атрибут, влияющий на способ закрашивания для самопересекающихся многоугольников. По умолчанию выбран режим ALTERNATE , в котором эти области не закрашиваются (закрашиваются только те области, которые расположены между нечетными и четными сторонами многоугольника).
С помощью функции SetPolyFillMode вы можете изменить значение этого атрибута на WINDING . В этом режиме для того чтобы определить, надо ли закрашивать область многоугольника, учитывается направление, в котором был нарисован этот многоугольник. Каждая сторона многоугольника может быть нарисована в направлении либо по часовой стрелке, либо против часовой стрелки. Если воображаемая линия, нарисованная в направлении из внутренней области многоугольника в наружную, пересекает сегмент, нарисованный в направлении по часовой стрелке, содержимое некоторого внутреннего счетчика увеличивается на единицу. Если же эта линия пересекает сегмент, нарисованный против часовой стрелки, содержимое счетчика уменьшается на единицу. Область закрашивается только в том случае, если содержимое счетчика не равно нулю.
Немного позже вы сможете изучить этот алгоритм с помощью приложения LINER.
Приведем прототип функции SetPolyFillMode: int WINAPI SetPolyFillMode(HDC hdc, int fnMode);
Параметр fnMode, определяющий режим закрашивания многоугольников, может принимать значения ALTERNATE или WINDING. Функция возвращает код старого режима закрашивания.
Вы можете определить используемый в данный момент режим закрашивания многоугольников с помощью функции GetPolyFillMode : int WINAPI GetPolyFillMode(HDC hdc);
Приложение LINER
Рис 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
Вывод текста с использованием области ограничения
Рис 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
Пунктирные линии увеличенной толщины
Рис 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