Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
gdi.doc
Скачиваний:
10
Добавлен:
15.09.2019
Размер:
306.18 Кб
Скачать

2. Рисование графических примитивов с помощью функций gdi

2.1 Рисование отрезков и кривых

Основные (хотя и не все) функции-члены CDC, предназначенные для рисования

отрезков и кривых, приведены в таблице 5.5.

Таблица 5.5. Функции-члены CDC для рисования отрезков и кривых

Функция

MoveTo

Задает текущую позицию

Назначение

LineTo

Polyline

PolylineTo

Arc

ArcTo

PolyBezier

Рисует отрезок из текущей позиции в заданную точку и смещает в нее

текущую позицию

Последовательно соединяет набор точек отрезками

Соединяет набор точек отрезками прямых, начиная с текущей позиции.

Текущая позиция смещается в последнюю точку набора.

Рисует дугу

Рисует дугу и смещает текущую позицию в конец дуги

Рисует один или несколько сплайнов Безье

PolyBezierTo Рисует один или несколько сплайнов Безье и помещает текущую пози-

цию в конец последнего сплайна

PolyDraw

Рисует набор отрезков и сплайнов Безье через набор точек и смещает

текущую позицию в конец последнего отрезка или сплайна

Для рисования отрезка надо поместить текущую позицию в один из концов от-

резка и вызвать LineTo с координатами второго конца:

dc.MoveTo( 0, 0 );

dc.LineTo( 0, 100 );

При выводе нескольких соединяющихся отрезков MoveTo достаточно вызвать

только для одного из концов первого отрезка, например:

dc.MoveTo( 0, 0 );

dc.LineTo( 0, 100 );

dc.LineTo( 100, 100 );

Несколько отрезков можно построить одним вызовом Polyline или

PolylineTo (отличие между ними в том, что PolylineTo пользуется текущей

позицией, а Polyline – нет). Например, квадрат можно нарисовать так:

POINT aPoint[5] = { 0, 0, 0, 100, 100, 100, 100, 0, 0, 0 };

dc.Polyline( aPoint, 5 );

или с помощью PolylineTo:

dc.MoveTo( 0, 0 );

POINT aPoint[4] = { 0, 100, 100, 100, 100, 0, 0, 0 };

dc.PolylineTo( aPoint, 4 );

Для рисования дуг окружностей и эллипсов предназначена функция CDC::Arc.

В качестве параметров ей передаются координаты описывающего эллипс прямо-

угольника и координаты начальной и конечной точек дуги (эти точки задают углы

для вырезания дуги из эллипса, поэтому могут точно на него не попадать). Ниже при-

веден пример для рисования левой верхней четверти эллипса шириной 200 единиц и

высотой 100 единиц:

CRect rect(0, 0, 200, 100);

CPoint point1(0, -500);

CPoint point2(-500, 0);

dc.Arc(rect, point1, point2);

61

Важная особенность всех функций GDI для рисования отрезков и кривых в

том, что последняя точка не рисуется. Т.е. при рисовании отрезка из точки (0, 0) в

точку (100, 100):

dc.MoveTo( 0, 0 );

dc.LineTo( 100, 100 );

пиксел (100, 100) принадлежать отрезку не будет. Если необходимо, чтобы последний

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

CDC::SetPixel, предназначенной для закраски отдельных пикселов.

2.2 Рисование эллипсов, многоугольников и других фигур

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

кривые. Некоторые из них перечислены в табл. 5.6.

Таблица 5.6. Функции-члены CDC для рисования замкнутых фигур

Функция

Chord

Ellipse

Pie

Polygon

Описание

Замкнутая фигура, образованная пересечением эллипса и отрезка

Эллипс или окружность

Сектор круговой диаграммы

Многоугольник

Rectangle Прямоугольник

RoundRect Прямоугольник с закругленными углами

Функциям GDI, рисующим замкнутые фигуры, передаются координаты описы-

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

ность, надо указать не центр и радиус, а описывающий квадрат, например:

dc.Ellipse( 0, 0, 100, 100 );

Координаты описывающего прямоугольника можно передавать в виде структу-

ры RECT или как объект CRect:

CRect rect( 0, 0, 100, 100 );

dc.Ellipse( rect );

Как и последняя точка отрезка, нижняя строка и правый столбец описывающе-

го прямоугольника не заполняются. Т.е. при вызове CDC::Rectangle:

dc.Rectangle( 0, 0, 8, 4 );

результат будет такой, как на рис. 5.2.

