Интерфейс графических устройств Windows ( ) представляет собой виртуальное графическое устройство для всех приложений независимо от конкретных аппаратных средств, из которых состоит компьютер. является неотъемлемой частью ядра операционной системы, контролирующей и обеспечивающей все операции графического вывода приложений Windows.
Корпорация Microsoft создала , чтобы отделить графические операции от фактически используемого оборудования. Для программиста предоставлены графические функции высокого уровня абстракции, которые обеспечивают одинаковые результаты выполнения независимо от используемого оборудования. От разработчиков аппаратных средств требуется обеспечить поставку и драйверов (программ управления оборудованием на низком уровне) для естественного подключения их к функциям .
Пользовательская сторона (пользователь - это разработчик приложения) реализована с помощью контекста устройства. Контекст графического устройства - это конкретная настройка на выполнение графических операций. Управление контекстом устройства обеспечивается MFC -классом . Объект, созданный с помощью этого класса, содержит все необходимые настройки , установленные по умолчанию или пользователем (разработчиком приложения). Фактически экземпляр класса или производных от него классов и представляет собой контекст устройства, олицетворяя собой все настройки .
Контекст устройства способен обеспечить вывод изображений на любое внешнее устройство, будь то экран, принтер, . Для отображения изображения на экране приложению необходимо иметь контекст устройства экрана, для отображения на принтере - контекст другого устройства - принтера.
Контексты экрана и принтера называются физическими контекстами или контекстами вывода. Существуют еще два контекста устройства: растр и метафайл. Растр является тем же контекстом устройства, содержащим набор пикселов и по своей структуре готовый для подключению к физическому контексту, но пока хранящийся в памяти. Метафайл является набором готовых инструкций, способных создать графическое изображение перед подключением к физическому контексту.
Контекстов устройства, как экземпляров класса , одновременно может быть создано в приложении несколько и все они будут храниться в памяти готовыми для подключения, но только один из них в текущий момент времени может быть связан с конкретным физическим устройством вывода информации. т.е. только один может служить физическим контекстом ( контекстом вывода ). Существуют наследники класса , создающие контексты в частных случаях, например, CPaintDC, CClientDC. Поскольку
Объекты контекста устройства, а равным образом и все различные объекты рисунка, классифицируются в Windows как ресурсы. Операционная система может иметь ограниченное количество таких ресурсов и они могут исчерпаться, если приложение занимает их и вовремя не освобождает. Эта потеря известна как утечка ресурсов и во многом подобна утечке памяти, поскольку тоже в конечном итоге приводит к блокировке системы.
Чтобы избежать утечки подобных ресурсов, удобнее создавать их как локальные на стеке в пределах тех функций, где они будут использоваться. Соответственно при выходе из такой функции ресурс автоматически освобождается. Единственное реальное исключение, - когда объект контекста устройства создается самой Windows и передается в функцию обработки события в качестве параметра pDC. Например
void CCircleView::OnDraw(CDC* pDC)
{
...................
}
Некоторые свойства (атрибуты) контекста устройства представляют собой простые типы данных, встроенные прямо в контекст устройства. Но большинство графических атрибутов контекста устройства представляют собой сложные данные типа структур или даже классов со своими функциями членами. Экземпляры этих классов называются графическими атрибутами контекста устройства. Экземпляры этих классов нужно создавать, настраивать и подключать к контексту устройства, чтобы они должным образом влияли на графический вывод.
Базовым типом данных Windows для хранения цветов является структура COLORREF. Проще всего задать цвет при помощи макрокоманды RGB, которой передаются три числовых значения от 0 до 255. Например:
COLORREF crRed = RGB(255, 0, 0);
COLORREF crGreen = RGB(0, 255, 0);
COLORREF crBlue = RGB(0, 0, 255);
Такой способ задания цвета в называется логическим цветом. Но это только наше пожелание, а реальное отображение цвета определяется способностью аппаратного устройства. Монитор с высокой разрешающей способностью, расчитанный на отображение 24-битной или 32-битной глубины цвета не будет иметь никаких расхождений между логическим и физическим цветом. Для более слабых мониторов будет сам осреднять цвета, подбирая наибольшее сходство между логическим (запрашиваемым) и физическим (реально отображаемым) цветом. Аналогичный процесс происходит и для принтеров.
Таким образом, если, например, красный цвет запрошен у физического устройства, способного его отобразить, то все нормально - мы получим то, что требовалось. Но если физическое устройство не способно отобразить запрошенный цвет, то оно применит самый близкий (с точки зрения ). Для черно-белых принтеров стремиться подобрать наиболее близкие цвета через оттенки серого, что иногда никуда не годится. Чтобы решить эту проблему, можно просто не запрашивать недоступные цвета. Чтобы выяснить, сколько цветов поддерживает конкретное физическое устройство, нужно обратиться к функции контекста этого устройства
int nColors = pDC->GetDeviceCaps(NUMCOLORS);
Для цветных мониторов система предоставляет стандартную палитру из 20 цветов, на основании которой формируются все другие цвета.
Стандартная цветовая палитра | ||||||
|---|---|---|---|---|---|---|
const COLORREF |
g_crBlack |
= RGB( |
0, | 0, | 0 | ) |
const COLORREF |
g_crYellow |
= RGB( |
255, | 255, | 0 | ) |
const COLORREF |
g_crDkYellow |
= RGB( |
128, | 12 | 0 | ) |
const COLORREF |
g_crRed |
= RGB( |
255, | 0, | 0 | ) |
const COLORREF |
g_crDkRed |
= RGB( |
128, | 0, | 0 | ) |
const COLORREF |
g_crMagenta |
= RGB( |
255, | 0, | 255 | ) |
const COLORREF |
g_crDkMagenta |
= RGB( |
128, | 0, | 128 | ) |
const COLORREF |
g_crBlue |
= RGB( |
0, | 0, | 255 | ) |
const COLORREF |
g_crDkBlue |
= RGB( |
0, | 0, | 128 | ) |
const COLORREF |
g_crCyan |
= RGB( |
0 | 255, | 255 | ) |
const COLORREF |
g_crDkCyan |
= RGB( |
0, | 128, | 128 | ) |
const COLORREF |
g_crGreen |
= RGB( |
0, | 255, | 0 | ) |
const COLORREF |
g_crDkGreen |
= RGB( |
0, | 128, | 0 | ) |
const COLORREF |
g_crGray |
= RGB( |
192, | 192, | 192 | ) |
const COLORREF |
g_crDkGray |
= RGB( |
128, | 128, | 128 | ) |
const COLORREF |
g_crWhite |
= RGB( |
255, | 255, | 255 | ) |
const COLORREF |
g_crLtYellow |
= RGB( |
255, | 251, | 240 | ) |
const COLORREF |
g_crLtGreen |
= RGB( |
192, | 220, | 192 | ) |
const COLORREF |
g_crLtBlue |
= RGB( |
166, | 202, | 240 | ) |
const COLORREF |
g_crMedGray |
= RGB( |
160, | 160, | 164 | ) |
Класс контекста устройства имеет множество методов, но существуют три основных направления применения этих методов для управления функциями :
Для каждого из них в существуют свои функции управления, потому что на уровне драйверов устройств каждый из этих наборов операций имеет свою реализацию и выполняется по разному.
Несмотря на то, что текст на экране, принтере или файле представляется точно так же, как в консольном приложении или в приложении DOS, это графический текст. Графическое представление текста, обеспечиваемое , предоставляет неограниченные возможности по его оформлению - изменение шрифта, цвета, фона, добавление теней, прозрачности и т.д. Перечислим основные функции работы с текстом.
COLORREF GetTextColor() - возвращает текущий цвет текста.COLORREF SetTextColor(COLORREF) - устанавливает цвет для вывода текста. Возвращает предыдущий цвет текста в виде значения типа COLORREF. Например, красный текст можно установить такpDC->SetTextColor(RGB(255, 0, 0)); pDC->TextOut(x, y, "Это красный текст");
int GetBkMode() - возвращает режим отображения фона текста или рисунка ( OPAQUE - непрозрачный, TRANSPARENT - прозрачный). Режим OPAQUE установлен по умолчанию.int SetBkMode(int) - устанавливает режим отображения фона ( OPAQUE - непрозрачный, TRANSPARENT - прозрачный). Возвращает предыдущий режим.COLORREF GetBkColor() - возвращает текущий цвет фона текста. Цвет фона закрашивает само поле текста, а не его символы.COLORREF SetBkColor(COLORREF) - устанавливает цвет фона для вывода текста. Возвращает предыдущий цвет фона текста в виде значения типа COLORREF. Для установленного режима TRANSPARENT цвет фона при прорисовке текста не учитывается.
Для выбора цвета пользователем ему можно предоставить стандартное диалоговое окно следующим кодом
Вызов стандартного окна выбора цвета
CColorDialog colorDlg;
if(colorDlg.DoModal() == IDOK){
COLORREF color = colorDlg.GetColor();
CString strText;
strText.Format("Вы выбрали цвет RGB( %d, %d, %d )"
, GetRValue(color)
, GetGValue(color)
, GetBValue(color)
);
AfxMessageBox(strText);
}
SetTextAlign(int nFlag) - выравнивание текста. Выравнивание текста описывает отношение между указанной координатами (x, y) начальной точкой отображения текста и самим полем, занимаемым текстом. По умолчанию принято выравнивание текста ниже и правее начальной точки вывода текста. Этой функции передается единственный параметр, представляющий собой комбинацию флагов, приведенных в таблице. Флаги, находящиеся в одном столбце, являются взаимоисключающими, т.е. из каждого столбца можно выбрать только один флаг. Флаги, указанные в первой строке, установлены по умолчанию.
| Константы выравнивания текста | ||
|---|---|---|
| По оси x | По оси y | Обновить текущую позицию |
TA_LEFT |
TA_TOP |
TA_NOUPDATECP |
TA_CENTER |
TA_BASELINE |
TA_UPDATECP |
TA_RIGHT |
TA_BOTTOM |
|
Например, в приведенном коде текст будет выровнен по правому краю
pDC->SetTextAlign( TA_RIGHT | TA_TOP ); pDC->TextOut(x, y, "Это выравненный справа текст");
SetTextJustification() - выравнивание текста по ширине. Функция позволяет установить количество пикселов, которые необходимо добавить в каждый символ пробела.SetTextCharacterExtra() - как и предыдущая функция, добавляет пискселы, но не только в пробелы, но и в символы текста.TextOut(int x, int y, CString strText) - размещает текст на поверхности рисунка и принимает три параметраx левого верхнего угла поля вывода текстаy левого верхнего угла поля вывода текстаTabbedTextOut() - отображает текст подобно функции TextOut(), но позволяет задать массив позиций табуляции, чтобы обеспечить представление текста в виде столбцов.ExtTextOut() - более универсальная функция вывода текста, позволяющая контролировать ширину даже отдельных символов. Во-первых, позволяет указать произвольный прямоугольник, ограничивающий область вывода текста. Во-вторых, позволяет отобразить текст в непрозрачном (заполненном цветом фона) прямоугольнике. И в-третьих, позволяет управлять шириной отображаемого тектса (интервалом между символами).GrayString() - создает пятнистый текст, аналогичный используемому в меню, когда соответствующий пункт недоступен.CSize GetTextExtent(CString , int) - вычисляет размер области, которую займет строка текста на рисунке в пикселах при использовании текущего шрифта. Эта функция принимает строку CString , где содержится текст, и возвращает структуру Size, которая имеет два члена: cx - определяющий ширину текста и cy - определяющий высоту текста. Например, приведем код, где текст центрируется относительно курсора.Вывод текста относительно центра курсора
// Отобразить фразу
// при помощи объекта MFC CClientDC
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
// Создать экземпляр clientDC класса CClientDC
CClientDC clientDC(this);// контекст для текущего окна
// Создать объект с текстом
CString strText = "Привет студентам от Снеткова В.М.!";
// Получить размер текстовой строки в пикселах
CSize size =
clientDC.GetTextExtent(strText. strText.GetLength());
// Вычислить такие координаты начала отображения строки,
// чтобы центр текста соответствовал курсору мыши
int xCentered = point.x - (size.cx / 2);
int yCentered = point.y - (size.cy / 2);
// Отобразить текст в окне
clientDC.TextOut(xCentered, yCentered, strText);
}
Следует договориться о терминах. В текущая позиция контекста устройства, где готова рисовать графическая функция, не видна на экране. Ее удобно называть графическим курсором по аналогии с текущей позицией экрана в DOS, переключенного в
CPoint GetCurrentPosition() - возвращает координаты графического курсора GDI .SetPixel(int x, int y, COLORREF RGB(RED, GREEN, BLUE)) - рисует точку текущим пером в заданной позиции графического курсора.MoveTo(int x, int y) - перемещает графический курсор в заданную точку.LineTo(int x, int y) - соединяет текущую позицию графического курсора с заданной точкой линией, цвет и толщина которой определяется параметрами текущего пера. Текущая позиция курсора перемещается в конец линии.Polyline (CPoint point[], int count); PolylineTo(CPoint point[], int count) - обе функции принимают в качестве параметров последовательность точек и рисуют отрезки прямых между ними. Первый параметр - указатель на массив объектов CPoint, второй - количество точек в массиве. Когда функция PolylineTo() завершает работу (возвращает управление клиенту), текущая позиция графического курсора остается в последней точке массива.Polygon(CPoint point[], int count) - рисует замкнутый многоугольник (полигон) по тем же параметрам, что и функции Polyline () и PolylineTo(). Единственное ее отличие - она дорисовывает замыкающее звено, соединяющее последнюю и начальную точки.Rectangle(CRect rect) - рисует прямоугольник по точкам объекта CRect текущим пером и кистью.RoundRect(CRect rect, CPoint ellipse) - рисует прямоугольник со скругленными углами текущим пером и кистью. Второй параметр класса CPoint в значении x содержит ширину, а в значении y - высоту эллипса, который используется для скругления углов.Ellipse(CRect rect); Ellipse(int x1, int y1, int x2, int y2) - CRect, либо координатами точек левого верхнего и правого нижнего углов. Для прямоугольника с равными сторонами рисуется круг. Эллипс рисуется с помощью текущего пера и кисти.Arc(CRect rect, CPoint ptStart, CPoint ptEnd); Arc(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); ArcTo(CRect rect, CPoint ptStart, CPoint ptEnd); ArcTo(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); - рисуют дугу между двумя точками. Первая координата rect (или x1,y1 и x2,y2 ) определяет описывающий прямоугольник, остальные - начальную и конечную точки дуги. Дуга рисуется против часовой стрелки от начала к концу. Функция ArcTo() после рисования бросает текущую позицию графического курсора в конечной точке дуги.Pie(CRect rect, CPoint ptStart, CPoint ptEnd); Pie(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); - рисует сегмент эллипса. Она принимает те же параметры, что и функции Arc(), ArcTo(). Дуга на внешнем конце сегмента определяется также, как и в Arc(), ArcTo(). Острие сегмента находится в центре описывающего прямоугольника rect.Видеопамять - специфический участок оперативной памяти, содержимое которого немедленно и постоянно отображается на экране. Растр ( bitmap - битовая карта) представляет собой участок оперативной памяти, по структуре являющийся точной копией видеопамяти. Однако в отличие от видеопамяти содержимое растровой памяти ( bitmap memory ) не является отображаемым непосредственно на экране.
Оперативная память, занятая растром, содержит данные, соответствующие прямоугольной области. В терминологии API Windows дескриптор занятой растром области памяти известен как HBITMAP. В этой области памяти можно располагать данные, перемещение которых в видеопамять приведет к отображению на экране изображения или узора.
Растровые графические объекты выводятся на экран способом, отличным от применяемого при прорисовке таких объектов, как линии и эллипсы. Параметры линий и эллипсов вычисляются математически, а затем состояние пикселов в видеопамяти изменяется таким образом, чтобы на экране появился соответствующий объект. Данные растра не вычисляются. В растровой памяти уже находится готовое изображение. В итоге все сводится к тому, чтобы скопировать данные из растровой памяти в видеопамять.
Для управления растром могут использоваться
API -функции, которые обращаются к растрам по HBITMAPMFC -класс CBitmap, который является оболочкой для API -функций Windows работы с растромПостроим приложение, которое будет иметь два окна, связанных дочерне-родительскими отношениями. Первое окно будет содержать различные опции, позволяющие выбрать форму, инструмент и цвет. Второе окно выступит в роли холста, где будут отображаться все выбранные настройки. Пользователи могут выбирать, что отобразится на холсте: линии, квадраты или растровый рисунок. Они смогут также задать цвет, перо и кисть для кругов или прямоугольников.
Создайте новый проект
Application Type Application Type Dialog Based Resource language:Project style:MFC standard Use of MFC:Use MFC in a static library User Interface Features Main frame styles:System menu - важно!!!About box - важно!!!Dialog title:GraphicsAdvanced Features
После создания мастером главной формы удалите из диалога все элементы управления
Русифицируйте ресурсы диалоговых окон с идентификаторами IDD_GRAPHICS_DIALOG и IDD_ABOUTBOX
Измените заголовок главного диалогового окна, указав фамилию, номер и название лабораторной работы
Измените свойство Minimize Box главного диалогового окна на True
Отредактируйте диалоговый ресурс IDD_ABOUTBOX
Замените иконку приложения на любую иную по выборуОкно должно содержать три группы переключателей:
Окно должно иметь две кнопки:
File Open для выбора рисунка
Разработайте показанный дизайн окна с соблюдением отмеченных рекомендаций
![]() |
Group в значение TrueID на более осмысленные в соответствии с названием, например, IDC_RPEN, IDC_RBRUSH и т.д., а для кнопок: IDC_BBITMAP, IDC_BEXITПереодически стройте приложение для контроля возможных ошибок |
После создания диалогового окна нужно назначить по одной переменной для каждой группы переключателей. Для этого нужно создать целочисленную переменную для первого переключателя каждой группы. Для всех последующих переключателей группы назначенная переменная будет принимать последовательно возрастающие значения в порядке возрастания значений их идентификаторов. Именно по этой причине важно создавать все переключатели группы по порядку. Если добавление выполнялось не по порядку, в режиме Format/Tab Order можно установить нужный порядок следования.
Для первых переключателей каждой группы назначьте переменные в соответствии с таблицей| Переменные для управления переключателями | ||||
|---|---|---|---|---|
| Объект | Переменная | Категория | Тип | Доступ |
IDC_RPEN |
m_iTool |
Value |
int |
public |
IDC_RLINE |
m_iShape |
Value |
int |
int |
IDC_RBLACK |
m_iColor |
Value |
public |
public |
Для этого выделите каждый первый переключатель группы и через контекстное меню вызывайте мастер Add Variable. Поля мастера заполняйте по образцу

