
- •Овсянник в.Н. Лабораторные работы по курсу «Объектно-ориентированное программирование»
- •13. Разработка приложения с архитектурой «документ-вид». Часть 2 120
- •14. Разработка приложения с архитектурой «документ-вид». Часть 3 135
- •1.Разработка типового консольного приложения
- •1.1.Методические указания
- •1. Запуск и настройка ис mvs-2008.
- •2.Поиск данных в массиве
- •3.Обработка текстового файла
- •4.Задача «куча камней»
- •5.Реализация алгоритма полного перебора
- •6. Исследование погрешностей вычислений
- •7.Поиск экстремумов с ограничениями или «Брачное агенство»
- •Постановка задачи
- •Задание
- •Листинг файла mAgency.Cpp (с главной функцией)
- •Листинг файла Lib.H
- •Листинг файла Lib.Cpp
- •8.Приложение с окном вида
- •9.Разработка графического редактора
- •Задание.
- •9.1.Проектирование программы
- •9.2.Разработка интерфейса
- •Установка флагов
- •Создание меню и панели инструментов
- •Обработка сообщений о нажатии кнопки мыши
- •9.3.Рисование
- •9.4.Растягивание фигур
- •9.5.Обновление изображения
- •9.6.Сохранение метафайла на диске и его загрузка
- •10.Приложение, основанное на модальном диалоговом окне
- •Методические указания Модальные и немодальные диалоговые окна
- •Ресурсы и элементы управления
- •Сценарий создания приложения
- •Разбор приложения
- •Усовершенствование приложения
- •11.Приложение с контекстным меню и строкой состояния
- •11.1.Методические указания к первой части работы
- •11.2.Сценарий выполнения первой части работы
- •11.3.Методические указания ко второй части работы Строка состояния
- •Определение секций в строке состояния
- •Строка сообщений
- •Индикатор состояния
- •Управление строкой состояния
- •11.4.Сценарий выполнения второй части работы
- •12.Разработка приложения с архитектурой «документ-вид». Часть 1
- •12.1.Сценарий создания приложения
- •13.Разработка приложения с архитектурой «документ-вид». Часть 2
- •13.1.Методические указания Класс коллекций cObList
- •13.2.Сценарий выполнения работы
- •14.Разработка приложения с архитектурой «документ-вид». Часть 3
- •14.1.Методические указания
- •15.Класс вектор
- •15.1.Пример класса tVector
- •15.2.Класс tVector с перегруженными операциями
- •16.Приложение с таблицей
- •16.1.Вариант 1 Сценарий выполнения работы
- •16.2.Вариант 2 Сценарий выполнения работы
9.4.Растягивание фигур
В «настоящих» графических редакторах пользователь может растягивать мышью создаваемую фигуру, что позволяет следить за ее контуром во время создания. Добавим и мы такую возможность в нашу программу. Начнем с растягивания прямых линий.
Шаг 16. Растягивание линий.
Очевидно, что код растягивания надо включать в обработчик перемещения мыши OnMouseMove(). Чтобы нарисовать линию новой длины и/или в ее новом положении надо выполнить два действия: стереть старое изображение линии от начальной точки к предыдущей и нарисовать новую линию от начальной точки к текущему положению указателя мыши.
Чтобы иметь возможность стереть старую линию, надо запоминать ее положение, для чего мы заблаговременно добавили в программу член класса OldPoint. При нажатии ЛКМ необходимо занести в OldPoint текущие координаты мыши. Для этого необходимо добавить в обработчик OnLButtonDown() операторы:
OldPoint.x=point.x; OldPoint.y=point.y; // или OldPoint=point;
Чтобы стереть линию от точки Anchor до точки OldPoint, лучше всего выбрать в контексте устройства бинарный растровый режим R2_NOT и снова нарисовать ту же линию. Что это значит? Бинарный растровый режим управляет тем, как будут выглядеть результаты рисования на экране. Всего таких режимов 16 и R2_NOT – один из них. Включение этого режима приводит к выводу пикселя, цвет которого является инвертированным цветом соответствующего пикселя экрана. Перед установкой нового бинарного режима надо запомнить текущий режим с тем, чтобы потом его восстановить. С учетом сказанного код метода OnMouseMove() должен стать таким:
|
Тестируйте программу. Обратили внимание, что над черным объектом линия при растягивании выводится белым цветом?
Шаг 17. Растягивание прямоугольников и эллипсов.
Код для выполнения этих операций подобен коду растягивания линий:
|
Опять-таки, тестируйте программу. Все работает? А если попробовать свернуть окно с рисунком и вновь его развернуть, что случится с изображением. Оно потеряется навеки! Обидно, да?
9.5.Обновление изображения
Метафайлом называется хранящийся в памяти объект, поддерживающий собственный контекст устройства. Любые операции, выполняемые с контекстом устройства, можно продублировать в метафайле. Если в дальнейшем потребуется повторить действия, выполненные пользователем (например, при обновлении изображения), достаточно воспроизвести данный метафайл. В результате такого воспроизведения все графические операции повторяются и изображение восстанавливается.
Шаг 18. Описание и инициализация класса метафайл.
Указатель на контекст устройства для метафайла CMetaFileDC имеет смысл описать в классе документа СPainterDoc. Для этого в заголовочный файл PainterDoc.h добавьте описание указателя:
// PainterDoc.h : interface of the CPainterDoc class
// …
class CPainterDoc : public CDocument
{
// …
// Implementation
public:
virtual ~CPainterDoc();
CMetaFileDC *pMetaFileDC;
В конструкторе класса документа мы должны инициализировать указатель pMetaFileDC, создав объект класса CMetaFileDC:
// PainterDoc.cpp : implementation of the CPainterDoc class
// …
CPainterDoc::CPainterDoc()
{
pMetaFileDC= new CMetaFileDC;
pMetaFileDC->Create();
}
Теперь мы подготовили указатель на контекст метафайла к работе и им можно пользоваться.
Шаг 19. Дублирование графических операций в метафайле.
Все, что пользователь рисует в окне, должно дублироваться в метафайле. Это означает, что при каждом вызове метода для контекста устройства вида необходимо вызвать аналогичный метод для контекста метафайла. (Естественно, что вызовы методов по «растягиванию» фигур дублировать смысла нет.) Например, в случае рисования линий и прямоугольников это делается так:
|
В приведенном фрагменте метода OnLButtonUp() добавления выделены комментарием /*!*/. Вы должны дополнить метод OnLButtonUp() операторами, обеспечивающими запись в метафайл кода рисования эллипса и заливки фигур по аналогии с записью линий и прямоугольников. Кроме того, Вы должны добавить код записи в метафайл фигуры произвольной формы, для чего надо модифицировать метод OnMouseMove() по аналогии с тем, как изображения записываются в метафайл в методе OnLButtonUp().
Так как указатель pMetaFileDC объявлен в классе документа, то для его использования надо иметь ссылку на объект документ типа CPainterDoc. Такую ссылку получают с помощью специально предназначенного для этой цели метода CPainterView:: GetDocument():
CPainterDoc *pDoc = GetDocument();
Обратите внимание на тот факт, что объект документ создавать не нужно и нельзя, так же как и уничтожать его: эту работу выполняет каркас приложения. Можно и нужно только получить указатель на этот объект. Реализацию метода CPainterView::GetDocument() Вы можете посмотреть в файле PainterView.h. А где находится еще одна реализация метода CPainterView::GetDocument()?
Вопрос. Существует объект класса вид, равно как и объект документ. Где они описаны?
Соберите программу и проверьте ее работу. Что-нибудь изменилось? Попробуйте что-нибудь нарисовать, свернуть окно и восстановить. Рисунок восстановился или нет? Если восстановился, позовите инженера: пусть он проверит компьютер.
Шаг 20. Воспроизведение метафайла.
За обновление изображения отвечает метод CPainterView::OnDraw(), который мы пока не модифицировали и он практически ничего не делает. А Вы еще не забыли, как тогда изображение появляется в окне вида. Если забыли – вновь просмотрите шаг 10.
Для того чтобы изображение в окне вида восстанавливалось, надо просто в методе CPainterView::OnDraw() воспроизвести метафайл.
Замечание. Вспомним, что метафайлом называют независимый от устройств формат графического файла, позволяющий сочетать растровое и векторное описание изображения. Существует несколько форматов метафайлов, например, Computer Graphics Metafile (файлы с расширением .CGM), Windows Metafile (файлы с расширением .WMF). Windows Metafile хранит не само изображение, а команды GDI, используемые для его создания.
Подобно любому другому файлу, метафайл надо открывать (или создавать) и закрывать. Очевидно, что сначала в методе OnDraw() метафайл надо закрыть, а затем воспроизвести его. Воспроизведение метафайла выполняет метод PlayMetaFile(), единственным параметром которого является дескриптор (логический номер) метафайла, который, в частности, возвращает метод закрытия метафайла Close().
Итак, для воспроизведения метафайла метод OnDraw() надо привести к следующему виду:
|
Код функции OnDraw() нуждается в некоторых комментариях.
Во-первых, «стандартным» приемом поиска ошибки является отладка, однако этот способ не годится для трассировки метода OnDraw(), так как он будет вызываться постоянно для перерисовки изображения. В этом случае удобно воспользоваться отладочной печатью, применив для этой цели макрос TRACE. Параметры у этого макроса такие же, как и у функции printf(). Ценным качеством TRACE является тот факт, что он работает только в отладочной версии программы (и игнорируется в окончательной сборке) и выводит сообщения на вкладку Debug окна вывода Output.
Во-вторых, когда Вы используете в своей программе указатели, то хорошим стилем программирования считается проверка корректности указателя перед его использованием: указатель не должен быть равен 0. Обратите внимание, например, на макрос ASSERT_VALID(pDoc), который имеется в методе OnDraw(). Что он делает? Да проверяет корректность указателя pDoc (только в отладочной версии приложения). Если указатель некорректен, выводится окно с сообщением об ошибке (рис. 7), текст в котором начинается с “Debug Assertion Failed”.
|
Рис. 7. Окно с сообщением об ошибке, инициированное макросом ASSERT_VALID
Если Вы внимательно посмотрите на код скелета приложения, сгенерированный ИС, то обнаружите там немалое число макросов ASSERT_VALID, ASSERT, методов AssertValid(), проверок типа if (!CDocument::OnNewDocument()) и т.п.
Сравнительно длинный код метода OnDraw() объясняется следующим. Для того чтобы можно было воспроизвести метафайл (т.е. восстановить изображение в окне) с помощью метода PlayMetaFile(), этот файл надо предварительно закрыть. Далее, класс CMetaFileDC не имеет метода открытия метафайла для дописывания в него новых изображений (точнее, команд генерации этих изображений). Выход такой: надо создать новый метафайл и воспроизвести в нем старый, для которого у нас есть логический номер MetaFileHandle. После этого можно подменить старый метафайл новым и удалить старый метафайл.
Соберите и тестируйте приложение. Проверьте, что однажды созданное изображение сохраняется при любых трансформациях окна: сворачивании, разворачивании, изменении размеров и т.д.
Итак, на этом этапе мы получили способ, с помощью которого можно сохранять изображение в окне вида.