Рис. 5.2. Прямоугольник, нарисованный вызовом dc.Rectangle(0,0,8,4)

62

2.3 Перья GDI и класс CPen

Для рисования отрезков, кривых и контуров фигур GDI использует объект-

перо, выбранное в контексте устройства. По умолчанию перо рисует сплошную чер-

ную линию толщиной 1 пиксел. Изменить вид линий можно, если создать соответст-

вующий объект-перо и выбрать его в контексте устройства функцией

CDC::SelectObject.

В MFC перья GDI представляются в виде объектов класса CPen. Проще всего

указать характеристика пера в конструкторе CPen, например:

CPen pen( PS_SOLID, 1, RGB(255, 0, 0) );

Второй способ: создать неинициализированный объект CPen, а затем создать

перо GDI вызовом CPen::CreatePen:

CPen pen;

pen.CreatePen( PS_SOLID, 1, RGB(255, 0, 0) );

Третий способ: создать неинициализированный объект CPen, заполнить струк-

туру LOGPEN характеристиками пера, а затем вызвать CPen::CreatePenIndirect

для создания пера GDI:

CPen pen;

LOGPEN lp;

lp.lopnStyle = PS_SOLID;

lp.lopnWidth.x = 1;

lp.lopnColor = RGB(255, 0, 0);

pen.CreatePenIndirect(&lp);

В структуре LOGPEN поле lopnWidth имеет тип POINT, но координата y не ис-

пользуется, а x задает толщину пера.

Функции CreatePen и CreatePenIndirect возвращают TRUE, если перо бы-

ло успешно создано (FALSE – если перо создать не удалось).

У пера есть три параметра: стиль, толщина и цвет. Возможные стили показаны

на рис. 5.3.

PS_DASHDOT

PS_SOLID

PS_DASH

PS_DOT

PS_DASHDOTDOT

PS_NULL

PS_INSIDEFRAME

Рис. 5.3. Стили пера.

Стиль PS_INSIDEFRAME предназначен для рисования линий, которые всегда

располагаются внутри описывающего прямоугольника фигуры. Допустим, вы рисуе-

те окружность диаметром 100 единиц пером PS_SOLID толщиной 20 единиц. Тогда

реальный диаметр окружности по внешней границе будет 120 единиц (см. рис.5.4).

Если ту же окружность нарисовать пером стиля PS_INSIDEFRAME, то диаметр ок-

ружности будет действительно 100 единиц. На рисование отрезков и других прими-

тивов, не имеющих описывающего прямоугольника, стиль PS_INSIDEFRAME не влия-

ет.

63

Рис. 5.4. Стиль пера PS_INSIDEFRAME.

Стиль PS_NULL бывает нужен для рисования фигур без контура (например, эл-

липсов), только с заполнением внутренней области.

Толщина пера задается в логических единицах. Перья стилей PS_DASH,

PS_DOT, PS_DASHDOT и PS_DASHDOTDOT должны быть обязательно толщиной 1 еди-

ница. Если задать толщину 0 единиц, то будет создано перо шириной 1 пиксел неза-

висимо от режима преобразования координат.

Чтобы использовать новое перо, его надо выбрать в контексте устройства. На-

пример, чтобы нарисовать эллипс красным пером толщиной 10 единиц, можно вы-

полнить следующие действия:

CPen pen( PS_SOLID, 10, RGB(255, 0, 0) );

CPen* pOldPen = dc.SelectObject( &pen );

dc.Ellipse( 0, 0, 100, 100 );

2.4 Кисти GDI и класс CBrush

По умолчанию внутренняя область замкнутых фигур (Rectangle, Ellipse и

т.п.) заполняется белыми пикселами. Цвет и стиль заливки определяется параметрами

кисти, выбранной в контексте устройства.

В MFC кисть представляется классом CBrush. Бывают три типа кистей: сплош-

ные, штриховые и шаблонные. Сплошные кисти рисуют одним цветом. Для штрихо-

вых кистей есть 6 предопределенных стилей, они чаще всего используются в инже-

нерных и архитектурных чертежах (рис. 5.5). Шаблонные кисти рисуют путем повто-

рения небольшой битовой карты. У класса CBrush есть конструкторы для создания

кистей каждого типа.

Рис. 5.5. Стили штриховых кистей.

Для создания сплошной кисти в конструкторе CBrush достаточно указать зна-

чение цвета:

64

CBrush brush( RGB(255, 0, 0) );