Мастер добавит код
Файл GraphicsDlg.h
class CGraphicsDlg : public CDialog
{
....................................................
public:
// Для управления переключателями группы Tool
int m_iTool;
// Для управления переключателями группы Shape
int m_iShape;
// Для управления переключателями группы Color
int m_iColor;
};
Файл GraphicsDlg.cpp
CGraphicsDlg::CGraphicsDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGraphicsDlg::IDD, pParent)
, m_iTool(0)
, m_iShape(0)
, m_iColor(0)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
Файл GraphicsDlg.cpp
void CGraphicsDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Radio(pDX, IDC_RPEN, m_iTool);
DDX_Radio(pDX, IDC_RLINE, m_iShape);
DDX_Radio(pDX, IDC_RBLACK, m_iColor);
}
Создайте обработчик для клавиши Exit. Введите в обработчик код OnOK()
Запустите приложение и убедитесь, что переключатели каждой группы работают независимо
Установите, чтобы при запуске приложения в группе Color был выделен переключатель White. Для этого измените в конструкторе класса CGraphicsDlg индекс инициализации переменной группы
Файл GraphicsDlg.cpp
CGraphicsDlg::CGraphicsDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGraphicsDlg::IDD, pParent)
, m_iTool(0)
, m_iShape(0)
, m_iColor(7)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
Добавим окно для использования в качестве холста рисования. Оно будет открыто во все время работы приложения. На нем не будет никаких управляющих элементов - только холст.
Раскройте дерево ресурсов проекта и, вызывая для каждого типа ресурса контекстное меню, обратите внимание, как меняется название последней опции контекстного меню Insert с добавлением имени типа ресурса
На узле Dialog из контекстного меню выполните опцию Insert Dialog
Удалите из нового диалогового окна, которое мы будем в дальнейшем использовать как холст, все элементы управления
Выделите диалоговое окно и в панели свойств установите свойство System Menu в значение False. Убедитесь, что сразу исчезнет системная кнопка закрытия окна. Это исключает возможность для пользователя закрыть окно до окончания работы всего приложения
Присвойте заголовку окна значение "Холст"
Присвойте окну идентификатор ресурса IDD_PAINT_DLG. Обратите внимание, что для диалоговых окон принято применять префикс IDD_, а для элементов управления - IDC_
Щелкните правой кнопкой мыши на диалоговом окне холста и выполните опции Add Class контекстного меню. Заполните поля мастера создания класса, управляющего диалоговым окном, так

Примечание. Перед добавлением нового класса нужно удостовериться, что текущим выделением является именно диалоговое окно, а не иной элемент или код. В противном случае мастер не сможет понять, что класс нужно создавать для диалогового окна.
Нам нужно в описании класса главного диалогового окна приложения включить переменную как экземпляр класса диалогового окна холста и через нее управлять холстом из главного окна. Для этого:
Перейдите на вкладку Class View и выделите класс CGraphicsDlg
Вызовите мастер Мастер добавит код
Файл GraphicsDlg.h
// GraphicsDlg.h : header file
//
#pragma once
#include "paintdlg.h"
// CGraphicsDlg dialog
class CGraphicsDlg : public CDialog
{
...................................................
private:
// Экземпляр класса CPaintDlg для управления холстом
CPaintDlg m_dlgPaint;
};
Файл GraphicsDlg.cpp
CGraphicsDlg::CGraphicsDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGraphicsDlg::IDD, pParent)
, m_iTool(0)
, m_iShape(0)
, m_iColor(7)
, m_dlgPaint(0)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
Удалите приведенную инициализацию переменной m_dlgPaint из конструктора класса CGraphicsDlg - она не нужна
Проверьте обязательно - должна быть включена инструкция #include "paintdlg.h" в начало файла GraphicsDlg.h
Добавьте в метод инициализации кодФайл GraphicsDlg.cpp
BOOL CGraphicsDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
...........................................................
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
// Создать диалоговое окно холста с текущим родителем
m_dlgPaint.Create(IDD_PAINT_DLG, this);
// Показать холст поверх родителя
m_dlgPaint.ShowWindow(SW_SHOW);
return TRUE; // return TRUE unless you set the focus to a control
}
Постройте приложение и убедитесь, что холст появляется вместе с родителем, сворачивается и закрывается вместе с ним, при этом находится всегда поверх родителяТакие же родительско-дочерние отношения существуют между главным диалоговым окном и всеми помещенными в него элементами управления. Каждый управляющий элемент - наследник своего диалогового окна. В некотором смысле второе диалоговое окно является просто еще одним управляющим элементом первого диалогового окна.
Поскольку все закрепленные за переключателями переменные объвлены в первом окне как общедоступные, то второе диалоговое окно может их видеть и по их состоянию реагировать на рисование должным образом. Моно разместить все функции рисования графики в классе второго диалогового окна. Однако для того, чтобы своевременно обновлять значения переменных, а также сообщать второму диалоговому окну, что нужно вывести графику, некоторые функции должны находиться в первом диалоговом окне.
Всякий раз, когда окно должно быть перерисовано заново (если оно было заслонено другим окном или свернуто), операционная система вызывает функцию диалогового окна OnPaint(). В ней можно разместить все функции рисования графики. Но одновременно во втором диалоговом окне пока нет такой функции, но мы ее позже определим.. Как заставить второе диалоговое окно обновляться каждый раз, когда мы в нем что-нибудь нарисуем?
Для этого можно применить к нему функцию Invalidate() (Считать недействительным). Вызов этой функции автоматически вызывает OnPaint() как функцию-член базового класса CDialog. Функция Invalidate() не требует никаких параметров и является членом класса CWnd, так что может использоваться для любого окна или элемента управления. Она сообщает окну и операционной системе, что отображаемая область окна более не соответствует действительности и требуется ее перерисовка.
На данном этапе замкнем все переключатели на один комбинированный обработчик, в котором будет организована синхронизация работы элементов управления и значений закрепленных за ними переменных с помощью функции UpdateData(TRUE) ( TRUE - привести переменные в соответсвие с экранным представлением элементов управления "экран->переменные", FALSE - привести экранное представление элементов управления в соответствие со значениями ассоциированных с ними переменных "переменные->экран"). Здесь же, принимая во внимание, что любое изменение состояния переключателей должно приводить к изменению графики на холсте, предусмотрим вызов обновления второго диалогового окна с помощью функции Invalidate() второго окна.
Откройте первое диалоговое окно и выделите на нем первый переключатель IDC_RPEN. Через контекстное меню элемента выполните опцию Add

Появится мастер, который заполните так

Мастер создаст шаблон обработчика и зарегистрирует его в карте сообщений
Объявление обработчика в файле GraphicsDlg.h
class CGraphicsDlg : public CDialog
{
...................................................
private:
// Экземпляр класса CPaintDlg для управления холстом
CPaintDlg m_dlgPaint;
public:
afx_msg void OnRadioSelection();
};
Шаблон обработчика в файле GraphicsDlg.cpp
void CGraphicsDlg::OnRadioSelection()
{
// TODO: Add your control notification handler code here
}
Регистрация обработчика в карте сообщений в файле GraphicsDlg.cpp BEGIN_MESSAGE_MAP(CGraphicsDlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_BEXIT, OnBnClickedBexit) ON_BN_CLICKED(IDC_RPEN, OnRadioSelection) END_MESSAGE_MAP()
Отредактируйте обработчик и карту сообщений так
Обработчик в файле GraphicsDlg.cpp
void CGraphicsDlg::OnRadioSelection()
{
UpdateData(TRUE); // Обновляем переменные с экрана
m_dlgPaint.Invalidate(); // Перерисовать второе окно
// Ниже код для отладки, потом убрать !!!!!
CString str;
str.Format("Tool=%d; Shape=%d; Color=%d", m_iTool, m_iShape, m_iColor);
MessageBox(str, "Состояние переключателей");
}
Карта сообщений в файле GraphicsDlg.cpp BEGIN_MESSAGE_MAP(CGraphicsDlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_BEXIT, OnBnClickedBexit) ON_BN_CLICKED(IDC_RPEN, OnRadioSelection) ON_BN_CLICKED(IDC_RBRUSH, OnRadioSelection) ON_BN_CLICKED(IDC_RBITMAP, OnRadioSelection) ON_BN_CLICKED(IDC_RLINE, OnRadioSelection) ON_BN_CLICKED(IDC_RCIRCLE, OnRadioSelection) ON_BN_CLICKED(IDC_RSQUARE, OnRadioSelection) ON_BN_CLICKED(IDC_RBLACK, OnRadioSelection) ON_BN_CLICKED(IDC_RBLUE, OnRadioSelection) ON_BN_CLICKED(IDC_RGREEN, OnRadioSelection) ON_BN_CLICKED(IDC_RCYAN, OnRadioSelection) ON_BN_CLICKED(IDC_RRED, OnRadioSelection) ON_BN_CLICKED(IDC_RMAGENTA, OnRadioSelection) ON_BN_CLICKED(IDC_RYELLOW, OnRadioSelection) ON_BN_CLICKED(IDC_RWHITE, OnRadioSelection) END_MESSAGE_MAP()
Запустите приложение и проверьте с помощью отладочного кода реагирование обработчика на нажатия на все переключатели, затем уберите отладочный код из обработчикаЧтобы имет возможность рисовать фигуры различным цветом в соответствии с состоянием группы переключателей Color, создадим в класса CPaintDlg массив структур типа COLORREF, необходимый для выбора цвета пера и кисти рисования. Массив будет состоять из 8 элементов, каждый из которых будет содержать цвет, соответствующий переключателям группы Color главного окна.
Добавьте новую глобальную переменную в класс второго диалогового окна, которая будет являться массивом цветов. Для этого в конец объявления класса CPainDlg введите код
Объявление цвета в файле PaintDlg.h
class CPaintDlg : public CDialog
{
............................................
public:
// Таблица цветов
static const COLORREF m_crColors[8];
};
В конце файла PaintDlg.cpp вне любой из функций (глобально) введите код
Определение цвета в файле PaintDlg.cpp
const COLORREF CPaintDlg::m_crColors[] = {
RGB(0, 0, 0), // Black - черный
RGB(0, 0, 255), // Blue - синий
RGB(0, 255, 0), // Green - зеленый
RGB(0, 255, 255), // Cyan - голубой
RGB(255, 0, 0), // Red - красный
RGB(255, 0, 255), // Magenta - сиреневый
RGB(255, 255, 0), // Yellow - желтый
RGB(255, 255, 255) // White - белый
};
Линии будем рисовать во втором диалоговом окне, выбирая для каждой перо со своим цветом и стилем. Для этого вначале создадим все перья. Перед рисованием каждой линии нужно вычислять ее расположение на окне рисования. Для всего этого создадим для класса CPaintDlg отдельный метод, который назjвем DrawLine().
В панели Class View выберите класс CPaintDlg
Вызовите мастер Add Function и настройте его так

