Сегодня я расскажу достаточно сложную и просто необходимую тему - работа с графикой. Мы уже использовали графический вывод в своих примерах, когда выводили текст внутри окна. Сегодня нам предстоит разобратся с технической стороной этого дела.
В мире Windows (так любит выражатся Билл Гейтс) прямой доступ к ресурсам запрещён. Здесь работа с устройствами происходит через некоторую прослойку между железом и твоей прогой. Эта прослойка называется контекстом. Контекст устройства позволяет получить доступ к железу и работать с ним. Контекст - это логическое представление этого устройства.
Честно говоря, когда я шесть лет назад впервые познакомился с контекстами, я считал их слишком сложными и думал, что прямой доступ к аппаратуре намного проще и эффективнее. Со временем я понял, что на счёт эффективности я не ошибся, но на счт простоты я очень сильно заблуждался. Когда я программировал под DОS, мне приходилось держать по несколько блоков почти одинакового кода: один для вывода на экран, другой для вывода на принтер. В Windows это не нужно. Работа с контекстами одинакова и они совместимы между собой. Например, одна функция выводит текст на контекст экрана. Ты подменяешь контекст экрана на контекст принтера и уже выводишь на принтер. Так что это одна из гениальнецших возможностей Windows.
В Win32 есть четрые типа контекстов:
Контекст дисплея
Информационный контекст
Конекст принтера
Контекст памяти
Сегодня нас будет интересовать контексты для рисования. Это контексты, с помощью которых можно выводить изображения на любое устройство типа дисплея или принтера. Для использования контекстов можно воспользоватся Windows API, а можно и использовать MFC, который упрощает эту работу. В MFC реализовано пять классов, позволяющих работать с различными контекстами рисования:
СВС - главный класс, от которого происходят все остальные
CPaintDC - класс для рисования
CClientDC - класс, который предназначен для рисования внутри клиентской области
CWindowDC - рисование по всей поверхности окна
CMetaFileDC - класс для рисования векторной графики.
Итак, начнём рассматривать эти классы по порядку.
CDC
CDC - это базовый класс, от которого происходят все остальные. В нём реализованы основные функции рисования, которые потом наследуются всеми потомками. Вся выводимая графика происходит с помощью методов CDC класса.
Этот класс содержит очень много функций, которые мы будем изучать постепенно, а большинство из них я распишу в разделе "Windows API".
Для создания объекта CDC можно использовать одну из трёх функций:
Функция возвращает созданный каонтекст рисования HDC. Этой функции передаётся четыре параметра:
lpszDriver - Строка содержащая имя драйвера
LPCTSTR - Строка содержащая имя устройства
lpszOutput - не используется и должно быть равно NULL
lpInitData - структура DEVMODE, которая содержит специфическую информацию о устройстве.
Самый сложный - третий параметр. Его можно оставить пустным (равным NULL) если ты хочешь использовать значения по умолчанию. А модно заполнить структуру с помощью функции DocumentProperties и изменить необходимые значения.
CreateIC - предназначена для создания информационного контекста, с помощью которого можно получить информацию о контексте. Функция возвращает указатель на IC (информационный контекст).
Передаваемые параметры те же самые, поэтому я не буду на них останавливаться.
HDC CreateCompatibleDC(
HDC hdc
);
CreateCompatibleDC - создаёт и возвращает указатель на контекст совместимый с существующим. В качестве единственного параметра ты должен передать указатель на существующий контекст и на выходе ты получишь новый, совместимый с переданным.
На первый взгляд всё сложно, но не так как кажется. Ты врят ли будешь использовать эти функции на прямую. Скорей всего ты будешь использовать потомков, которые проще в использовании и инициализации.
CPaintDC
Этот класс происходит от CDC и наследует себе все его функции добавляя свои новые возможности. Он предназначен для рисования только на поверхности окна. Рисование с помощью этого класса превращается в три простых шага:
Создание класса CPaintDC
Рисование
Уничтожение CPaintDC
Рассмотрим простейший пример использования CPaintDC. Запусти VC++. Нажми File->New и выбери в окне "MFC AppWizard". Введи имя проекта (я ввел Draw) и нажми ОК. Перед тобой появится первое окно мастера. Здесь выбери "Single Document" и убери флажок с "Document/View architecture support". Можно давить пимпу "Finish".
Теперь выбери ClassWizard из меню View. Перед тобой появится окно мастера, как на рисунке 1.
Рис 1. ClassWizard
В строке Class Name выбери CChildView и внизу окна, в списке Member Functions ты увидешь список уже доступных функций. Выдели там функцию OnPaint и нажми кнопку Edit Code. VC++ покажет тебе заготовку для функции. Модифицируй её так:
void CChildView::OnPaint()
{
CPaintDC dc(this); // создаю класс
// TODO: Add your message handler code here
CRect rc;
GetClientRect(&rc); //Получаю размер окна
dc.Ellipse(rc); //Рисую
// Do not call CWnd::OnPaint() for painting messages
}
Рис 2. Результат работы
Как видишь, всё просто. на рисунке 2 ты можешь видеть результат работы проги.
В статье А я "С", просто "С" я использовал рисования без использования классов. Там это происходило следующим образом. В функции обработчике событий мы вылавливливали сообщение WM_PAINT и рисовали так:
case WM_PAINT://Нужна прорисовка окна
hdc = BeginPaint(hWnd, &ps);
RECT rt;
GetClientRect(hWnd, &rt);
DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
EndPaint(hWnd, &ps);
break;
С использованием CPaintDC процесс немного упрощается, но главное, что он становится более понятным.
CClientDC
Это тоже потомок от CDC, только он позволяет рисовать только внутри клиентской области окна. Работает он так же просто, как и CPaintDC:
void CChildView::OnPaint()
{
CClientDC dc(this); // создаю класс
// TODO: Add your message handler code here
CRect rc;
GetClientRect(&rc); //Получаю размер окна
dc.Ellipse(rc); //Рисую
// Do not call CWnd::OnPaint() for painting messages
}
Как видишь, я только подменил имя класса. Всё остальное остальсь прежним.
CWindowDC
Этот класс предназначен для рисования по всей поверхности окна. Здесь ты уже не ограничен клиентской поверхностью, а можешь разгулятся по всему окну включая меню, обрамление и заголовок окна.
Следующий пример выводит эллипс по всей поверхности заголовка:
Нажми View->ClassWizard. В списке ClassName выбери CMainFrame. В поле Object ID выбери CMainFrame. В списке Messages выбери WM_PAINT. Всё это я отобразил на рис 3.
Рис 3. ClassWizard
Нажми кнопку Add Function и затем Edit Code. Теперь модифицируй полученную заготовку до вида:
void CMainFrame::OnPaint()
{
CWindowDC dc(this); // создаю класс
// TODO: Add your message handler code here
CRect rc;
GetClientRect(&rc); //Получаю размер окна
int cy=GetSystemMetrics(SM_CYCAPTION); //Получаю высоту заголовка
CRect rcEll (0,0, rc.right, cy);
dc.Ellipse(rcEll); //Рисую
// Do not call CWnd::OnPaint() for painting messages
}
Рис 4. Результат работы
Результат ты можешь лицезреть на рис 4. Прикольно? Вот именно.
Ну вот и всё. Пока хватит. В следующий раз продолжим.