или создать кисть в два этапа (сначала объект MFC, затем объект GDI):

CBrush brush;

brush.CreateSolidBrush( RGB(255, 0, 0) );

При создании штриховых кистей в конструкторе CBrush указываются стиль и

цвет кисти, например:

CBrush brush( HS_DIAGCROSS, RGB(255, 0, 0) );

или:

CBrush brush;

brush.CreateHatchBrush( HS_DIAGCROSS, RGB(255, 0, 0) );

При рисовании штриховой кистью GDI заполняет "пустые" места цветом фона

(по умолчанию белый, его можно изменить функцией CDC::SetBkColor или

включить/выключить заполнение фона режимом OPAQUE или TRANSPARENT с помо-

щью CDC::SetBkMode). Например, заштрихованный квадрат со стороной 100 единиц

можно нарисовать так:

CBrush brush( HS_DIAGCROSS, RGB (255, 255, 255) );

dc.SelectObject( &brush );

dc.SetBkColor( RGB(192, 192, 192) );

dc.Rectangle( 0, 0, 100, 100 );

2.5 Отображение текста

В предыдущей лекции уже упоминался один из способов вывода текста в окно

с помощью функции CDC::DrawText. Ей можно указать прямоугольник, внутри

которого выводить текст, и флаги, указывающие, как именно располагать текст

внутри прямоугольника. Например, для вывода текста в виде одной строки по центру

прямоугольника rect использовался вызов:

dc.DrawText( "Hello, MFC", -1, &rect,

DT_SINGLELINE ¦ DT_CENTER ¦ DT_VCENTER );

Кроме DrawText, в классе CDC есть еще несколько функций для работы с тек-

стом. Некоторые из них приведены в табл. 5.7. Одна из самых часто используемых –

функция TextOut, которая выводит текст подобно DrawText, но принимает в каче-

стве параметров координаты точки начала вывода текста или использует для этого

текущую позицию. Оператор:

dc.TextOut( 0, 0, "Hello, MFC" );

выведет строку "Hello, MFC", начиная с левого верхнего угла окна, связанного с кон-

текстом dc. Функция TabbedTextOut при выводе строки заменяет символы табуля-

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

По умолчанию, координаты, переданные в TextOut, TabbedTextOut и

ExtTextOut, считаются левым верхнем углом описывающего прямоугольника для

первого символа строки. Однако интерпретацию координат можно изменить, задав в

контексте устройства свойство выравнивания текста. Для этого используется функ-

ция CDC::SetTextAlign, например, для выравнивания текста по правой границе:

dc.SetTextAlign( TA_RIGHT );

65

Чтобы функция TextOut вместо явно указанных координат пользовалась те-

кущей позицией, надо вызвать SetTextAlign с указанием стиля и установленным

флагом TA_UPDATECP. Тогда TextOut после вывода каждой строки будет изменять

текущую позицию. Так можно вывести несколько строк подряд с сохранением

корректного расстояния между ними.

Таблица 5.7. Функции-члены CDC для вывода текста

Функция

DrawText

TextOut

Описание

Выводит текст с заданным форматированием внутри описывающего

прямоугольника

Выводит символьную строку в текущей или заданной позиции

TabbedTextOut Выводит символьную строку, содержащую табуляции

ExtTextOut Выводит символьную строку с возможным заполнением описывающе-

го прямоугольника фоновым цветом или изменением межсимвольного

расстояния

GetTextExtent Вычисляет ширину строки с учетом параметров текущего шрифта

GetTabbedText

Extent

GetTextMetric

s

Вычисляет ширину строки с табуляциями с учетом текущего шрифта

Возвращает свойства текущего шрифта (высоту символа, среднюю

ширину символа и т.п.)

SetTextAlign Задает параметры выравнивания для функции TextOut и некоторых

других функций вывода

SetTextJustif

ication

Задает дополнительную ширину, необходимую для выравнивания сим-

вольной строки

SetTextColor Задает в контексте устройства цвет вывода текста

SetBkColor

Задает в контексте устройства цвет фона, которым заполняется область

между символами при выводе текста

Функции GetTextMetrics и GetTextExtent предназначены для получения

свойств текущего шрифта, выбранного в контексте устройства. GetTextMetrics

возвращает эти свойства в виде структуры TEXTMETRIC. GetTextExtent (или

GetTabbedTextExtent) вычисляет в логических единицах ширину заданной строки

с учетом текущего шрифта. Пример использования GetTextExtent – вычисление

ширины промежутка между словами, чтобы равномерно распределить текст по за-