Мастер добавит код
Объявление функции в файле PaintDlg.h
class CPaintDlg : public CDialog
{
............................................
public:
// Таблица цветов
static const COLORREF m_crColors[8];
private:
// Рисование линий на холсте
void DrawLine(CPaintDC * pDC, int iColor);
};
Заготовка определения функции в файле PaintDlg.cpp
// Рисование линий на холсте
void CPaintDlg::DrawLine(CPaintDC * pDC, int iColor)
{
}
Заполните код функции DrawLine() так
Функция DrawLine() в файле PaintDlg.cpp
// Рисование линий на холсте
void CPaintDlg::DrawLine(CPaintDC * pDC, int iColor)
{
// Объявить и создать перья
CPen pnSolidPen(PS_SOLID, 1, m_crColors[iColor]);
CPen pnDashPen(PS_DASH, 1, m_crColors[iColor]);
CPen pnDotPen(PS_DOT, 1, m_crColors[iColor]);
CPen pnDashDotPen(PS_DASHDOT, 1, m_crColors[iColor]);
CPen pnDashDotDotPen(PS_DASHDOTDOT, 1, m_crColors[iColor]);
CPen pnNullPen(PS_NULL, 1, m_crColors[iColor]);
CPen pnInsidePen(PS_INSIDEFRAME, 1, m_crColors[iColor]);
CPen pnCombinationPen(PS_DOT | PS_GEOMETRIC | PS_ENDCAP_ROUND
, 1, m_crColors[iColor]);
// Получить область рисования
CRect rRect;
GetClientRect(rRect);
rRect.NormalizeRect();
// Вычислить расстояние между линиями
CPoint ptStart;
CPoint ptEnd;
int iDisp = rRect.Height() / 8; // Высота междурядья
CPen* pOldPen = NULL; // Обезопасили указатель
// Определить начальные точки линий
ptStart.y = rRect.top + iDisp / 2;
ptStart.x = rRect.left + iDisp / 2;
ptEnd.y = ptStart.y;
ptEnd.x = rRect.right - iDisp / 2;
// Рисуем каждую линию со своим пером
for(int count = 0; count < 8; count++){
// Выбрать для текущей линии перо
switch(count){
case 0: // Запоминаем старое перо контекста перед
// первой линией, чтобы затем вновь вернуть
// его контексту после прорисовки всех линий
pOldPen = pDC->SelectObject(pnSolidPen);
break;
case 1:
pDC->SelectObject(pnDashPen);
break;
case 2:
pDC->SelectObject(pnDotPen);
break;
case 3:
pDC->SelectObject(pnDashDotPen);
break;
case 4:
pDC->SelectObject(pnDashDotDotPen);
break;
case 5:
pDC->SelectObject(pnNullPen);
break;
case 6:
pDC->SelectObject(pnInsidePen);
break;
case 7:
pDC->SelectObject(pnCombinationPen);
} // end of switch
// Нарисовать линию
pDC->MoveTo(ptStart);
pDC->LineTo(ptEnd);
// Переместиться на позицию слудующей линии
ptStart.y += iDisp;
ptEnd.y = ptStart.y;
} // end of for
// Вернуть контексту его прежнее перо
pDC->SelectObject(pOldPen);
}
Вызов функции DrawLine() нужно размещать в месте, где линии будут рисоваться при каждом разрушении окна. Такое место одно - в функции OnPaint() второго окна. Но ее пока нет, поэтому ее нужно создать как обработчик сообщения WM_PAINT операционной системы.
Добавим обработчик события WM_PAINT в класс второго диалогового окна. Для этого:
Выделите в панели
Скорректируйте сгенерированный оболочкой обработчик так
Обработчик сообщения WM_PAINT класса CPaintDlg
void CPaintDlg::OnPaint()
{
CPaintDC dc(this); // Локальный контекст устройства
// Получить указатель на родительское окно
CGraphicsDlg* pWnd = (CGraphicsDlg*) GetParent();
if(!pWnd) return; // Если родитель не существует - выйти
if(pWnd->m_iTool == 0 // Если выбран режим Pen
pWnd->m_iShape == 0) // и если выбран режим Line
DrawLine(dc, pWnd->m_iColor); // Рисуем линии текущим цветом
}
Отметьте для себя, что здесь создается указатель на на класс главного окна и ему присваевается содержимое, возвращенное API -функцией GetParent(). Используется также операция приведение указателя.
Для видимости объявления класса CGraphicsDlg в файле PaintDlg.cpp включите в начало этого файла инструкцию препроцессора #include "GraphicsDlg.h"
Включение инструкции препроцессора в файл PaintDlg.cpp
// PaintDlg.cpp : implementation file
//
#include "stdafx.h"
#include "Graphics.h"
#include "PaintDlg.h"
#include ".\paintdlg.h"
#include "GraphicsDlg.h"
// CPaintDlg dialog
IMPLEMENT_DYNAMIC(CPaintDlg, CDialog)
CPaintDlg::CPaintDlg(CWnd* pParent /*=NULL*/)
: CDialog(CPaintDlg::IDD, pParent)
{
}
CPaintDlg::~CPaintDlg()
{
}
.....................................................
Закомментируйте отладочный код в обработчике OnRadioSelection() файла GraphicsDlg.cpp
Постройте приложение и убедитесь, что все пока работает как нужно
Должно получиться что-то подобное

Отметьте для себя, что при начальном запуске программы стили линий не соблюдаются. Попробуйте устранить это, рисуя вначале линии в контексте памяти, а затем, перекидывая этот контекст в контекст экрана с помощью функции . Примерные возможности рассмотрим ниже при работе с растрами.
Добавим в приложение код для рисования кругов и квадратов с помощью -функций Ellipse() и Rectangle(). Обеим функциям передается объект типа CRect, задающий прямоугольник, в который будет вписана рисуемая фигура. Функция Rectangle() заполняет все место в прямоугольнике, а функция Ellipse() рисует эллипс или круг, причем середина каждой стороны прямоугольника касается края эллипса.
Будем использовать в этих функциях текущие настройки пера и кисти, которые выберет пользователь. Для этого нужно их создать. В случае с пером можно использовать нулевое перо (стиль пера PS_NULL ), но когда речь идет о кисти, то нужно будет создать сплошную кисть, имеющую цвет фона окна (светло-серый).
Прямоугольники и эллипсы расположим на холсте в две строки по четыре фигуры в каждой строке. Для рисования фигур создадим отдельный метод, как и в случае с рисованием линий, и назовем его DrawRegion().
В панели Class View выберите класс CPaintDlg
Вызовите мастер Add Function и настройте его так

Мастер добавит код
Объявление функции DrawRegion() в файле PaintDlg.h
class CPaintDlg : public CDialog
{
.............................................
public:
// Таблица цветов
static const COLORREF m_crColors[8];
private:
// Рисование линий на холсте
void DrawLine(CPaintDC * pDC, int iColor);
public:
afx_msg void OnPaint();
private:
// Функция рисования примитивных фигур GDI - прямоугольников и эллипсов
void DrawRegion(CPaintDC * pDC, int iTool, int iShape, int iColor);
};
Заготовка определения функции DrawRegion() в файле PaintDlg.cpp
// Функция рисования примитивных фигур GDI - прямоугольников и эллипсов
void CPaintDlg::DrawRegion(CPaintDC * pDC, int iTool, int iShape, int iColor)
{
}
Заполните код функции DrawRegion() так
Функция DrawRegion() в файле PaintDlg.cpp
// Функция рисования примитивных фигур GDI - прямоугольников и эллипсов
void CPaintDlg::DrawRegion(CPaintDC * pDC, int iTool, int iShape, int iColor)
{
// Объявить и создать перья
CPen pnSolidPen(PS_SOLID, 1, m_crColors[iColor]);
CPen pnDashPen(PS_DASH, 1, m_crColors[iColor]);
CPen pnDotPen(PS_DOT, 1, m_crColors[iColor]);
CPen pnDashDotPen(PS_DASHDOT, 1, m_crColors[iColor]);
CPen pnDashDotDotPen(PS_DASHDOTDOT, 1, m_crColors[iColor]);
CPen pnNullPen(PS_NULL, 1, m_crColors[iColor]);
CPen pnInsidePen(PS_INSIDEFRAME, 1, m_crColors[iColor]);
CPen pnCombinationPen(PS_DOT|PS_GEOMETRIC|PS_ENDCAP_ROUND
, 1, m_crColors[iColor]);
// Объявить и создать кисти
CBrush brSolidBrush(m_crColors[iColor]);
CBrush brBDiagBrush(HS_BDIAGONAL, m_crColors[iColor]);
CBrush brCrossBrush(HS_CROSS, m_crColors[iColor]);
CBrush brDiagCrossBrush(HS_DIAGCROSS, m_crColors[iColor]);
CBrush brFDiagBrush(HS_FDIAGONAL, m_crColors[iColor]);
CBrush brHorizBrush(HS_HORIZONTAL, m_crColors[iColor]);
CBrush brVertBrush(HS_VERTICAL, m_crColors[iColor]);
CBrush brNullBrush(RGB(208, 208, 208)); // Светло-серый системный цвет
// Получить область рисования
// Все размеры GDI в пикселах
CRect rClientRect;
GetClientRect(rClientRect);
rClientRect.NormalizeRect();
// Параметры рисования
const int iDisp = 8;// Высота и ширина междурядья и краев клиентской области
int iHeight = (rClientRect.Height() - 3 * iDisp) / 2; // Высота фигуры для двух рядов
int iWidth = (rClientRect.Width() - 5 * iDisp) / 4; // Ширина фигуры для четырех в ряд
CRect rCurrentRect; // Текущие координаты рисования
CPen* pOldPen = NULL; // Создали указатель пера и сразу обезопасили
CBrush* pOldBrush = NULL; // Создали указатель кисти и сразу обезопасили
// Перебираем в цикле все перья и кисти
// и сразу рисуем
for(int count = 0; count < 8; count ++){
// Вычисляем текущие координаты привязки фигуры
if(count == 0){ // Начать первую строку
rCurrentRect.left = rClientRect.left + iDisp;// Координата X
rCurrentRect.right = rCurrentRect.left + iWidth;// Координата X
rCurrentRect.top = rClientRect.top + iDisp;// Координата Y
rCurrentRect.bottom = rCurrentRect.top + iHeight;// Координата Y
}
else if(count == 4){ // Начать вторую строку
rCurrentRect.left = rClientRect.left + iDisp;// Координата X
rCurrentRect.right = rCurrentRect.left + iWidth;// Координата X
rCurrentRect.top = rClientRect.top + 2 * iDisp + iHeight;// Координата Y
rCurrentRect.bottom = rCurrentRect.top + iHeight;// Координата Y
}
else{// Продолжить текущую строку
rCurrentRect.left = rCurrentRect.right + iDisp;// Координата X
rCurrentRect.right = rCurrentRect.left + iWidth;// Координата X
}
// Выбираем очередное перо и кисть
switch(count){
case 0: // Сплошная линия и сплошная заливка
// Сразу запомним старое перо и кисть
pOldPen = pDC->SelectObject(pnSolidPen);
pOldBrush = pDC->SelectObject(brSolidBrush);
break;
case 1: // Заливка штриховкой обратными косыми
pDC->SelectObject(pnDashPen);
pDC->SelectObject(brBDiagBrush);
break;
case 2: // Заливка вертикальной и горизонтальной штриховкой
pDC->SelectObject(pnDotPen);
pDC->SelectObject(brCrossBrush);
break;
case 3: // Заливка штриховкой по обеим диагоналям
pDC->SelectObject(pnDashDotPen);
pDC->SelectObject(brDiagCrossBrush);
break;
case 4: // Заливка диагональю из черточек и двух точек
pDC->SelectObject(pnDashDotDotPen);
pDC->SelectObject(brFDiagBrush);
break;
case 5: // Нулевая обводка, горизонтальная штриховка,
pDC->SelectObject(pnNullPen);
pDC->SelectObject(brHorizBrush);
break;
case 6: // Заливка вертикальной штриховкой
pDC->SelectObject(pnInsidePen);
pDC->SelectObject(brVertBrush);
break;
case 7: // Нулевая заливка
pDC->SelectObject(pnCombinationPen);
pDC->SelectObject(brNullBrush);
} // end of switch
// Анализируем переключатели группы Tool
// Чем рисовать: пером или кистью (что включено)?
// Мы заготовили и то и другое!!!
switch(iTool){
case 0: // Включен режим Tool = Pen - рисовать только пером
// Скрываем кисть
pDC->SelectObject(brNullBrush);
break;
case 1: // Включен режим Tool = Brush - отображать только заливку
// Скрываем перо
pDC->SelectObject(pnNullPen);
break;
default:
// Скрываем и то и другое на всякий случай
pDC->SelectObject(pnNullPen);
pDC->SelectObject(brNullBrush);
}
// Анализируем переключатели группы Shape
// Что рисовать: эллипс или прямоугольник (что включено)?
// Координаты расположения для каждой итерации у нас есть!!!
switch(iShape){
case 2: // Включен режим Shape = Square - рисовать прямоугольник
pDC->Rectangle(rCurrentRect);
break;
case 1: // Включен режим Shape = Circle - рисовать эллипс
pDC->Ellipse(rCurrentRect);
}
} // end of for
// Восстановить в контексте устройства первоначальные перо и кисть
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldBrush);
}
Вызов функции DrawRegion() нужно размещать в том же самом месте, где линии будут рисоваться при каждом разрушении окна и куда мы ранее поместили вызов функции DrawLine(). Это место - в функции OnPaint() второго окна, обслуживаемого классом CPaintDlg.
Дополните функцию OnPaint() соответствующим новым кодом
Изменения в функции OnPaint() файла PaintDlg.cpp
void CPaintDlg::OnPaint()
{
CPaintDC dc(this); // Локальный контекст устройства
// Получить указатель на родительское окно
CGraphicsDlg* pWnd = (CGraphicsDlg*) GetParent();
if(!pWnd) return; // Если родитель не существует - выйти
if(pWnd->m_iTool == 0 // Если выбран режим Pen
pWnd->m_iShape == 0) // и если выбран режим Line
DrawLine(dc, pWnd->m_iColor); // Рисуем линии
if(pWnd->m_iTool != 2 // Если не выбран растр Bitmap
pWnd->m_iShape != 0) // Если не выбран режим Line
// Рисуем прямоугольник, эллипс или их контуры текущим цветом
DrawRegion(dc, pWnd->m_iTool, pWnd->m_iShape, pWnd->m_iColor);
}
Постройте приложение и убедитесь, что все работает как нужно
Должно получиться что-то подобное

Созданное нами окно-холст имеет фиксированные размеры, установленные нами при проектировании его графического ресурса и не может их менять в процессе работы. Дадим возможность пользователю изменять размеры холста. Для этого:
Откройте диалоговый ресурс с идентификатором IDD_PAINT_DLG второго диалогового окна в окне дизайнера ресурсов и измените два его свойства такBorder = Resizing (с изменяемым размером)Title Bar = True (с заголовком окна)
Постройте приложение и убедитесь, что теперь холст способен изменять свои размерыНо при изменении размеров рисунки в нем размазываются и перерисовываются только после того, как окно закроется другим окном и возникнет необходимость перерисовать его полностью. Исправим это:
Вновь выделите ресурс диалогового окна холста в окне дизайнера ресурсов и в таблице свойств включите режим Messages
Найдите сообщение WM_SIZE, которое выдает Windows каждый раз, когда меняется размер окна, и создайте функцию-обработчик OnSize()Поскольку обработчик создается для диалогового окна холста, то именно это окно и будет обрабатывать это сообщение Windows, когда пользователь изменит его размеры.
Заполните обработчик так
Изменения в функции OnSize() файла PaintDlg.cpp
void CPaintDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
Invalidate(); // Перерисовать второе окно
}
Постройте приложение и убедитесь, что при изменении размеров окна холста все работает как нужно
Здесь осталась еще одна проблема, о которой мы упоминали ранее. При первом запуске приложения в холсте не прорисовываются стили линий или фигур. Для исправления этого можно поместить в функцию OnPaint() принудительный вызов обработчика OnRadioSelection(), который будет имитировать нажатие мышью на радиокнопке. Но чтобы этот вызов сработал только один раз при первом появлении окна, посадим его на условие. Для этого введем логическую переменную-флаг как член класса CGraphicsDlg, который поднимем в функции OnInitDialog(), а после первого срабатывания вызова OnRadioSelection() сразу опустим и более поднимать не будем.
Введите в класс CGraphicsDlg объявление переменной-флага вручную
Добавление в классе CGraphicsDlg файла GraphicsDlg.h
class CGraphicsDlg : public CDialog
{
...............................................
private:
bool b_singleFlag;// для первой прорисовки холста
};
Введите инициализацию переменной-флага в функцию OnInitDialog() класса CGraphicsDlg
Добавление в функции OnInitDialog() файла GraphicsDlg.cpp
BOOL CGraphicsDlg::OnInitDialog()
{
......................................................
// TODO: Add extra initialization here
// Создать диалоговое окно холста с текущим родителем
m_dlgPaint.Create(IDD_PAINT_DLG, this);
// Показать холст поверх родителя
m_dlgPaint.ShowWindow(SW_SHOW);
b_singleFlag = true;
return TRUE; // return TRUE unless you set the focus to a control
}
Добавьте принудительный вызов обработчика в функцию OnPaint() класса CGraphicsDlg
Добавление в функцию OnPaint() файла GraphicsDlg.cpp
void CGraphicsDlg::OnPaint()
{
................................................
else
{
CDialog::OnPaint();
}
if(b_singleFlag) // Вызов один раз
OnRadioSelection(); // для имитации нажатия на радиокнопке
if(b_singleFlag) // Сбросить флаг
b_singleFlag = !b_singleFlag;
}
Постройте приложение и убедитесь, что при начальном появлении окна все прорисовывается правильно
Здесь осталась еще одна проблема, требующая решения. Поперемещайте холст за заголовок, после его начального появления, по экрану и частично за границы экрана. Вы увидете, что прорисовка опять разрушается и не восстанавливается или восстанавливается неверно. Исправим это (чтобы кот не гулял сам по себе, как Мельникова):
Вновь выделите диалоговый ресурс IDD_PAINT_DLG в окне дизайнера ресурсов и в таблице свойств включите режим Messages
Найдите сообщение WM_MOVE, которое выдает Windows каждый раз, когда окно перемещается по экрану за его заголовок пользователем, и создайте функцию-обработчик OnMove()Поскольку обработчик создается для диалогового окна холста, то именно это окно и будет обрабатывать это сообщение Windows, когда пользователь переместит холст.
Заполните обработчик так
Функция-обработчик OnMove() файла PaintDlg.cpp
void CPaintDlg::OnMove(int x, int y)
{
CDialog::OnMove(x, y);
// TODO: Add your message handler code here
Invalidate(); // Перерисовать второе окно
}
Постройте приложениеТеперь при любом перемещении окна функция Invalidate() заставляет срабатывать обработчик OnPaint() класса CPaintDlg. А там мы встроили вызовы функций рисования фигур DrawLine() и DrawRegion(), которые срабатывают по текущим настройкам элементов управления главного окна.
Вместо городьбы огорода с флагом b_singleFlag и принудительным вызовом функции-обработчика OnRadioSelection(), можно поступить иначе. Можно просто послать холсту после его создания сообщение WM_MOVE, предварительно создав обработчик сообщения WM_ACTIVATE и поместив туда код PostMessage(WM_MOVE) . Можете подумать и попробовать, лично мне лень...
Добавим в наше приложение возможности для загрузки и отображения точечных (растровых) рисунков. Сразу заметим, что растровые рисунки содержат данные в формате .bmp, именно такой формат графических данных размещается в контексте устройства экрана. Загружать и отображать растровые рисунки можно несколькими способами:
Picture Control.bmp, хранящихся на дискеРассмотрим все эти способы.
Нужно иметь ввиду, что когда мы создаем растровые изображения как ресурсы, включая их в проект в режиме проектирования, то все файлы с изображениями встраиваются в исполнимый файл приложения при компиляции проекта (и соответственно - увеличивают его размеры). Поэтому такой способ приемлем, скорее, лишь для включения небольших картинок типа иконок.
Откройте диалоговый ресурс приложения с идентификатором IDD_ABOUTBOX
Выделите на форме элемент управления Picture Control, содержащий пиктограмму с иконкой приложения, и удалите его
Удалите все остальные элементы управления формы IDD_ABOUTBOX, включая и кнопку OK
Измените размеры формы на, примерно, 320x220 (для рисунка с запасом)
Поместите на форму из панели Toolbox новый элемент управления Picture Control и расположите его в верхнем левом углу рамки направляющих линий формы
Через контекстное меню панели Resource View

