Малювання в DC. Як уникнути помилок, Різне, Програмування, статті

Дана стаття не може претендувати на повноту розкриття принципів малювання в середовищі Microsoft Windows, вона створена в допомогу тим, хто робить свої перші кроки в цій області, з метою звернути увагу на основні моменти і запобігти можливим (найбільш часто зустрічаються) помилки. Приклади коду будуть представлені в двох варіантах: з використанням WinAPI і MFC.

1. Отримання DC (Контексту пристрою)


Малювання в Windows здійснюється в контексті пристрою (DC). Існують 4 типи DC: Display, Printer, Memory (Compatible DC) і Information. Перші 3 використовуються для малювання, Information DC – для отримання інформації про пристрій. У даній статті буде розглядатися Display DC (звернення з Memory DC буде розглянуто в наступній статті, яка зараз у підготовці), а поводження з Printer DC – велика окрема тема, яку може бути хто-небудь з учасників освітить тут на форумі. 🙂

Отже, нам потрібно в деякому вікні намалювати щось своє власне, картинку, графік, текст і т.д. Насамперед необхідно отримати контекст для малювання. Для цього в WinAPI застосовуються такі методи:

WinAPI

/ / Отримати DC клієнтської області вікна по його хендл
HDC GetDC(HWND hWnd);

/ / Отримати DC всього вікна (включаючи його заголовок, меню, скроллбари і т.д.) за його хендл
HDC GetWindowDC (HWND hWnd);

/ / Звільнити DC, раніше отриманий по GetDC або GetWindowDC
int ReleaseDC (HWND hWnd, HDC hDC);


У MFC класом “обгорткою” для DC служить клас CDC. Для отримання об’єкта CDC якого або вікна, в класі CWnd існують наступні методи:

MFC

/ / Отримати DC клієнтської області вікна
CDC *CWnd:: GetDC ();

/ / Отримати DC всього вікна (включаючи його заголовок, меню, скроллбари і т.д.)
CDC *CWnd:: GetWindowDC ();

/ / Звільнити DC, раніше отриманий по GetDC або GetWindowDC
int  CWnd:: ReleaseDC (HWND hWnd, HDC hDC);


З наведених методів одержання DC найбільш часто використовується GetDC, т.к. зазвичай малювання відбувається в клієнтської частини.

ВАЖЛИВО: Будь DC, отриманий за GetDC або GetWindowDC повинен бути потім звільнений через виклик ReleaseDC. В іншому випадку відбуваються витоку ресурсів GDI, що при довгому часу роботи програми неминуче призведе до глюків при малюванні. Також, треба відзначити, що ReleaseDC повинно викликатися тільки для DC, отриманих за GetDC або GetWindowDC.
Схема отримання – звільнення DC

WinAPI

/ / HWnd – хендл вікна, DC якого нам необхідний

/ / Отримуємо DC
HDC hDC = ::GetDC (hWnd);

/ / Тут малюємо, використовуючи отриманий DC
// …

/ / Звільняємо DC
::ReleaseDC (hWnd, hDC);


MFC

/ / M_Button1 – член класу, об’єкт типу CButton
CDC *pDC = m_Button1.GetDC();

/ / Тут малюємо, використовуючи отриманий DC
// …

/ / Звільняємо DC
m_Button1.ReleaseDC(pDC);


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) Основне правило тут – “забрав – віддай”. Тобто, послідовність має бути така:


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:


Що не треба робити в MFC:


Все це є прикладами часто зустрічаються помилок.
Окрему увагу хочеться приділити методу 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();
}

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*