данной ширине. Допустим, надо вывести строку в участке шириной nWidth. Для вы-

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

CString string = "Строка с тремя пробелами ";

CSize size = dc.GetTextExtent( string );

dc.SetTextJustification( nWidth - size.cx, 3 );

dc.TextOut( 0, y, string );

Второй параметр SetTextJustification задает число символов-

разделителей в строке. По умолчанию символом-разделителем является пробел. По-

сле вызова SetTextJustification, все последующие вызовы TextOut и других

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

SetTextJustification', равномерно между всеми символами-разделителями.

2.6 Шрифты GDI и класс CFont

Все текстовые функции-члены CDC пользуются текущим шрифтом, выбранным

в контексте устройства. Шрифт – это набор символов определенного размера (высо-

ты) и начертания, у которых есть общие свойства, например, толщина символа (нор-

66

мальная или жирная). В типографии размер шрифта измеряется в специальных еди-

ницах – пунктах. 1 пункт примерно равен 1/72 дюйма. Высота символа шрифта 12 пт

равна примерно 1/6 дюйма, но в Windows реальная высота несколько зависит от

свойств устройства вывода. Термин "начертание" определяет общий стиль шрифта.

Например, Times New Roman и Courier New являются различными начертаниями.

Шрифт – один из типов объектов модуля GDI. В MFC для работы со шрифтами

есть класс CFont. Сначала надо создать объект этого класса, а затем с помощью од-

ной из его функций-членов CreateFont, CreateFontIndirect, CreatePointFont

или CreatePointFontIndirect создать шрифт в модуле GDI. Функциям

CreateFont и CreateFontIndirect можно задавать размер шрифта в пикселах, а

CreatePointFont и CreatePointFontIndirect – в пунктах. Например, для соз-

дания 12-пунктного экранного шрифта Times New Roman функцией

CreatePointFont надо выполнить вызовы (размер задается в 1/10 пункта):

CFont font;

font.CreatePointFont( 120, "Times New Roman" );

Сделать то же самое с помощью CreateFont несколько сложнее, т.к. требует-

ся узнать, сколько в контексте устройства логических единиц приходится на один

дюйм по вертикали и затем перевести высоту из пунктов в пикселы:

CClientDC dc(this);

int nHeight = -((dc.GetDeviceCaps(LOGPIXELSY)*12)/72);

CFont font;

font.CreateFont( nHeight, 0, 0, 0, FW_NORMAL, 0, 0, 0,

DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,

DEFAULT_QUALITY, DEFAULT_PITCH ¦ FF_DONTCARE, "Times New Roman" );

Среди множества параметров CreateFont есть толщина, признак курсива и др

свойства. Эти же свойства шрифта можно хранить в специальной структуре LOGFONT

и передавать ее для создания шрифта в CreatePointFontIndirect, например:

LOGFONT lf;

memset( &lf, 0, sizeof(lf) );

lf.lfHeight = 120;

lf.lfWeight = FW_BOLD;

lf.lfItalic = TRUE;

strcpy( lf.lfFaceName, "Times New Roman" );

CFont font;

font.CreatePointFontIndirect( &lf );

Если вы попытаетесь создать шрифт, не установленный в системе, то GDI по-

пробует подобрать наиболее похожий шрифт из установленных. Хотя внутренний

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

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

2.7 Стандартные объекты GDI

В Windows есть набор предопределенных часто используемых перьев, кистей,

шрифтов и других объектов GDI, которые не надо создавать, а можно использовать

уже готовые. Они называются стандартными объектами GDI (табл. 5.8). Их можно

выбирать в контексте устройства с помощью функции CDC::SelectStockObject

или присваивать их существующим объектам CPen, CBrush, и др. с помощью

CGdiObject::CreateStockObject. Класс CGdiObject является базовым классом

для CPen, CBrush, CFont и других MFC-классов, представляющих объекты GDI.

67

Таблица 5.8. Часто используемые стандартные объекты GDI

Объект

NULL_PEN

Пустое (прозрачное) перо

Описание

BLACK_PEN

WHITE_PEN

NULL_BRUSH

HOLLOW_BRUSH

BLACK_BRUSH

DKGRAY_BRUSH

GRAY_BRUSH

LTGRAY_BRUSH

WHITE_BRUSH

Черное сплошное перо толщиной 1 пиксел

Белое сплошное перо толщиной 1 пиксел

Пустая (прозрачная) кисть

То же, что NULL_BRUSH