импортируйте файл MyPhoto.bmp из каталога Pictures этой лабораторной работы ( перед демонстрацией приложения преподавателю эту картинку обязательно замените на другую )
Заметьте при этом, что в каталоге res проекта появился новый файл с тем же именем. Оболочка при импорте "как Плюшкин" тащит все к себе. Это и понятно, завтра импортируемый файл могут удалить, а вдруг послезавтра опять придется строить приложение.
Переименуйте идентификатор ресурса на ID=IDB_ABOUT
Выделите на форме элемент Picture Control и через панель свойств настройте его параметрыType=BitmapImage = IDB_ABOUTReal Size Image = True
Подгоните форму под размеры рисунка
Отметьте для себя, что окно импорта ресурса готово было импортировать рисунок не только в формате .bmp. Если хотите, проверьте, можно ли загрузить в элемент Picture Control рисунок другого формата
Постройте приложение, запустите его
На заголовке главного окна вызовите контекстное меню и выполните опцию About... Должно получиться примерно следующее

Далее выполните следующее...Здесь мы будем загружать растровые ресурсы, которые должны создать на этапе проектирования и которые при компиляции будут храниться в исполнимом файле. Фактически мы создадим код, который будет только отображать эти ресурсы для пользователя на холсте, а не подкачивать в работающее приложение откуда-то со стороны.
В этом смысле растровые ресурсы можно загружать программно двумя почти идентичными способами:
API-Windows напрямуюMFC -класс CBitmapДалее мы увидим, что можно отображать рисунки не только как заранее (на этапе проектирования) загруженные ресурсы. Но можно отображать рисунки (и не только растровые формата .bmp) из файлов на диске через стандартное диалоговое окно File Open. Для программной загрузки рисунков из файлов можно использовать два класса:
CBitmap - только для загрузки файлов формата .bmp ( MFC -библиотека)CImage - для загрузки файлов других популярных форматов ( ATL -библиотека)Для всех перечисленных способов желательно проверить два варианта отображения рисунка:
BitBlt )StretchBlt )Воспользуемся только вторым способом и примем следующий порядок использования инструментов главной панели:
Кнопка Bitmap должна срабатывать только при включенном режиме Tool=Bitmap.Проверку этого условия мы разместим и в обработчике кнопки, и в обработчике OnPaint() перед вызовом функции вывода рисунка
Когда режим Tool=Bitmap включен, у нас остаются не у дел инструменты Shape и Color. Задействуем их так...
Режим Shape=Line приспособим для вывода ресурсов растровых рисунков формата .bmp с помощью API -функций Windows. При этом радиокнопками Color будем выбирать различные растровые рисунки, которые загрузим в ресурсы приложения на этапе проектирования
Режим Shape=Circle установим для загрузки растровых рисунков формата .bmp с помощью функций MFC -класса CBitmap через стандартное диалоговое окно File Open
Режим Shape=Square установим для загрузки растровых рисунков формата .bmp с помощью функций ATL -класса CImage через стандартное диалоговое окно File Open. При этом сделаем возможным загрузку и отображение не только .bmp -файлов, но и файлов других популярных форматов (например, JPEG и PNG )
При загрузке файлов через диалоговое окно кнопки Color использоваться не будут
Загрузите ресурсы Bitmap из каталога Pictures\Photo лабораторной работы. При этом импортируйте файлыPhoto1.bmp Photo2.bmp Photo3.bmp Photo4.bmp Photo5.bmp Photo6.bmp Photo7.bmp Photo8.bmp Имена дескрипторов оставьте как есть

Установите размеры окна холста 281x199Создадим в классе CGraphicsDlg общедоступную переменную strFileName для хранения имени выбранного файла. Для этого:
В панели Class View выделите класс CGraphicsDlg и через контекстное меню вызовите мастер, который заполните так

