Рисование в DC. Как избежать ошибок.
Страница 2. Ресурсы GDI. Выбор ресурсов в DC



2. Ресурсы GDI. Выбор ресурсов в DC.

Рисование в DC происходит посредством ресурсов GDI, таких как Pen, Brush, Font и Bitmap. Остальные типы ресурсов являются специфическими и не будут рассмотрены в данной статье.

Под каждый из этих типов в Windows заведен соответствующий тип данных: HPEN, HBRUSH, HFONT и HBITMAP. В WinAPI для универсализации каждый из приведенных типов приводится к типу HGDIOBJ как в качестве параметра функций, так и в качестве возвращаемого значения.

В MFC каждый тип представлен соответствующим классом: CPen, CBrush, CFont и CBitmap. Базовым для всех этих классов является класс CGdiObject.

Большинство функций рисования в Windows использует выбранные в DC объекты, так например LineTo использует выбранный в DC Pen, ExtFloodFill – Brush, а TextOut – Font. Соответственно, чтобы рисовать нужными нам цветами и стилями, требуется выбрать в нужном нам DC наши собственные объекты, которые естественно, перед этим необходимо создать.

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

Несмотря на все разнообразие способов создания объектов GDI, существует лишь один метод их удаления. Все объекты, созданные по методам CreateXXXX (CreatePen, CreateBrushIndirect, …) должны быть удалены методом DeleteObject. Для объектов, полученных по GetStockObject или CGdiObject::CreateStockObject вызывать DeleteObject необязательно, хотя ошибкой это не является.

WinAPI
Цитата
BOOL DeleteObject (HGDIOBJ hgdiObj);


MFC
Цитата
BOOL CGdiObject::DeleteObject();


Замечание: для объектов MFC CGdiObject::DeleteObject вызовется автоматически в деструкторе. То есть этот метод можно не вызывать для объектов, создающихся однократно.

Для выбора объекта в DC используются следующий методы:

WinAPI
Цитата
//Выбрать объект в DC
HGDIOBJ SelectObject (HDC hdc, HGDIOBJ hgdiobj);


MFC
Цитата
//Выбрать Pen в DC
CPen * CDC::SelectObject (CPen *pPen);

//Выбрать Brush в DC
CBrush * CDC::SelectObject (CBrush *pBrush);

//Выбрать Font в DC
CFont* CDC::SelectObject (CFont* pFont);

//Выбрать Bitmap в DC
CBitmap* CDC::SelectObject (CBitmap* pBitmap);


Все методы SelectObject возвращают объект GDI, который был выбран в DC перед этим.


ВАЖНО
1) Основное правило здесь – «забрал - отдай». То есть, последовательность должна быть такая:

  • Выбрал свой объект в DC;

  • Запомнил объект, который вернул метод SelectObject;

  • Использовал свой объект при рисовании;

  • Выбрал в DC объект, который запомнил (тем самым освободил свой объект из DC).


2) Вызов DeleteObject для объекта, который в данный момент выбран в DC к успеху не приведет. Перед удалением объект обязательно должен быть освобожден из DC.



Примеры:

WinAPI
// Получаем DC для рисования
HDC hDC = GetDC (hWnd);

// Получаем размер клиентской области окна
RECT rc;
GetClientRect(hWnd, &rc);

// Создаем Pen
HPEN hPen = CreatePen (PS_SOLID, 1, RGB(255, 0, 0));

// Выбираем свой Pen в DC, запоминаем старый Pen
HPEN hOldPen = (HPEN)SelectObject (hDC, hPen);

// Перемещаем точку рисования в левый верхний угол окна
MoveToEx(hDC, rc.left, rc.top, NULL);
// Рисуем линию в правый нижний угол
LineTo(hDC, rc.right, rc.bottom);

// Выбираем старый Pen в DC (освобождаем свой Pen из DC)
SelectObject(hDC, hOldPen);

// Удаляем Pen
DeleteObject (hPen);

// Освобождаем DC
ReleaseDC (hWnd, hDC);


MFC
// Получаем DC для рисования
CDC *pDC = m_Buton1.GetDC();

// Получаем размер клиентской области окна
RECT rc;
m_Button1.GetClientRect(&rc);

// Создаем Pen
CPen Pen;
Pen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));

// Выбираем свой Pen в DC, запоминаем старый Pen
CPen *pOldPen = pDC->SelectObject (&Pen);

// Перемещаем точку рисования в левый верхний угол окна
pDC->MoveTo(rc.left, rc.top);
// Рисуем линию в правый нижний угол
pDC->LineTo(rc.right, rc.bottom);

// Выбираем старый Pen в DC (освобождаем свой Pen из DC)
pDC->SelectObject(pOldPen);

// Удалять Pen в данном случае необязательно, но это не повредит
Pen.DeleteObject ();

// Освобождаем DC
m_Button1.ReleaseDC (pDC);



ВАЖНО
Что не надо делать в WinAPI:

  • Удалять (DeleteObject) объект, полученный по SelectObject.

Что не надо делать в MFC:

  • Удалять DC, полученный через GetDC с помощью delete (delete pDC;);

  • Удалять DC, полученный через GetDC с помощью CDC::DeleteDC (pDC->DeleteDC(););

  • Удалять объект, полученный по SelectObject (delete pOldPen; или pOldPen->DeleteObject(););

Все это является примерами часто встречающихся ошибок.

Отдельное внимание хочется уделить методу CGdiObject::Detach(), очень часто ошибочно используемого для удаления объекта GDI (вместо CGdiObject::DeleteObject();)

Цитата
HGDIOBJ CGdiObject::Detach();

Этот метод используется для «отсоединения» от объекта класса CGdiObject(CPen, CBrush и т.д.) хэндла объекта GDI. При этом сам хэндл(а значит, и объект GDI) не удаляется.

Пример использования метода Detach:
// Функция создаст и вернет хэндл GDI объекта Font 
// После использования фонта, созданного этой функцией,
// требуется его удалить с использованием [b]DeleteObject[/b]
HFONT CreateMyFont ()
{
  // Создаем фонт 
  CFont Font;
  Font.CreateFont(
   12,                        // nHeight
   0,                         // nWidth
   0,                         // nEscapement
   0,                         // nOrientation
   FW_NORMAL,                 // nWeight
   FALSE,                     // bItalic
   FALSE,                     // bUnderline
   0,                         // cStrikeOut
   ANSI_CHARSET,              // nCharSet
   OUT_DEFAULT_PRECIS,        // nOutPrecision
   CLIP_DEFAULT_PRECIS,       // nClipPrecision
   DEFAULT_QUALITY,           // nQuality
   DEFAULT_PITCH | FF_SWISS,  // nPitchAndFamily
   "Arial"));                 // lpszFacename

  // Возвращаем хэндл HFONT созданного шрифта
  // По выходу из функции объект Font удалится, 
  // в то время как хэндл созданного фонта уже будет 
  // отсоединен от него, и с успехом будет возвращен
  // из функции
  return (HFONT)Font.Detach();
}
 
« Предыдущая статья   Следующая статья »