Черная кисть

Темно-серая кисть

Серая кисть

Светло-серая кисть

Белая кисть

ANSI_FIXED_FONT Моноширинный системный шрифт ANSI

ANSI_VAR_FONT

SYSTEM_FONT

Пропорциональный системный шрифт ANSI

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

SYSTEM_FIXED_FONT Моноширинный системный шрифт (для совместимости со стары-

ми версиями Windows)

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

сделать двумя способами, во-первых:

CPen pen( PS_NULL, 0, (RGB (0, 0, 0)) );

dc.SelectObject( &pen );

CBrush brush( RGB(192, 192, 192) );

dc.SelectObject(&brush);

dc.Ellipse( 0, 0, 100, 100 );

Т.к. прозрачное перо и светло-серая кисть есть среди стандартных объектов

GDI, ту же фигуру можно нарисовать так:

dc.SelectStockObject( NULL_PEN );

dc.SelectStockObject( LTGRAY_BRUSH );

dc.Ellipse( 0, 0, 100, 100 );

2.8 Удаление объектов GDI

Перья, кисти и другие объекты GDI занимают не только память программы, но

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

объекты GDI, которые больше не нужны. При автоматическом создании объектов

CPen, CBrush, CFont и др. подклассов CGdiObject соответствующие объекты GDI

автоматически удаляются из деструкторов этих классов. Если же объекты

CGdiObject создавались динамически оператором new, то обязательно надо вызы-

вать для них оператор delete. Явно удалить объект GDI, не уничтожая объект

CGdiObject, можно вызовом функции CGdiObject::DeleteObject. Стандартные

объекты GDI, даже "созданные" функцией CreateStockObject, удалять не надо.

Visual C++ может автоматически отслеживать объекты GDI, которые вы забы-

ли удалить. Для этого применяется перегрузка оператора new. Чтобы разрешить такое

слежение в конкретном исходном файле, после директивы включения заголовочного

файла Afxwin.h надо добавить директиву определения макроса:

#define new DEBUG_NEW

После завершения работы приложения номера строк и имена файлов, содер-

жащие не удаленные объекты GDI, будут показаны в отладочном окне Visual C++.

68

Для удаления объектов GDI важно знать, что нельзя удалить объект, который

выбран в контексте устройства. Следующий пример является ошибочным:

void CMainWindow::OnPaint()

{

CPaintDC dc( this );

CBrush brush( RGB(255, 0, 0) );

dc.SelectObject( &brush );

dc.Ellipse( 0, 0, 200, 100 );

}

Ошибка заключается в том, что объект CPaintDC создается раньше CBrush.

Т.к. оба объекта созданы автоматически, и CBrush – вторым, то его деструктор будет

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

как будет удален объект dc. Эта попытка будет неудачной. Вы можете исправить по-

ложение, если создадите кисть первой. Но везде соблюдать подобное правило в про-

грамме тяжело, и очень утомительно искать такие ошибки.

В GDI нет функции для отмены выбора объекта в контексте, вроде

UnselectObject. Решение заключается в том, чтобы перед удалением объекта

CPaintDC выбрать в нем другие объекты GDI, например, стандартную кисть GDI.

Многие программисты поступают по-другому: при первом выборе в контексте уст-

ройства собственного объекта GDI сохраняют указатель на предыдущий объект, ко-

торый возвращается функцией SelectObject. Затем, перед удалением контекста, в

нем выбираются те объекты, которые были в нем "по умолчанию". Например:

CPen pen( PS_SOLID, 1, RGB(255, 0, 0) );

CPen* pOldPen = dc.SelectObject(&pen);

CBrush brush( RGB(0, 0, 255) );

CBrush* pOldBrush = dc.SelectObject( &brush );

dc.SelectObject( pOldPen );

dc.SelectObject( pOldBrush );

Способ с использованием стандартных объектов GDI реализуется так:

CPen pen( PS_SOLID, 1, RGB(255, 0, 0) );

dc.SelectObject( &pen );

CBrush brush( RGB(0, 0, 255) );

dc.SelectObject( &brush );

dc.SelectStockObject( BLACK_PEN );

dc.SelectStockObject( WHITE_BRUSH );

При динамическом создании объектов GDI нельзя забывать про оператор delete:

CPen* pPen = new CPen( PS_SOLID, 1, RGB(255, 0, 0) );

CPen* pOldPen = dc.SelectObject( pPen );

dc.SelectObject( pOldPen );

delete pPen;

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]