Объявление переменной strFileName файла GraphicsDlg.h
class CGraphicsDlg : public CDialog
{
..................................................
public:
// Переменная для хранения имени файла
CString strFileName;
};
Инициализация переменной strFileName файла GraphicsDlg.cpp
CGraphicsDlg::CGraphicsDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGraphicsDlg::IDD, pParent)
, m_iTool(0)
, m_iShape(0)
, m_iColor(7)
, strFileName(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
Поместите в файл PaintDlg.cpp инструкцию #include <atlimage.h> с объявлением класса CImage -библиотеки ( именно в конец группы инструкций include, как показано ниже, иначе компилятор заблудится и будет выдавать ошибку!)
Начало файла PaintDlg.cpp
// PaintDlg.cpp : implementation file
//
#include "stdafx.h"
#include "Graphics.h"
#include "PaintDlg.h"
#include ".\paintdlg.h"
#include "GraphicsDlg.h"
#include <atlimage.h>
// CPaintDlg dialog
IMPLEMENT_DYNAMIC(CPaintDlg, CDialog)
CPaintDlg::CPaintDlg(CWnd* pParent /*=NULL*/)
: CDialog(CPaintDlg::IDD, pParent)
{
}
........................................
Откройте ресурс IDD_GRAPHICS_DIALOG главного диалогового окна в режиме редактирования
Двойным щелчком по кнопке Bitmap создайте обработчик, который заполните так
Функция-обработчик кнопки Bitmap файла GraphicsDlg.cpp
void CGraphicsDlg::OnBnClickedBbitmap()
{
if(m_iTool != 2 || m_iShape == 0) return;
// Готовим фильтры
static char szFilterBmp[] = "Bitmap Files (*.bmp)|*.bmp||";
static char szFilterDefault[] =
"JPEG Files (*.jpg)|*.jpg|"
"PNG Files (*.png)|*.png|"
"Bitmap Files (*.bmp)|*.bmp||";
char *filter;// Вспомогательный указатель
// Определить фильтр
if(m_iShape == 1) // Circle для использования класса CBitmap
filter = szFilterBmp;
else // Оставшийся режим Shape==Square
filter = szFilterDefault;
// Открываем диалог для выбора файла
// Создать стандартное диалоговое окно File Open
CFileDialog dlgOpenFileDialog(TRUE, "", strFileName,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, filter);
// Отобразить диалоговое окно
if(dlgOpenFileDialog.DoModal() == IDOK){
// Заменить имя файла на выбранное для последующего отображения
strFileName = dlgOpenFileDialog.GetPathName();
m_dlgPaint.Invalidate(); // Перерисовать второе окно
}
}
Создайте функцию DrawImage(), прототип которой разместите в описании класса CPaintDlg файла PaintDlg.cpp
Прототип функции DrawImage() файла PaintDlg.h
class CPaintDlg : public CDialog
{
.............................................
public:
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnMove(int x, int y);
void DrawImage(CPaintDC * pDC, int iTool, int iShape, int iColor);
};
Определение функции разместите в фале PaintDlg.cpp и наполните кодом так
Определение функции DrawImage() в файле PaintDlg.cpp
void CPaintDlg::DrawImage(CPaintDC* dc, int iTool, int iShape, int iColor)
{
// Получить указатель на родительское окно
// для доступа к имени файла, полученного из File Open
CGraphicsDlg* pWnd = (CGraphicsDlg*) GetParent();
if(!pWnd) return; // Если родитель не существует - выйти
// Получить область рисования
CRect rClientRect;
GetClientRect(rClientRect);
rClientRect.NormalizeRect();
// Параметры рисования
const int iDisp = 7;// Отступы от краев клиентской области
int iWidth = rClientRect.Width() - 2 * iDisp; // Ширина области рисования
int iHeight = rClientRect.Height() - 2 * iDisp; // Высота области рисования
// Создадим ананимные перечисления локально,
// для удобства работы, чтобы не запутаться
enum{Pen, Brush, Bitmap};
enum{Line, Circle, Square};
enum{Black, Blue, Green, Cyan
,Red, Magenta, Yellow
,White}; // Режим для CImage отдельно, чтобы не забыть
/**************************************************************
Shape==Line - выводим ресурсы растровых
рисунков формата *.bmp
с помощью API-функций Windows.
Радиокнопки Color используем для выбора различных
растровых рисунков, которые загрузим
в ресурсы приложения на этапе проектирования.
**************************************************************/
if(iShape == Line){
HBITMAP hbitmap; // Создать объект для хранения изображения
int id;
switch(iColor){
case Black: id = IDB_BITMAP1; break;
case Blue: id = IDB_BITMAP2; break;
case Green: id = IDB_BITMAP3; break;
case Cyan: id = IDB_BITMAP4; break;
case Red: id = IDB_BITMAP5; break;
case Magenta: id = IDB_BITMAP6; break;
case Yellow: id = IDB_BITMAP7; break;
case White: id = IDB_BITMAP8;
}
// Поместить растровое изображение в память
hbitmap = ::LoadBitmap(::AfxGetInstanceHandle()
,MAKEINTRESOURCE(id));
// Создать контекст устройства в памяти
HDC hMemDC = ::CreateCompatibleDC(NULL);
// Поместить растровое изображение в dc памяти
SelectObject(hMemDC, hbitmap);
// Копировать содержимое dc памяти в dc экрана
::StretchBlt(dc->m_hDC, iDisp, iDisp, iWidth, iHeight,// Приемник: dc экрана
hMemDC, 0, 0, 400, 300, // Источник: dc памяти
SRCCOPY);
// Уничтожить dc памяти и растровое изображение
::DeleteDC(hMemDC);
::DeleteObject(hbitmap);
} // Конец if
/**************************************************************
Shape==Circle - отображаем файл формата *.bmp,
имя которого получено из File Open,
с помощью функций MFC-класса CBitmap.
Состояние радиокнопок Color значения не имеет.
**************************************************************/
if(iShape == Circle){
// Создать объект для хранения и загрузить в него изображение
HBITMAP hbitmap =
(HBITMAP) ::LoadImage(AfxGetInstanceHandle(),
pWnd->strFileName, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE | LR_CREATEDIBSECTION);
if(hbitmap){// Файл успешно загружен
// Создаем экземпляр класса CBitmap
CBitmap m_Bitmap;
// Прикрепляем загруженный рисунок к объекту класса
m_Bitmap.Attach(hbitmap);
// Создать родную структуру для объекта класса CBitmap
BITMAP bm;
// Перекачать загруженное в структуру HBITMAP изображение
// в родную для объекта m_Bitmap структуру BITMAP
m_Bitmap.GetBitmap(bm);
// Создать совместимый контекст устройства в памяти
// по переданному в функцию параметру CPaintDC* dc
CDC dcMem;
dcMem.CreateCompatibleDC(dc);
// Поместить растровое изображение в dc памяти
dcMem.SelectObject(m_Bitmap); // Может адрес?
// Копировать и изменить размеры точечного
// рисунка по диалоговому окну холста
dc->StretchBlt(iDisp, iDisp, iWidth, iHeight,// Приемник: dc экрана
dcMem, 0, 0, bm.bmWidth, bm.bmHeight,// Источник: dc памяти
SRCCOPY);
}
}
/**************************************************************
Shape==Square- выводим стандартное диалоговое окно
File Open для загрузки и
отображения .bmp-файлов или файлов других
популярных форматов (например, JPEG и PNG).
Состояние радиокнопок Color значения не имеет.
**************************************************************/
if(iShape == Square){
// Создать экземпляр класса CImage
CImage m_Image;
// Загрузить в него рисунок из файла
if(pWnd->strFileName.GetLength() > 0){ // Если выбрано имя файла
m_Image.Load(pWnd->strFileName);
// Задать координаты рисования
CRect rRect(iDisp, iDisp, iDisp + iWidth, iDisp + iHeight);
// Отобразить на контексте экрана
m_Image.Draw(dc->m_hDC, rRect);
}
}
}
Разместите вызов функции DrawImage() в обработчике OnPaint() файла PaintDlg.cpp, дополнив его так
Вызов функции DrawImage() в файле PaintDlg.cpp
void CPaintDlg::OnPaint()
{
CPaintDC dc(this); // Локальный контекст устройства
// Получить указатель на родительское окно
CGraphicsDlg* pWnd = (CGraphicsDlg*) GetParent();
if(!pWnd) return; // Если родитель не существует - выйти
if(pWnd->m_iTool == 0 // Если выбран режим Pen
pWnd->m_iShape == 0) // и если выбран режим Line
DrawLine(dc, pWnd->m_iColor); // Рисуем линии
if(pWnd->m_iTool != 2 // Если не выбран растр Bitmap
pWnd->m_iShape != 0) // Если не выбран режим Line
// Рисуем прямоугольник, эллипс или их контуры текущим цветом
DrawRegion(dc, pWnd->m_iTool, pWnd->m_iShape, pWnd->m_iColor);
if(pWnd->m_iTool == 2) // Если выбран рисунок
// Выдаем рисунки
DrawImage(dc, pWnd->m_iTool, pWnd->m_iShape, pWnd->m_iColor);
}
Запустите приложение и проверьте работу на файлах разных форматов
Обратите внимание на размер исполнимого файла - он недопустимо большой. Это потому, что растровые рисунки мы включили в ресурсы.

Интерфейс графических устройств Windows ( ) представляет собой виртуальное графическое устройство для всех приложений независимо от конкретных аппаратных средств, из которых состоит компьютер. является неотъемлемой частью ядра операционной системы, контролирующей и обеспечивающей все операции графического вывода приложений Windows.
Корпорация Microsoft создала , чтобы отделить графические операции от фактически используемого оборудования. Для программиста предоставлены графические функции высокого уровня абстракции, которые обеспечивают одинаковые результаты выполнения независимо от используемого оборудования. От разработчиков аппаратных средств требуется обеспечить поставку и драйверов (программ управления оборудованием на низком уровне) для естественного подключения их к функциям .
Пользовательская сторона (пользователь - это разработчик приложения) реализована с помощью контекста устройства. Контекст графического устройства - это конкретная настройка на выполнение графических операций. Управление контекстом устройства обеспечивается MFC -классом . Объект, созданный с помощью этого класса, содержит все необходимые настройки , установленные по умолчанию или пользователем (разработчиком приложения). Фактически экземпляр класса или производных от него классов и представляет собой контекст устройства, олицетворяя собой все настройки .
Контекст устройства способен обеспечить вывод изображений на любое внешнее устройство, будь то экран, принтер, . Для отображения изображения на экране приложению необходимо иметь контекст устройства экрана, для отображения на принтере - контекст другого устройства - принтера.
Контексты экрана и принтера называются физическими контекстами или контекстами вывода. Существуют еще два контекста устройства: растр и метафайл. Растр является тем же контекстом устройства, содержащим набор пикселов и по своей структуре готовый для подключению к физическому контексту, но пока хранящийся в памяти. Метафайл является набором готовых инструкций, способных создать графическое изображение перед подключением к физическому контексту.
Контекстов устройства, как экземпляров класса , одновременно может быть создано в приложении несколько и все они будут храниться в памяти готовыми для подключения, но только один из них в текущий момент времени может быть связан с конкретным физическим устройством вывода информации. т.е. только один может служить физическим контекстом ( контекстом вывода ). Существуют наследники класса , создающие контексты в частных случаях, например, CPaintDC, CClientDC. Поскольку
Объекты контекста устройства, а равным образом и все различные объекты рисунка, классифицируются в Windows как ресурсы. Операционная система может иметь ограниченное количество таких ресурсов и они могут исчерпаться, если приложение занимает их и вовремя не освобождает. Эта потеря известна как утечка ресурсов и во многом подобна утечке памяти, поскольку тоже в конечном итоге приводит к блокировке системы.
Чтобы избежать утечки подобных ресурсов, удобнее создавать их как локальные на стеке в пределах тех функций, где они будут использоваться. Соответственно при выходе из такой функции ресурс автоматически освобождается. Единственное реальное исключение, - когда объект контекста устройства создается самой Windows и передается в функцию обработки события в качестве параметра pDC. Например
void CCircleView::OnDraw(CDC* pDC)
{
...................
}
Некоторые свойства (атрибуты) контекста устройства представляют собой простые типы данных, встроенные прямо в контекст устройства. Но большинство графических атрибутов контекста устройства представляют собой сложные данные типа структур или даже классов со своими функциями членами. Экземпляры этих классов называются графическими атрибутами контекста устройства. Экземпляры этих классов нужно создавать, настраивать и подключать к контексту устройства, чтобы они должным образом влияли на графический вывод.
Базовым типом данных Windows для хранения цветов является структура COLORREF. Проще всего задать цвет при помощи макрокоманды RGB, которой передаются три числовых значения от 0 до 255. Например:
COLORREF crRed = RGB(255, 0, 0);
COLORREF crGreen = RGB(0, 255, 0);
COLORREF crBlue = RGB(0, 0, 255);
Такой способ задания цвета в называется логическим цветом. Но это только наше пожелание, а реальное отображение цвета определяется способностью аппаратного устройства. Монитор с высокой разрешающей способностью, расчитанный на отображение 24-битной или 32-битной глубины цвета не будет иметь никаких расхождений между логическим и физическим цветом. Для более слабых мониторов будет сам осреднять цвета, подбирая наибольшее сходство между логическим (запрашиваемым) и физическим (реально отображаемым) цветом. Аналогичный процесс происходит и для принтеров.
Таким образом, если, например, красный цвет запрошен у физического устройства, способного его отобразить, то все нормально - мы получим то, что требовалось. Но если физическое устройство не способно отобразить запрошенный цвет, то оно применит самый близкий (с точки зрения ). Для черно-белых принтеров стремиться подобрать наиболее близкие цвета через оттенки серого, что иногда никуда не годится. Чтобы решить эту проблему, можно просто не запрашивать недоступные цвета. Чтобы выяснить, сколько цветов поддерживает конкретное физическое устройство, нужно обратиться к функции контекста этого устройства
int nColors = pDC->GetDeviceCaps(NUMCOLORS);
Для цветных мониторов система предоставляет стандартную палитру из 20 цветов, на основании которой формируются все другие цвета.
Стандартная цветовая палитра | ||||||
|---|---|---|---|---|---|---|
const COLORREF |
g_crBlack |
= RGB( |
0, | 0, | 0 | ) |
const COLORREF |
g_crYellow |
= RGB( |
255, | 255, | 0 | ) |
const COLORREF |
g_crDkYellow |
= RGB( |
128, | 12 | 0 | ) |
const COLORREF |
g_crRed |
= RGB( |
255, | 0, | 0 | ) |
const COLORREF |
g_crDkRed |
= RGB( |
128, | 0, | 0 | ) |
const COLORREF |
g_crMagenta |
= RGB( |
255, | 0, | 255 | ) |
const COLORREF |
g_crDkMagenta |
= RGB( |
128, | 0, | 128 | ) |
const COLORREF |
g_crBlue |
= RGB( |
0, | 0, | 255 | ) |
const COLORREF |
g_crDkBlue |
= RGB( |
0, | 0, | 128 | ) |
const COLORREF |
g_crCyan |
= RGB( |
0 | 255, | 255 | ) |
const COLORREF |
g_crDkCyan |
= RGB( |
0, | 128, | 128 | ) |
const COLORREF |
g_crGreen |
= RGB( |
0, | 255, | 0 | ) |
const COLORREF |
g_crDkGreen |
= RGB( |
0, | 128, | 0 | ) |
const COLORREF |
g_crGray |
= RGB( |
192, | 192, | 192 | ) |
const COLORREF |
g_crDkGray |
= RGB( |
128, | 128, | 128 | ) |
const COLORREF |
g_crWhite |
= RGB( |
255, | 255, | 255 | ) |
const COLORREF |
g_crLtYellow |
= RGB( |
255, | 251, | 240 | ) |
const COLORREF |
g_crLtGreen |
= RGB( |
192, | 220, | 192 | ) |
const COLORREF |
g_crLtBlue |
= RGB( |
166, | 202, | 240 | ) |
const COLORREF |
g_crMedGray |
= RGB( |
160, | 160, | 164 | ) |
Класс контекста устройства имеет множество методов, но существуют три основных направления применения этих методов для управления функциями :
Для каждого из них в существуют свои функции управления, потому что на уровне драйверов устройств каждый из этих наборов операций имеет свою реализацию и выполняется по разному.
Несмотря на то, что текст на экране, принтере или файле представляется точно так же, как в консольном приложении или в приложении DOS, это графический текст. Графическое представление текста, обеспечиваемое , предоставляет неограниченные возможности по его оформлению - изменение шрифта, цвета, фона, добавление теней, прозрачности и т.д. Перечислим основные функции работы с текстом.
COLORREF GetTextColor() - возвращает текущий цвет текста.COLORREF SetTextColor(COLORREF) - устанавливает цвет для вывода текста. Возвращает предыдущий цвет текста в виде значения типа COLORREF. Например, красный текст можно установить такpDC->SetTextColor(RGB(255, 0, 0)); pDC->TextOut(x, y, "Это красный текст");
int GetBkMode() - возвращает режим отображения фона текста или рисунка ( OPAQUE - непрозрачный, TRANSPARENT - прозрачный). Режим OPAQUE установлен по умолчанию.int SetBkMode(int) - устанавливает режим отображения фона ( OPAQUE - непрозрачный, TRANSPARENT - прозрачный). Возвращает предыдущий режим.COLORREF GetBkColor() - возвращает текущий цвет фона текста. Цвет фона закрашивает само поле текста, а не его символы.COLORREF SetBkColor(COLORREF) - устанавливает цвет фона для вывода текста. Возвращает предыдущий цвет фона текста в виде значения типа COLORREF. Для установленного режима TRANSPARENT цвет фона при прорисовке текста не учитывается.
Для выбора цвета пользователем ему можно предоставить стандартное диалоговое окно следующим кодом
Вызов стандартного окна выбора цвета
CColorDialog colorDlg;
if(colorDlg.DoModal() == IDOK){
COLORREF color = colorDlg.GetColor();
CString strText;
strText.Format("Вы выбрали цвет RGB( %d, %d, %d )"
, GetRValue(color)
, GetGValue(color)
, GetBValue(color)
);
AfxMessageBox(strText);
}
SetTextAlign(int nFlag) - выравнивание текста. Выравнивание текста описывает отношение между указанной координатами (x, y) начальной точкой отображения текста и самим полем, занимаемым текстом. По умолчанию принято выравнивание текста ниже и правее начальной точки вывода текста. Этой функции передается единственный параметр, представляющий собой комбинацию флагов, приведенных в таблице. Флаги, находящиеся в одном столбце, являются взаимоисключающими, т.е. из каждого столбца можно выбрать только один флаг. Флаги, указанные в первой строке, установлены по умолчанию.
| Константы выравнивания текста | ||
|---|---|---|
| По оси x | По оси y | Обновить текущую позицию |
TA_LEFT |
TA_TOP |
TA_NOUPDATECP |
TA_CENTER |
TA_BASELINE |
TA_UPDATECP |
TA_RIGHT |
TA_BOTTOM |
|
Например, в приведенном коде текст будет выровнен по правому краю
pDC->SetTextAlign( TA_RIGHT | TA_TOP ); pDC->TextOut(x, y, "Это выравненный справа текст");
SetTextJustification() - выравнивание текста по ширине. Функция позволяет установить количество пикселов, которые необходимо добавить в каждый символ пробела.SetTextCharacterExtra() - как и предыдущая функция, добавляет пискселы, но не только в пробелы, но и в символы текста.TextOut(int x, int y, CString strText) - размещает текст на поверхности рисунка и принимает три параметраx левого верхнего угла поля вывода текстаy левого верхнего угла поля вывода текстаTabbedTextOut() - отображает текст подобно функции TextOut(), но позволяет задать массив позиций табуляции, чтобы обеспечить представление текста в виде столбцов.ExtTextOut() - более универсальная функция вывода текста, позволяющая контролировать ширину даже отдельных символов. Во-первых, позволяет указать произвольный прямоугольник, ограничивающий область вывода текста. Во-вторых, позволяет отобразить текст в непрозрачном (заполненном цветом фона) прямоугольнике. И в-третьих, позволяет управлять шириной отображаемого тектса (интервалом между символами).GrayString() - создает пятнистый текст, аналогичный используемому в меню, когда соответствующий пункт недоступен.CSize GetTextExtent(CString , int) - вычисляет размер области, которую займет строка текста на рисунке в пикселах при использовании текущего шрифта. Эта функция принимает строку CString , где содержится текст, и возвращает структуру Size, которая имеет два члена: cx - определяющий ширину текста и cy - определяющий высоту текста. Например, приведем код, где текст центрируется относительно курсора.Вывод текста относительно центра курсора
// Отобразить фразу
// при помощи объекта MFC CClientDC
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
// Создать экземпляр clientDC класса CClientDC
CClientDC clientDC(this);// контекст для текущего окна
// Создать объект с текстом
CString strText = "Привет студентам от Снеткова В.М.!";
// Получить размер текстовой строки в пикселах
CSize size =
clientDC.GetTextExtent(strText. strText.GetLength());
// Вычислить такие координаты начала отображения строки,
// чтобы центр текста соответствовал курсору мыши
int xCentered = point.x - (size.cx / 2);
int yCentered = point.y - (size.cy / 2);
// Отобразить текст в окне
clientDC.TextOut(xCentered, yCentered, strText);
}
Следует договориться о терминах. В текущая позиция контекста устройства, где готова рисовать графическая функция, не видна на экране. Ее удобно называть графическим курсором по аналогии с текущей позицией экрана в DOS, переключенного в
CPoint GetCurrentPosition() - возвращает координаты графического курсора GDI .SetPixel(int x, int y, COLORREF RGB(RED, GREEN, BLUE)) - рисует точку текущим пером в заданной позиции графического курсора.MoveTo(int x, int y) - перемещает графический курсор в заданную точку.LineTo(int x, int y) - соединяет текущую позицию графического курсора с заданной точкой линией, цвет и толщина которой определяется параметрами текущего пера. Текущая позиция курсора перемещается в конец линии.Polyline (CPoint point[], int count); PolylineTo(CPoint point[], int count) - обе функции принимают в качестве параметров последовательность точек и рисуют отрезки прямых между ними. Первый параметр - указатель на массив объектов CPoint, второй - количество точек в массиве. Когда функция PolylineTo() завершает работу (возвращает управление клиенту), текущая позиция графического курсора остается в последней точке массива.Polygon(CPoint point[], int count) - рисует замкнутый многоугольник (полигон) по тем же параметрам, что и функции Polyline () и PolylineTo(). Единственное ее отличие - она дорисовывает замыкающее звено, соединяющее последнюю и начальную точки.Rectangle(CRect rect) - рисует прямоугольник по точкам объекта CRect текущим пером и кистью.RoundRect(CRect rect, CPoint ellipse) - рисует прямоугольник со скругленными углами текущим пером и кистью. Второй параметр класса CPoint в значении x содержит ширину, а в значении y - высоту эллипса, который используется для скругления углов.Ellipse(CRect rect); Ellipse(int x1, int y1, int x2, int y2) - CRect, либо координатами точек левого верхнего и правого нижнего углов. Для прямоугольника с равными сторонами рисуется круг. Эллипс рисуется с помощью текущего пера и кисти.Arc(CRect rect, CPoint ptStart, CPoint ptEnd); Arc(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); ArcTo(CRect rect, CPoint ptStart, CPoint ptEnd); ArcTo(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); - рисуют дугу между двумя точками. Первая координата rect (или x1,y1 и x2,y2 ) определяет описывающий прямоугольник, остальные - начальную и конечную точки дуги. Дуга рисуется против часовой стрелки от начала к концу. Функция ArcTo() после рисования бросает текущую позицию графического курсора в конечной точке дуги.Pie(CRect rect, CPoint ptStart, CPoint ptEnd); Pie(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); - рисует сегмент эллипса. Она принимает те же параметры, что и функции Arc(), ArcTo(). Дуга на внешнем конце сегмента определяется также, как и в Arc(), ArcTo(). Острие сегмента находится в центре описывающего прямоугольника rect.Видеопамять - специфический участок оперативной памяти, содержимое которого немедленно и постоянно отображается на экране. Растр ( bitmap - битовая карта) представляет собой участок оперативной памяти, по структуре являющийся точной копией видеопамяти. Однако в отличие от видеопамяти содержимое растровой памяти ( bitmap memory ) не является отображаемым непосредственно на экране.
Оперативная память, занятая растром, содержит данные, соответствующие прямоугольной области. В терминологии API Windows дескриптор занятой растром области памяти известен как HBITMAP. В этой области памяти можно располагать данные, перемещение которых в видеопамять приведет к отображению на экране изображения или узора.
Растровые графические объекты выводятся на экран способом, отличным от применяемого при прорисовке таких объектов, как линии и эллипсы. Параметры линий и эллипсов вычисляются математически, а затем состояние пикселов в видеопамяти изменяется таким образом, чтобы на экране появился соответствующий объект. Данные растра не вычисляются. В растровой памяти уже находится готовое изображение. В итоге все сводится к тому, чтобы скопировать данные из растровой памяти в видеопамять.
Для управления растром могут использоваться
API -функции, которые обращаются к растрам по HBITMAPMFC -класс CBitmap, который является оболочкой для API -функций Windows работы с растромПостроим приложение, которое будет иметь два окна, связанных дочерне-родительскими отношениями. Первое окно будет содержать различные опции, позволяющие выбрать форму, инструмент и цвет. Второе окно выступит в роли холста, где будут отображаться все выбранные настройки. Пользователи могут выбирать, что отобразится на холсте: линии, квадраты или растровый рисунок. Они смогут также задать цвет, перо и кисть для кругов или прямоугольников.
Создайте новый проект
Application Type Application Type Dialog Based Resource language:Project style:MFC standard Use of MFC:Use MFC in a static library User Interface Features Main frame styles:System menu - важно!!!About box - важно!!!Dialog title:GraphicsAdvanced Features
После создания мастером главной формы удалите из диалога все элементы управления
Русифицируйте ресурсы диалоговых окон с идентификаторами IDD_GRAPHICS_DIALOG и IDD_ABOUTBOX
Измените заголовок главного диалогового окна, указав фамилию, номер и название лабораторной работы
Измените свойство Minimize Box главного диалогового окна на True
Отредактируйте диалоговый ресурс IDD_ABOUTBOX
Замените иконку приложения на любую иную по выборуОкно должно содержать три группы переключателей:
Окно должно иметь две кнопки:
File Open для выбора рисунка
Разработайте показанный дизайн окна с соблюдением отмеченных рекомендаций
![]() |
Group в значение TrueID на более осмысленные в соответствии с названием, например, IDC_RPEN, IDC_RBRUSH и т.д., а для кнопок: IDC_BBITMAP, IDC_BEXITПереодически стройте приложение для контроля возможных ошибок |
После создания диалогового окна нужно назначить по одной переменной для каждой группы переключателей. Для этого нужно создать целочисленную переменную для первого переключателя каждой группы. Для всех последующих переключателей группы назначенная переменная будет принимать последовательно возрастающие значения в порядке возрастания значений их идентификаторов. Именно по этой причине важно создавать все переключатели группы по порядку. Если добавление выполнялось не по порядку, в режиме Format/Tab Order можно установить нужный порядок следования.
Для первых переключателей каждой группы назначьте переменные в соответствии с таблицей| Переменные для управления переключателями | ||||
|---|---|---|---|---|
| Объект | Переменная | Категория | Тип | Доступ |
IDC_RPEN |
m_iTool |
Value |
int |
public |
IDC_RLINE |
m_iShape |
Value |
int |
int |
IDC_RBLACK |
m_iColor |
Value |
public |
public |
Для этого выделите каждый первый переключатель группы и через контекстное меню вызывайте мастер Add Variable. Поля мастера заполняйте по образцу

Мастер добавит код
Файл GraphicsDlg.h
class CGraphicsDlg : public CDialog
{
....................................................
public:
// Для управления переключателями группы Tool
int m_iTool;
// Для управления переключателями группы Shape
int m_iShape;
// Для управления переключателями группы Color
int m_iColor;
};
Файл GraphicsDlg.cpp
CGraphicsDlg::CGraphicsDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGraphicsDlg::IDD, pParent)
, m_iTool(0)
, m_iShape(0)
, m_iColor(0)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
Файл GraphicsDlg.cpp
void CGraphicsDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Radio(pDX, IDC_RPEN, m_iTool);
DDX_Radio(pDX, IDC_RLINE, m_iShape);
DDX_Radio(pDX, IDC_RBLACK, m_iColor);
}
Создайте обработчик для клавиши Exit. Введите в обработчик код OnOK()
Запустите приложение и убедитесь, что переключатели каждой группы работают независимо
Установите, чтобы при запуске приложения в группе Color был выделен переключатель White. Для этого измените в конструкторе класса CGraphicsDlg индекс инициализации переменной группы
Файл GraphicsDlg.cpp
CGraphicsDlg::CGraphicsDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGraphicsDlg::IDD, pParent)
, m_iTool(0)
, m_iShape(0)
, m_iColor(7)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
Добавим окно для использования в качестве холста рисования. Оно будет открыто во все время работы приложения. На нем не будет никаких управляющих элементов - только холст.
Раскройте дерево ресурсов проекта и, вызывая для каждого типа ресурса контекстное меню, обратите внимание, как меняется название последней опции контекстного меню Insert с добавлением имени типа ресурса
На узле Dialog из контекстного меню выполните опцию Insert Dialog
Удалите из нового диалогового окна, которое мы будем в дальнейшем использовать как холст, все элементы управления
Выделите диалоговое окно и в панели свойств установите свойство System Menu в значение False. Убедитесь, что сразу исчезнет системная кнопка закрытия окна. Это исключает возможность для пользователя закрыть окно до окончания работы всего приложения
Присвойте заголовку окна значение "Холст"
Присвойте окну идентификатор ресурса IDD_PAINT_DLG. Обратите внимание, что для диалоговых окон принято применять префикс IDD_, а для элементов управления - IDC_
Щелкните правой кнопкой мыши на диалоговом окне холста и выполните опции Add Class контекстного меню. Заполните поля мастера создания класса, управляющего диалоговым окном, так

Примечание. Перед добавлением нового класса нужно удостовериться, что текущим выделением является именно диалоговое окно, а не иной элемент или код. В противном случае мастер не сможет понять, что класс нужно создавать для диалогового окна.
Нам нужно в описании класса главного диалогового окна приложения включить переменную как экземпляр класса диалогового окна холста и через нее управлять холстом из главного окна. Для этого:
Перейдите на вкладку Class View и выделите класс CGraphicsDlg
Вызовите мастер Мастер добавит код
Файл GraphicsDlg.h
// GraphicsDlg.h : header file
//
#pragma once
#include "paintdlg.h"
// CGraphicsDlg dialog
class CGraphicsDlg : public CDialog
{
...................................................
private:
// Экземпляр класса CPaintDlg для управления холстом
CPaintDlg m_dlgPaint;
};
Файл GraphicsDlg.cpp
CGraphicsDlg::CGraphicsDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGraphicsDlg::IDD, pParent)
, m_iTool(0)
, m_iShape(0)
, m_iColor(7)
, m_dlgPaint(0)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
Удалите приведенную инициализацию переменной m_dlgPaint из конструктора класса CGraphicsDlg - она не нужна
Проверьте обязательно - должна быть включена инструкция #include "paintdlg.h" в начало файла GraphicsDlg.h
Добавьте в метод инициализации кодФайл GraphicsDlg.cpp
BOOL CGraphicsDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
...........................................................
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
// Создать диалоговое окно холста с текущим родителем
m_dlgPaint.Create(IDD_PAINT_DLG, this);
// Показать холст поверх родителя
m_dlgPaint.ShowWindow(SW_SHOW);
return TRUE; // return TRUE unless you set the focus to a control
}
Постройте приложение и убедитесь, что холст появляется вместе с родителем, сворачивается и закрывается вместе с ним, при этом находится всегда поверх родителяТакие же родительско-дочерние отношения существуют между главным диалоговым окном и всеми помещенными в него элементами управления. Каждый управляющий элемент - наследник своего диалогового окна. В некотором смысле второе диалоговое окно является просто еще одним управляющим элементом первого диалогового окна.
Поскольку все закрепленные за переключателями переменные объвлены в первом окне как общедоступные, то второе диалоговое окно может их видеть и по их состоянию реагировать на рисование должным образом. Моно разместить все функции рисования графики в классе второго диалогового окна. Однако для того, чтобы своевременно обновлять значения переменных, а также сообщать второму диалоговому окну, что нужно вывести графику, некоторые функции должны находиться в первом диалоговом окне.
Всякий раз, когда окно должно быть перерисовано заново (если оно было заслонено другим окном или свернуто), операционная система вызывает функцию диалогового окна OnPaint(). В ней можно разместить все функции рисования графики. Но одновременно во втором диалоговом окне пока нет такой функции, но мы ее позже определим.. Как заставить второе диалоговое окно обновляться каждый раз, когда мы в нем что-нибудь нарисуем?
Для этого можно применить к нему функцию Invalidate() (Считать недействительным). Вызов этой функции автоматически вызывает OnPaint() как функцию-член базового класса CDialog. Функция Invalidate() не требует никаких параметров и является членом класса CWnd, так что может использоваться для любого окна или элемента управления. Она сообщает окну и операционной системе, что отображаемая область окна более не соответствует действительности и требуется ее перерисовка.
На данном этапе замкнем все переключатели на один комбинированный обработчик, в котором будет организована синхронизация работы элементов управления и значений закрепленных за ними переменных с помощью функции UpdateData(TRUE) ( TRUE - привести переменные в соответсвие с экранным представлением элементов управления "экран->переменные", FALSE - привести экранное представление элементов управления в соответствие со значениями ассоциированных с ними переменных "переменные->экран"). Здесь же, принимая во внимание, что любое изменение состояния переключателей должно приводить к изменению графики на холсте, предусмотрим вызов обновления второго диалогового окна с помощью функции Invalidate() второго окна.
Откройте первое диалоговое окно и выделите на нем первый переключатель IDC_RPEN. Через контекстное меню элемента выполните опцию Add

Появится мастер, который заполните так

Мастер создаст шаблон обработчика и зарегистрирует его в карте сообщений
Объявление обработчика в файле GraphicsDlg.h
class CGraphicsDlg : public CDialog
{
...................................................
private:
// Экземпляр класса CPaintDlg для управления холстом
CPaintDlg m_dlgPaint;
public:
afx_msg void OnRadioSelection();
};
Шаблон обработчика в файле GraphicsDlg.cpp
void CGraphicsDlg::OnRadioSelection()
{
// TODO: Add your control notification handler code here
}
Регистрация обработчика в карте сообщений в файле GraphicsDlg.cpp BEGIN_MESSAGE_MAP(CGraphicsDlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_BEXIT, OnBnClickedBexit) ON_BN_CLICKED(IDC_RPEN, OnRadioSelection) END_MESSAGE_MAP()
Отредактируйте обработчик и карту сообщений так
Обработчик в файле GraphicsDlg.cpp
void CGraphicsDlg::OnRadioSelection()
{
UpdateData(TRUE); // Обновляем переменные с экрана
m_dlgPaint.Invalidate(); // Перерисовать второе окно
// Ниже код для отладки, потом убрать !!!!!
CString str;
str.Format("Tool=%d; Shape=%d; Color=%d", m_iTool, m_iShape, m_iColor);
MessageBox(str, "Состояние переключателей");
}
Карта сообщений в файле GraphicsDlg.cpp BEGIN_MESSAGE_MAP(CGraphicsDlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_BEXIT, OnBnClickedBexit) ON_BN_CLICKED(IDC_RPEN, OnRadioSelection) ON_BN_CLICKED(IDC_RBRUSH, OnRadioSelection) ON_BN_CLICKED(IDC_RBITMAP, OnRadioSelection) ON_BN_CLICKED(IDC_RLINE, OnRadioSelection) ON_BN_CLICKED(IDC_RCIRCLE, OnRadioSelection) ON_BN_CLICKED(IDC_RSQUARE, OnRadioSelection) ON_BN_CLICKED(IDC_RBLACK, OnRadioSelection) ON_BN_CLICKED(IDC_RBLUE, OnRadioSelection) ON_BN_CLICKED(IDC_RGREEN, OnRadioSelection) ON_BN_CLICKED(IDC_RCYAN, OnRadioSelection) ON_BN_CLICKED(IDC_RRED, OnRadioSelection) ON_BN_CLICKED(IDC_RMAGENTA, OnRadioSelection) ON_BN_CLICKED(IDC_RYELLOW, OnRadioSelection) ON_BN_CLICKED(IDC_RWHITE, OnRadioSelection) END_MESSAGE_MAP()
Запустите приложение и проверьте с помощью отладочного кода реагирование обработчика на нажатия на все переключатели, затем уберите отладочный код из обработчикаЧтобы имет возможность рисовать фигуры различным цветом в соответствии с состоянием группы переключателей Color, создадим в класса CPaintDlg массив структур типа COLORREF, необходимый для выбора цвета пера и кисти рисования. Массив будет состоять из 8 элементов, каждый из которых будет содержать цвет, соответствующий переключателям группы Color главного окна.
Добавьте новую глобальную переменную в класс второго диалогового окна, которая будет являться массивом цветов. Для этого в конец объявления класса CPainDlg введите код
Объявление цвета в файле PaintDlg.h
class CPaintDlg : public CDialog
{
............................................
public:
// Таблица цветов
static const COLORREF m_crColors[8];
};
В конце файла PaintDlg.cpp вне любой из функций (глобально) введите код
Определение цвета в файле PaintDlg.cpp
const COLORREF CPaintDlg::m_crColors[] = {
RGB(0, 0, 0), // Black - черный
RGB(0, 0, 255), // Blue - синий
RGB(0, 255, 0), // Green - зеленый
RGB(0, 255, 255), // Cyan - голубой
RGB(255, 0, 0), // Red - красный
RGB(255, 0, 255), // Magenta - сиреневый
RGB(255, 255, 0), // Yellow - желтый
RGB(255, 255, 255) // White - белый
};
Линии будем рисовать во втором диалоговом окне, выбирая для каждой перо со своим цветом и стилем. Для этого вначале создадим все перья. Перед рисованием каждой линии нужно вычислять ее расположение на окне рисования. Для всего этого создадим для класса CPaintDlg отдельный метод, который назjвем DrawLine().
В панели Class View выберите класс CPaintDlg
Вызовите мастер Add Function и настройте его так

Мастер добавит код
Объявление функции в файле PaintDlg.h
class CPaintDlg : public CDialog
{
............................................
public:
// Таблица цветов
static const COLORREF m_crColors[8];
private:
// Рисование линий на холсте
void DrawLine(CPaintDC * pDC, int iColor);
};
Заготовка определения функции в файле PaintDlg.cpp
// Рисование линий на холсте
void CPaintDlg::DrawLine(CPaintDC * pDC, int iColor)
{
}
Заполните код функции DrawLine() так
Функция DrawLine() в файле PaintDlg.cpp
// Рисование линий на холсте
void CPaintDlg::DrawLine(CPaintDC * pDC, int iColor)
{
// Объявить и создать перья
CPen pnSolidPen(PS_SOLID, 1, m_crColors[iColor]);
CPen pnDashPen(PS_DASH, 1, m_crColors[iColor]);
CPen pnDotPen(PS_DOT, 1, m_crColors[iColor]);
CPen pnDashDotPen(PS_DASHDOT, 1, m_crColors[iColor]);
CPen pnDashDotDotPen(PS_DASHDOTDOT, 1, m_crColors[iColor]);
CPen pnNullPen(PS_NULL, 1, m_crColors[iColor]);
CPen pnInsidePen(PS_INSIDEFRAME, 1, m_crColors[iColor]);
CPen pnCombinationPen(PS_DOT | PS_GEOMETRIC | PS_ENDCAP_ROUND
, 1, m_crColors[iColor]);
// Получить область рисования
CRect rRect;
GetClientRect(rRect);
rRect.NormalizeRect();
// Вычислить расстояние между линиями
CPoint ptStart;
CPoint ptEnd;
int iDisp = rRect.Height() / 8; // Высота междурядья
CPen* pOldPen = NULL; // Обезопасили указатель
// Определить начальные точки линий
ptStart.y = rRect.top + iDisp / 2;
ptStart.x = rRect.left + iDisp / 2;
ptEnd.y = ptStart.y;
ptEnd.x = rRect.right - iDisp / 2;
// Рисуем каждую линию со своим пером
for(int count = 0; count < 8; count++){
// Выбрать для текущей линии перо
switch(count){
case 0: // Запоминаем старое перо контекста перед
// первой линией, чтобы затем вновь вернуть
// его контексту после прорисовки всех линий
pOldPen = pDC->SelectObject(pnSolidPen);
break;
case 1:
pDC->SelectObject(pnDashPen);
break;
case 2:
pDC->SelectObject(pnDotPen);
break;
case 3:
pDC->SelectObject(pnDashDotPen);
break;
case 4:
pDC->SelectObject(pnDashDotDotPen);
break;
case 5:
pDC->SelectObject(pnNullPen);
break;
case 6:
pDC->SelectObject(pnInsidePen);
break;
case 7:
pDC->SelectObject(pnCombinationPen);
} // end of switch
// Нарисовать линию
pDC->MoveTo(ptStart);
pDC->LineTo(ptEnd);
// Переместиться на позицию слудующей линии
ptStart.y += iDisp;
ptEnd.y = ptStart.y;
} // end of for
// Вернуть контексту его прежнее перо
pDC->SelectObject(pOldPen);
}
Вызов функции DrawLine() нужно размещать в месте, где линии будут рисоваться при каждом разрушении окна. Такое место одно - в функции OnPaint() второго окна. Но ее пока нет, поэтому ее нужно создать как обработчик сообщения WM_PAINT операционной системы.
Добавим обработчик события WM_PAINT в класс второго диалогового окна. Для этого:
Выделите в панели
Скорректируйте сгенерированный оболочкой обработчик так
Обработчик сообщения WM_PAINT класса CPaintDlg
void CPaintDlg::OnPaint()
{
CPaintDC dc(this); // Локальный контекст устройства
// Получить указатель на родительское окно
CGraphicsDlg* pWnd = (CGraphicsDlg*) GetParent();
if(!pWnd) return; // Если родитель не существует - выйти
if(pWnd->m_iTool == 0 // Если выбран режим Pen
pWnd->m_iShape == 0) // и если выбран режим Line
DrawLine(dc, pWnd->m_iColor); // Рисуем линии текущим цветом
}
Отметьте для себя, что здесь создается указатель на на класс главного окна и ему присваевается содержимое, возвращенное API -функцией GetParent(). Используется также операция приведение указателя.
Для видимости объявления класса CGraphicsDlg в файле PaintDlg.cpp включите в начало этого файла инструкцию препроцессора #include "GraphicsDlg.h"
Включение инструкции препроцессора в файл PaintDlg.cpp
// PaintDlg.cpp : implementation file
//
#include "stdafx.h"
#include "Graphics.h"
#include "PaintDlg.h"
#include ".\paintdlg.h"
#include "GraphicsDlg.h"
// CPaintDlg dialog
IMPLEMENT_DYNAMIC(CPaintDlg, CDialog)
CPaintDlg::CPaintDlg(CWnd* pParent /*=NULL*/)
: CDialog(CPaintDlg::IDD, pParent)
{
}
CPaintDlg::~CPaintDlg()
{
}
.....................................................
Закомментируйте отладочный код в обработчике OnRadioSelection() файла GraphicsDlg.cpp
Постройте приложение и убедитесь, что все пока работает как нужно
Должно получиться что-то подобное

Отметьте для себя, что при начальном запуске программы стили линий не соблюдаются. Попробуйте устранить это, рисуя вначале линии в контексте памяти, а затем, перекидывая этот контекст в контекст экрана с помощью функции . Примерные возможности рассмотрим ниже при работе с растрами.
Добавим в приложение код для рисования кругов и квадратов с помощью -функций Ellipse() и Rectangle(). Обеим функциям передается объект типа CRect, задающий прямоугольник, в который будет вписана рисуемая фигура. Функция Rectangle() заполняет все место в прямоугольнике, а функция Ellipse() рисует эллипс или круг, причем середина каждой стороны прямоугольника касается края эллипса.
Будем использовать в этих функциях текущие настройки пера и кисти, которые выберет пользователь. Для этого нужно их создать. В случае с пером можно использовать нулевое перо (стиль пера PS_NULL ), но когда речь идет о кисти, то нужно будет создать сплошную кисть, имеющую цвет фона окна (светло-серый).
Прямоугольники и эллипсы расположим на холсте в две строки по четыре фигуры в каждой строке. Для рисования фигур создадим отдельный метод, как и в случае с рисованием линий, и назовем его DrawRegion().
В панели Class View выберите класс CPaintDlg
Вызовите мастер Add Function и настройте его так

Мастер добавит код
Объявление функции DrawRegion() в файле PaintDlg.h
class CPaintDlg : public CDialog
{
.............................................
public:
// Таблица цветов
static const COLORREF m_crColors[8];
private:
// Рисование линий на холсте
void DrawLine(CPaintDC * pDC, int iColor);
public:
afx_msg void OnPaint();
private:
// Функция рисования примитивных фигур GDI - прямоугольников и эллипсов
void DrawRegion(CPaintDC * pDC, int iTool, int iShape, int iColor);
};
Заготовка определения функции DrawRegion() в файле PaintDlg.cpp
// Функция рисования примитивных фигур GDI - прямоугольников и эллипсов
void CPaintDlg::DrawRegion(CPaintDC * pDC, int iTool, int iShape, int iColor)
{
}
Заполните код функции DrawRegion() так
Функция DrawRegion() в файле PaintDlg.cpp
// Функция рисования примитивных фигур GDI - прямоугольников и эллипсов
void CPaintDlg::DrawRegion(CPaintDC * pDC, int iTool, int iShape, int iColor)
{
// Объявить и создать перья
CPen pnSolidPen(PS_SOLID, 1, m_crColors[iColor]);
CPen pnDashPen(PS_DASH, 1, m_crColors[iColor]);
CPen pnDotPen(PS_DOT, 1, m_crColors[iColor]);
CPen pnDashDotPen(PS_DASHDOT, 1, m_crColors[iColor]);
CPen pnDashDotDotPen(PS_DASHDOTDOT, 1, m_crColors[iColor]);
CPen pnNullPen(PS_NULL, 1, m_crColors[iColor]);
CPen pnInsidePen(PS_INSIDEFRAME, 1, m_crColors[iColor]);
CPen pnCombinationPen(PS_DOT|PS_GEOMETRIC|PS_ENDCAP_ROUND
, 1, m_crColors[iColor]);
// Объявить и создать кисти
CBrush brSolidBrush(m_crColors[iColor]);
CBrush brBDiagBrush(HS_BDIAGONAL, m_crColors[iColor]);
CBrush brCrossBrush(HS_CROSS, m_crColors[iColor]);
CBrush brDiagCrossBrush(HS_DIAGCROSS, m_crColors[iColor]);
CBrush brFDiagBrush(HS_FDIAGONAL, m_crColors[iColor]);
CBrush brHorizBrush(HS_HORIZONTAL, m_crColors[iColor]);
CBrush brVertBrush(HS_VERTICAL, m_crColors[iColor]);
CBrush brNullBrush(RGB(208, 208, 208)); // Светло-серый системный цвет
// Получить область рисования
// Все размеры GDI в пикселах
CRect rClientRect;
GetClientRect(rClientRect);
rClientRect.NormalizeRect();
// Параметры рисования
const int iDisp = 8;// Высота и ширина междурядья и краев клиентской области
int iHeight = (rClientRect.Height() - 3 * iDisp) / 2; // Высота фигуры для двух рядов
int iWidth = (rClientRect.Width() - 5 * iDisp) / 4; // Ширина фигуры для четырех в ряд
CRect rCurrentRect; // Текущие координаты рисования
CPen* pOldPen = NULL; // Создали указатель пера и сразу обезопасили
CBrush* pOldBrush = NULL; // Создали указатель кисти и сразу обезопасили
// Перебираем в цикле все перья и кисти
// и сразу рисуем
for(int count = 0; count < 8; count ++){
// Вычисляем текущие координаты привязки фигуры
if(count == 0){ // Начать первую строку
rCurrentRect.left = rClientRect.left + iDisp;// Координата X
rCurrentRect.right = rCurrentRect.left + iWidth;// Координата X
rCurrentRect.top = rClientRect.top + iDisp;// Координата Y
rCurrentRect.bottom = rCurrentRect.top + iHeight;// Координата Y
}
else if(count == 4){ // Начать вторую строку
rCurrentRect.left = rClientRect.left + iDisp;// Координата X
rCurrentRect.right = rCurrentRect.left + iWidth;// Координата X
rCurrentRect.top = rClientRect.top + 2 * iDisp + iHeight;// Координата Y
rCurrentRect.bottom = rCurrentRect.top + iHeight;// Координата Y
}
else{// Продолжить текущую строку
rCurrentRect.left = rCurrentRect.right + iDisp;// Координата X
rCurrentRect.right = rCurrentRect.left + iWidth;// Координата X
}
// Выбираем очередное перо и кисть
switch(count){
case 0: // Сплошная линия и сплошная заливка
// Сразу запомним старое перо и кисть
pOldPen = pDC->SelectObject(pnSolidPen);
pOldBrush = pDC->SelectObject(brSolidBrush);
break;
case 1: // Заливка штриховкой обратными косыми
pDC->SelectObject(pnDashPen);
pDC->SelectObject(brBDiagBrush);
break;
case 2: // Заливка вертикальной и горизонтальной штриховкой
pDC->SelectObject(pnDotPen);
pDC->SelectObject(brCrossBrush);
break;
case 3: // Заливка штриховкой по обеим диагоналям
pDC->SelectObject(pnDashDotPen);
pDC->SelectObject(brDiagCrossBrush);
break;
case 4: // Заливка диагональю из черточек и двух точек
pDC->SelectObject(pnDashDotDotPen);
pDC->SelectObject(brFDiagBrush);
break;
case 5: // Нулевая обводка, горизонтальная штриховка,
pDC->SelectObject(pnNullPen);
pDC->SelectObject(brHorizBrush);
break;
case 6: // Заливка вертикальной штриховкой
pDC->SelectObject(pnInsidePen);
pDC->SelectObject(brVertBrush);
break;
case 7: // Нулевая заливка
pDC->SelectObject(pnCombinationPen);
pDC->SelectObject(brNullBrush);
} // end of switch
// Анализируем переключатели группы Tool
// Чем рисовать: пером или кистью (что включено)?
// Мы заготовили и то и другое!!!
switch(iTool){
case 0: // Включен режим Tool = Pen - рисовать только пером
// Скрываем кисть
pDC->SelectObject(brNullBrush);
break;
case 1: // Включен режим Tool = Brush - отображать только заливку
// Скрываем перо
pDC->SelectObject(pnNullPen);
break;
default:
// Скрываем и то и другое на всякий случай
pDC->SelectObject(pnNullPen);
pDC->SelectObject(brNullBrush);
}
// Анализируем переключатели группы Shape
// Что рисовать: эллипс или прямоугольник (что включено)?
// Координаты расположения для каждой итерации у нас есть!!!
switch(iShape){
case 2: // Включен режим Shape = Square - рисовать прямоугольник
pDC->Rectangle(rCurrentRect);
break;
case 1: // Включен режим Shape = Circle - рисовать эллипс
pDC->Ellipse(rCurrentRect);
}
} // end of for
// Восстановить в контексте устройства первоначальные перо и кисть
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldBrush);
}
Вызов функции DrawRegion() нужно размещать в том же самом месте, где линии будут рисоваться при каждом разрушении окна и куда мы ранее поместили вызов функции DrawLine(). Это место - в функции OnPaint() второго окна, обслуживаемого классом CPaintDlg.
Дополните функцию OnPaint() соответствующим новым кодом
Изменения в функции OnPaint() файла PaintDlg.cpp
void CPaintDlg::OnPaint()
{
CPaintDC dc(this); // Локальный контекст устройства
// Получить указатель на родительское окно
CGraphicsDlg* pWnd = (CGraphicsDlg*) GetParent();
if(!pWnd) return; // Если родитель не существует - выйти
if(pWnd->m_iTool == 0 // Если выбран режим Pen
pWnd->m_iShape == 0) // и если выбран режим Line
DrawLine(dc, pWnd->m_iColor); // Рисуем линии
if(pWnd->m_iTool != 2 // Если не выбран растр Bitmap
pWnd->m_iShape != 0) // Если не выбран режим Line
// Рисуем прямоугольник, эллипс или их контуры текущим цветом
DrawRegion(dc, pWnd->m_iTool, pWnd->m_iShape, pWnd->m_iColor);
}
Постройте приложение и убедитесь, что все работает как нужно
Должно получиться что-то подобное

Созданное нами окно-холст имеет фиксированные размеры, установленные нами при проектировании его графического ресурса и не может их менять в процессе работы. Дадим возможность пользователю изменять размеры холста. Для этого:
Откройте диалоговый ресурс с идентификатором IDD_PAINT_DLG второго диалогового окна в окне дизайнера ресурсов и измените два его свойства такBorder = Resizing (с изменяемым размером)Title Bar = True (с заголовком окна)
Постройте приложение и убедитесь, что теперь холст способен изменять свои размерыНо при изменении размеров рисунки в нем размазываются и перерисовываются только после того, как окно закроется другим окном и возникнет необходимость перерисовать его полностью. Исправим это:
Вновь выделите ресурс диалогового окна холста в окне дизайнера ресурсов и в таблице свойств включите режим Messages
Найдите сообщение WM_SIZE, которое выдает Windows каждый раз, когда меняется размер окна, и создайте функцию-обработчик OnSize()Поскольку обработчик создается для диалогового окна холста, то именно это окно и будет обрабатывать это сообщение Windows, когда пользователь изменит его размеры.
Заполните обработчик так
Изменения в функции OnSize() файла PaintDlg.cpp
void CPaintDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
Invalidate(); // Перерисовать второе окно
}
Постройте приложение и убедитесь, что при изменении размеров окна холста все работает как нужно
Здесь осталась еще одна проблема, о которой мы упоминали ранее. При первом запуске приложения в холсте не прорисовываются стили линий или фигур. Для исправления этого можно поместить в функцию OnPaint() принудительный вызов обработчика OnRadioSelection(), который будет имитировать нажатие мышью на радиокнопке. Но чтобы этот вызов сработал только один раз при первом появлении окна, посадим его на условие. Для этого введем логическую переменную-флаг как член класса CGraphicsDlg, который поднимем в функции OnInitDialog(), а после первого срабатывания вызова OnRadioSelection() сразу опустим и более поднимать не будем.
Введите в класс CGraphicsDlg объявление переменной-флага вручную
Добавление в классе CGraphicsDlg файла GraphicsDlg.h
class CGraphicsDlg : public CDialog
{
...............................................
private:
bool b_singleFlag;// для первой прорисовки холста
};
Введите инициализацию переменной-флага в функцию OnInitDialog() класса CGraphicsDlg
Добавление в функции OnInitDialog() файла GraphicsDlg.cpp
BOOL CGraphicsDlg::OnInitDialog()
{
......................................................
// TODO: Add extra initialization here
// Создать диалоговое окно холста с текущим родителем
m_dlgPaint.Create(IDD_PAINT_DLG, this);
// Показать холст поверх родителя
m_dlgPaint.ShowWindow(SW_SHOW);
b_singleFlag = true;
return TRUE; // return TRUE unless you set the focus to a control
}
Добавьте принудительный вызов обработчика в функцию OnPaint() класса CGraphicsDlg
Добавление в функцию OnPaint() файла GraphicsDlg.cpp
void CGraphicsDlg::OnPaint()
{
................................................
else
{
CDialog::OnPaint();
}
if(b_singleFlag) // Вызов один раз
OnRadioSelection(); // для имитации нажатия на радиокнопке
if(b_singleFlag) // Сбросить флаг
b_singleFlag = !b_singleFlag;
}
Постройте приложение и убедитесь, что при начальном появлении окна все прорисовывается правильно
Здесь осталась еще одна проблема, требующая решения. Поперемещайте холст за заголовок, после его начального появления, по экрану и частично за границы экрана. Вы увидете, что прорисовка опять разрушается и не восстанавливается или восстанавливается неверно. Исправим это (чтобы кот не гулял сам по себе, как Мельникова):
Вновь выделите диалоговый ресурс IDD_PAINT_DLG в окне дизайнера ресурсов и в таблице свойств включите режим Messages
Найдите сообщение WM_MOVE, которое выдает Windows каждый раз, когда окно перемещается по экрану за его заголовок пользователем, и создайте функцию-обработчик OnMove()Поскольку обработчик создается для диалогового окна холста, то именно это окно и будет обрабатывать это сообщение Windows, когда пользователь переместит холст.
Заполните обработчик так
Функция-обработчик OnMove() файла PaintDlg.cpp
void CPaintDlg::OnMove(int x, int y)
{
CDialog::OnMove(x, y);
// TODO: Add your message handler code here
Invalidate(); // Перерисовать второе окно
}
Постройте приложениеТеперь при любом перемещении окна функция Invalidate() заставляет срабатывать обработчик OnPaint() класса CPaintDlg. А там мы встроили вызовы функций рисования фигур DrawLine() и DrawRegion(), которые срабатывают по текущим настройкам элементов управления главного окна.
Вместо городьбы огорода с флагом b_singleFlag и принудительным вызовом функции-обработчика OnRadioSelection(), можно поступить иначе. Можно просто послать холсту после его создания сообщение WM_MOVE, предварительно создав обработчик сообщения WM_ACTIVATE и поместив туда код PostMessage(WM_MOVE) . Можете подумать и попробовать, лично мне лень...
Добавим в наше приложение возможности для загрузки и отображения точечных (растровых) рисунков. Сразу заметим, что растровые рисунки содержат данные в формате .bmp, именно такой формат графических данных размещается в контексте устройства экрана. Загружать и отображать растровые рисунки можно несколькими способами:
Picture Control.bmp, хранящихся на дискеРассмотрим все эти способы.
Нужно иметь ввиду, что когда мы создаем растровые изображения как ресурсы, включая их в проект в режиме проектирования, то все файлы с изображениями встраиваются в исполнимый файл приложения при компиляции проекта (и соответственно - увеличивают его размеры). Поэтому такой способ приемлем, скорее, лишь для включения небольших картинок типа иконок.
Откройте диалоговый ресурс приложения с идентификатором IDD_ABOUTBOX
Выделите на форме элемент управления Picture Control, содержащий пиктограмму с иконкой приложения, и удалите его
Удалите все остальные элементы управления формы IDD_ABOUTBOX, включая и кнопку OK
Измените размеры формы на, примерно, 320x220 (для рисунка с запасом)
Поместите на форму из панели Toolbox новый элемент управления Picture Control и расположите его в верхнем левом углу рамки направляющих линий формы
Через контекстное меню панели Resource View

импортируйте файл MyPhoto.bmp из каталога Pictures этой лабораторной работы ( перед демонстрацией приложения преподавателю эту картинку обязательно замените на другую )
Заметьте при этом, что в каталоге res проекта появился новый файл с тем же именем. Оболочка при импорте "как Плюшкин" тащит все к себе. Это и понятно, завтра импортируемый файл могут удалить, а вдруг послезавтра опять придется строить приложение.
Переименуйте идентификатор ресурса на ID=IDB_ABOUT
Выделите на форме элемент Picture Control и через панель свойств настройте его параметрыType=BitmapImage = IDB_ABOUTReal Size Image = True
Подгоните форму под размеры рисунка
Отметьте для себя, что окно импорта ресурса готово было импортировать рисунок не только в формате .bmp. Если хотите, проверьте, можно ли загрузить в элемент Picture Control рисунок другого формата
Постройте приложение, запустите его
На заголовке главного окна вызовите контекстное меню и выполните опцию About... Должно получиться примерно следующее

Далее выполните следующее...Здесь мы будем загружать растровые ресурсы, которые должны создать на этапе проектирования и которые при компиляции будут храниться в исполнимом файле. Фактически мы создадим код, который будет только отображать эти ресурсы для пользователя на холсте, а не подкачивать в работающее приложение откуда-то со стороны.
В этом смысле растровые ресурсы можно загружать программно двумя почти идентичными способами:
API-Windows напрямуюMFC -класс CBitmapДалее мы увидим, что можно отображать рисунки не только как заранее (на этапе проектирования) загруженные ресурсы. Но можно отображать рисунки (и не только растровые формата .bmp) из файлов на диске через стандартное диалоговое окно File Open. Для программной загрузки рисунков из файлов можно использовать два класса:
CBitmap - только для загрузки файлов формата .bmp ( MFC -библиотека)CImage - для загрузки файлов других популярных форматов ( ATL -библиотека)Для всех перечисленных способов желательно проверить два варианта отображения рисунка:
BitBlt )StretchBlt )Воспользуемся только вторым способом и примем следующий порядок использования инструментов главной панели:
Кнопка Bitmap должна срабатывать только при включенном режиме Tool=Bitmap.Проверку этого условия мы разместим и в обработчике кнопки, и в обработчике OnPaint() перед вызовом функции вывода рисунка
Когда режим Tool=Bitmap включен, у нас остаются не у дел инструменты Shape и Color. Задействуем их так...
Режим Shape=Line приспособим для вывода ресурсов растровых рисунков формата .bmp с помощью API -функций Windows. При этом радиокнопками Color будем выбирать различные растровые рисунки, которые загрузим в ресурсы приложения на этапе проектирования
Режим Shape=Circle установим для загрузки растровых рисунков формата .bmp с помощью функций MFC -класса CBitmap через стандартное диалоговое окно File Open
Режим Shape=Square установим для загрузки растровых рисунков формата .bmp с помощью функций ATL -класса CImage через стандартное диалоговое окно File Open. При этом сделаем возможным загрузку и отображение не только .bmp -файлов, но и файлов других популярных форматов (например, JPEG и PNG )
При загрузке файлов через диалоговое окно кнопки Color использоваться не будут
Загрузите ресурсы Bitmap из каталога Pictures\Photo лабораторной работы. При этом импортируйте файлыPhoto1.bmp Photo2.bmp Photo3.bmp Photo4.bmp Photo5.bmp Photo6.bmp Photo7.bmp Photo8.bmp Имена дескрипторов оставьте как есть

Установите размеры окна холста 281x199Создадим в классе CGraphicsDlg общедоступную переменную strFileName для хранения имени выбранного файла. Для этого:
В панели Class View выделите класс CGraphicsDlg и через контекстное меню вызовите мастер, который заполните так

Объявление переменной strFileName файла GraphicsDlg.h
class CGraphicsDlg : public CDialog
{
..................................................
public:
// Переменная для хранения имени файла
CString strFileName;
};
Инициализация переменной strFileName файла GraphicsDlg.cpp
CGraphicsDlg::CGraphicsDlg(CWnd* pParent /*=NULL*/)
: CDialog(CGraphicsDlg::IDD, pParent)
, m_iTool(0)
, m_iShape(0)
, m_iColor(7)
, strFileName(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
Поместите в файл PaintDlg.cpp инструкцию #include <atlimage.h> с объявлением класса CImage -библиотеки ( именно в конец группы инструкций include, как показано ниже, иначе компилятор заблудится и будет выдавать ошибку!)
Начало файла PaintDlg.cpp
// PaintDlg.cpp : implementation file
//
#include "stdafx.h"
#include "Graphics.h"
#include "PaintDlg.h"
#include ".\paintdlg.h"
#include "GraphicsDlg.h"
#include <atlimage.h>
// CPaintDlg dialog
IMPLEMENT_DYNAMIC(CPaintDlg, CDialog)
CPaintDlg::CPaintDlg(CWnd* pParent /*=NULL*/)
: CDialog(CPaintDlg::IDD, pParent)
{
}
........................................
Откройте ресурс IDD_GRAPHICS_DIALOG главного диалогового окна в режиме редактирования
Двойным щелчком по кнопке Bitmap создайте обработчик, который заполните так
Функция-обработчик кнопки Bitmap файла GraphicsDlg.cpp
void CGraphicsDlg::OnBnClickedBbitmap()
{
if(m_iTool != 2 || m_iShape == 0) return;
// Готовим фильтры
static char szFilterBmp[] = "Bitmap Files (*.bmp)|*.bmp||";
static char szFilterDefault[] =
"JPEG Files (*.jpg)|*.jpg|"
"PNG Files (*.png)|*.png|"
"Bitmap Files (*.bmp)|*.bmp||";
char *filter;// Вспомогательный указатель
// Определить фильтр
if(m_iShape == 1) // Circle для использования класса CBitmap
filter = szFilterBmp;
else // Оставшийся режим Shape==Square
filter = szFilterDefault;
// Открываем диалог для выбора файла
// Создать стандартное диалоговое окно File Open
CFileDialog dlgOpenFileDialog(TRUE, "", strFileName,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, filter);
// Отобразить диалоговое окно
if(dlgOpenFileDialog.DoModal() == IDOK){
// Заменить имя файла на выбранное для последующего отображения
strFileName = dlgOpenFileDialog.GetPathName();
m_dlgPaint.Invalidate(); // Перерисовать второе окно
}
}
Создайте функцию DrawImage(), прототип которой разместите в описании класса CPaintDlg файла PaintDlg.cpp
Прототип функции DrawImage() файла PaintDlg.h
class CPaintDlg : public CDialog
{
.............................................
public:
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnMove(int x, int y);
void DrawImage(CPaintDC * pDC, int iTool, int iShape, int iColor);
};
Определение функции разместите в фале PaintDlg.cpp и наполните кодом так
Определение функции DrawImage() в файле PaintDlg.cpp
void CPaintDlg::DrawImage(CPaintDC* dc, int iTool, int iShape, int iColor)
{
// Получить указатель на родительское окно
// для доступа к имени файла, полученного из File Open
CGraphicsDlg* pWnd = (CGraphicsDlg*) GetParent();
if(!pWnd) return; // Если родитель не существует - выйти
// Получить область рисования
CRect rClientRect;
GetClientRect(rClientRect);
rClientRect.NormalizeRect();
// Параметры рисования
const int iDisp = 7;// Отступы от краев клиентской области
int iWidth = rClientRect.Width() - 2 * iDisp; // Ширина области рисования
int iHeight = rClientRect.Height() - 2 * iDisp; // Высота области рисования
// Создадим ананимные перечисления локально,
// для удобства работы, чтобы не запутаться
enum{Pen, Brush, Bitmap};
enum{Line, Circle, Square};
enum{Black, Blue, Green, Cyan
,Red, Magenta, Yellow
,White}; // Режим для CImage отдельно, чтобы не забыть
/**************************************************************
Shape==Line - выводим ресурсы растровых
рисунков формата *.bmp
с помощью API-функций Windows.
Радиокнопки Color используем для выбора различных
растровых рисунков, которые загрузим
в ресурсы приложения на этапе проектирования.
**************************************************************/
if(iShape == Line){
HBITMAP hbitmap; // Создать объект для хранения изображения
int id;
switch(iColor){
case Black: id = IDB_BITMAP1; break;
case Blue: id = IDB_BITMAP2; break;
case Green: id = IDB_BITMAP3; break;
case Cyan: id = IDB_BITMAP4; break;
case Red: id = IDB_BITMAP5; break;
case Magenta: id = IDB_BITMAP6; break;
case Yellow: id = IDB_BITMAP7; break;
case White: id = IDB_BITMAP8;
}
// Поместить растровое изображение в память
hbitmap = ::LoadBitmap(::AfxGetInstanceHandle()
,MAKEINTRESOURCE(id));
// Создать контекст устройства в памяти
HDC hMemDC = ::CreateCompatibleDC(NULL);
// Поместить растровое изображение в dc памяти
SelectObject(hMemDC, hbitmap);
// Копировать содержимое dc памяти в dc экрана
::StretchBlt(dc->m_hDC, iDisp, iDisp, iWidth, iHeight,// Приемник: dc экрана
hMemDC, 0, 0, 400, 300, // Источник: dc памяти
SRCCOPY);
// Уничтожить dc памяти и растровое изображение
::DeleteDC(hMemDC);
::DeleteObject(hbitmap);
} // Конец if
/**************************************************************
Shape==Circle - отображаем файл формата *.bmp,
имя которого получено из File Open,
с помощью функций MFC-класса CBitmap.
Состояние радиокнопок Color значения не имеет.
**************************************************************/
if(iShape == Circle){
// Создать объект для хранения и загрузить в него изображение
HBITMAP hbitmap =
(HBITMAP) ::LoadImage(AfxGetInstanceHandle(),
pWnd->strFileName, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE | LR_CREATEDIBSECTION);
if(hbitmap){// Файл успешно загружен
// Создаем экземпляр класса CBitmap
CBitmap m_Bitmap;
// Прикрепляем загруженный рисунок к объекту класса
m_Bitmap.Attach(hbitmap);
// Создать родную структуру для объекта класса CBitmap
BITMAP bm;
// Перекачать загруженное в структуру HBITMAP изображение
// в родную для объекта m_Bitmap структуру BITMAP
m_Bitmap.GetBitmap(bm);
// Создать совместимый контекст устройства в памяти
// по переданному в функцию параметру CPaintDC* dc
CDC dcMem;
dcMem.CreateCompatibleDC(dc);
// Поместить растровое изображение в dc памяти
dcMem.SelectObject(m_Bitmap); // Может адрес?
// Копировать и изменить размеры точечного
// рисунка по диалоговому окну холста
dc->StretchBlt(iDisp, iDisp, iWidth, iHeight,// Приемник: dc экрана
dcMem, 0, 0, bm.bmWidth, bm.bmHeight,// Источник: dc памяти
SRCCOPY);
}
}
/**************************************************************
Shape==Square- выводим стандартное диалоговое окно
File Open для загрузки и
отображения .bmp-файлов или файлов других
популярных форматов (например, JPEG и PNG).
Состояние радиокнопок Color значения не имеет.
**************************************************************/
if(iShape == Square){
// Создать экземпляр класса CImage
CImage m_Image;
// Загрузить в него рисунок из файла
if(pWnd->strFileName.GetLength() > 0){ // Если выбрано имя файла
m_Image.Load(pWnd->strFileName);
// Задать координаты рисования
CRect rRect(iDisp, iDisp, iDisp + iWidth, iDisp + iHeight);
// Отобразить на контексте экрана
m_Image.Draw(dc->m_hDC, rRect);
}
}
}
Разместите вызов функции DrawImage() в обработчике OnPaint() файла PaintDlg.cpp, дополнив его так
Вызов функции DrawImage() в файле PaintDlg.cpp
void CPaintDlg::OnPaint()
{
CPaintDC dc(this); // Локальный контекст устройства
// Получить указатель на родительское окно
CGraphicsDlg* pWnd = (CGraphicsDlg*) GetParent();
if(!pWnd) return; // Если родитель не существует - выйти
if(pWnd->m_iTool == 0 // Если выбран режим Pen
pWnd->m_iShape == 0) // и если выбран режим Line
DrawLine(dc, pWnd->m_iColor); // Рисуем линии
if(pWnd->m_iTool != 2 // Если не выбран растр Bitmap
pWnd->m_iShape != 0) // Если не выбран режим Line
// Рисуем прямоугольник, эллипс или их контуры текущим цветом
DrawRegion(dc, pWnd->m_iTool, pWnd->m_iShape, pWnd->m_iColor);
if(pWnd->m_iTool == 2) // Если выбран рисунок
// Выдаем рисунки
DrawImage(dc, pWnd->m_iTool, pWnd->m_iShape, pWnd->m_iColor);
}
Запустите приложение и проверьте работу на файлах разных форматов
Обратите внимание на размер исполнимого файла - он недопустимо большой. Это потому, что растровые рисунки мы включили в ресурсы.

Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.