Глава 10. Работа с документами и окнами просмотра документов
Рассмотрим некоторые процессы модификации шаблонов приложений для работы с документами.
Редактирование документа
Для работы с документом в классе документа обычно определяют элементы (простые переменные, переменные структурного типа, объекты различных классов и др.), отвечающие за хранение данных документа, считываемых и записываемых в файл.
Если в процессе работы с документом выделяется дополнительная память для хранения информации, то в деструкторе
класса документа необходимо освободить всю дополнительную память, полученную в процессе работы.В тех методах класса окна просмотра документа , где содержимое документа (соответствующие элементы класса) изменяется, для получения доступа к документу следует объявить указатель
pDoc на текущий документ и получить его при помощи метода GetDocument класса окна просмотра . Изменить содержание документа, на который указывает pDoc. Затем следует установить флаг изменения документа, вызвав метод SetModifiedFlag для объекта, на который указывает pDoc. Далее для mdi-приложения необходимо вызвать метод UpdateAllViews класса документадля перерисовки всех обликов данного документа.В классе окна просмотра следует переопределить метод
OnUpdate для того, чтобы выводить только последние изменения. Иначе этот метод вызывает метод OnDraw для перерисовки всего изображения.Для восстановления всего изображения в окне просмотра после перекрытия окна необходимо изменить метод
OnDraw класса окна просмотра документа , добавив вывод всего изображения :void CMyView::OnDraw(CDC* pDC) { CDoc* pDoc = GetDocument(); // Вывод содержимого документа, на который указывает pDoc, в окно // просмотра с использованием контекста устройства, указываемого pDC …… }
Синхронизация окон просмотра документа
Как правило, многооконные приложения позволяют открыть для одного документа несколько окон просмотра. Чтобы открыть дополнительное окно для просмотра уже открытого документа, нужно выбрать из меню
Window строку New Window.Заголовки окон просмотра одного документа будет одинаковыми за исключением того, что каждое такое окно имеет дополнительный числовой идентификатор, означающий номер окна.
К сожалению, окна просмотра одного документа несогласованны. Если в документ внести изменения через одно окно, они не появятся во втором до тех пор, пока содержимое второго окна не будет перерисовано. Чтобы избежать рассогласования между окнами просмотра одного и того же документа, необходимо сразу после изменения документа в одном окне вызвать метод
UpdateAllViews , определенный в классе CDocument :void UpdateAllViews(CView* pSender, LPARAM lHint=0l, CObject* pHint=NULL);
Метод
UpdateAllViews вызывает метод CView::OnUpdate для всех окон просмотра данного документа, за исключением окна просмотра, указанного параметром pSender. Как правило, в качестве pSender используют указатель того окна просмотра, через которое был изменен документ. Его состояние изменять не надо, так как оно отображает последние изменения в документе.Если изменение документа вызвано другими причинами, не связанными с окнами просмотра, в качестве параметра
pSender можно указать значение NULL. В этом случае будут вызваны методы OnUpdate всех окон просмотра без исключения.Параметры
lHint и pHint могут содержать дополнительную информацию об изменении документа. Методы OnUpdate получают значения lHint и pHint и могут использовать их, чтобы сократить перерисовку документа.Нужно отметить, что содержимое других окон при таком способе использования метода
UpdateAllViews перерисовывается полностью, несмотря на то, что дорисовать нужно только одну фигуру. Перерисовка всех объектов может заметно замедлить работу приложения.Выход из этого положения существует. При вызове метода
UpdateAllViews можно указать, какой объект надо дорисовать. А потом нужно переопределить метод OnUpdate так, чтобы приложение дорисовывало в окнах просмотра только новую фигуру. Для передачи информации от метода UpdateAllViews методу OnUpdate используют параметры lHint и pHint.Рассмотрим более подробно, как работает метод
OnUpdate:virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
Первый параметр
pSender содержит указатель на объект класса окна просмотра, который вызвал изменение документа. Если обновляются все окна просмотра, этот параметр содержит значение NULL.Второй и третий параметры -
lHint и pHint - содержат дополнительную информацию, указанную во время вызова метода UpdateAllViews. Если дополнительная информация не определена, тогда эти параметры содержат соответственно значения 0 l и NULL.Метод
OnUpdate вызывается, когда после изменения документа вызывается метод CDocument::UpdateAllViews . Метод OnUpdate также вызывается методом OnInitialUpdate (если он не переопределен в приложении).Реализация
OnUpdate из класса CView определяет, что внутренняя область окна просмотра подлежит обновлению и передает данному окну сообщение WM_PAINT (для этого вызывается функция InvalidateRect). Это сообщение обрабатывается методом OnDraw .Параметр
lHint имеет тип LPARAM и может содержать любое 32-битное значение. Параметр pHint является указателем на объект типа CObject. Поэтому, если есть необходимость его использовать, нужно определить собственный класс, наследованный от базового класса CObject, создать объект этого класса и передать указатель на него методу UpdateAllViews .Указатель на объект класса, наследованного от
CObject, можно присвоить указателю на объект класса CObject, поэтому такая операция разрешена. Следовательно, через этот указатель можно передавать объекты различных типов, наследованных от CObject.При разработке метода
OnUpdate необходимо проверять тип объекта, передаваемого через параметр pHint. Для этого можно воспользоваться методом IsKindOf класса CObject. Метод IsKindOf позволяет узнать тип объекта уже на этапе выполнения приложения.Когда переопределяется метод
OnUpdate , необходимо иметь в виду, что этот метод вызывается не только методом UpdateAllViews . В некоторых случаях он может быть вызван, если надо перерисовать все изображение в окне просмотра. В этом случае параметр lHint содержит 0, а параметр pHint - NULL. Необходимо обрабатывать эту ситуацию отдельно, вызывая метод CView::OnUpdate и обновляя все окно целиком .Создание нового документа
Когда пользователь выбирает из меню
File строки New, вызывается виртуальный метод OnNewDocument , определенный в базовом классе CDocument. Если этот метод не переопределяется, то по умолчанию он вызывает метод DeleteContents и далее помечает документ как чистый (пустой).Можно переопределить метод
OnNewDocument в наследуемом классе документа, чтобы выполнить его инициализацию (инициализацию его переменных, выделение при необходимости памяти). При этом требуется из переопределенного метода OnNewDocument сначала вызвать метод OnNewDocument , определенный в базовом классе.BOOL CDoc::OnNewDocument() { // Вызов метода базового класса if (!CDocument::OnNewDocument()) return FALSE; // Подготовка документа – инициализацию его переменных, // выделение при необходимости памяти для хранения данных ……… return TRUE; }
Когда пользователь создает новый документ в приложении, построенном на основе однооконного интерфейса, то на самом деле используется старый документ. Новый объект класса, представляющего документ, не создается. Метод
OnNewDocument должен удалить содержимое документа и выполнить повторную инициализацию существующего объекта класса документа.Из этого следует, что нельзя выполнять инициализацию документа в конструкторе класса документа, так как конструктор будет вызван только один раз за время работы приложения. Более правильно использовать для цели метод OnNewDocument .
Если в наследуемом классе документа переопределить метод
DeleteContents , то метод OnNewDocument базового класса CDocument, будет вызывать метод DeleteContents, определенный в наследуемом классе. Переопределенный метод DeleteContents обычно имеет следущий вид:void CDoc::DeleteContents() { // Очистка документа – освобождение памяти, // выделенной для хранения данных …… // Вызов метода базового класса CDocument::DeleteContents(); }
Теперь в приложении при выборе из меню
File строки New содержимое окна просмотра и документа обновляется. Приложение готово, например, выводить на экран новое изображение, создаваемое пользователем.Сохранение и восстановление документа на диске
Чтобы приложение имело возможность сохранения документов в файле, нужно изменить метод
Serialize класса документа. Метод Serialize вызывается всякий раз, когда надо сохранить документ в файле на диске или загрузить его из существующего файла. В частности, этот метод вызывается, когда пользователь выбирает из меню File строки Save, Save As и Open.Средство
MFC AppWizard подготавливает шаблон метода Serialize для класса, представляющего документ приложения :void CDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: add storing code here } else { // TODO: add loading code here } }
В методе
Serialize необходимо определить, как он должен сохранять и восстанавливать документы приложения.Метод
Serialize имеет единственный параметр ar, представляющий ссылку на объект класса CArchive. Этот объект, называемый архивом, представляет файл документа, расположенный на диске. Кроме того, архив несет в себе информацию о том, что делать с документом - записать его в файл или выгрузить из файла.Для того, чтобы определить, какую операцию надо выполнить, используется метод
IsStoring , определенный в классе CArchive . Если этот метод возвращает ненулевое значение для объекта ar, переданного методу Serialize , значит, надо сохранить документ в файле.Перед открытием приложением другого файла-документа (или перед созданием нового) при наличии внесенных изменений в текущий документ принято предупреждать об этом пользователя. Класс
CDocument и все классы, для которых он является базовым, позволяют установить специальный флаг модификации, означающий, что документ был изменен. В этом случае перед закрытием документа пользователю будет предложено его сохранить. Для установки этого флага предназначен метод SetModifiedFlag .Если документ изменен, необходимо установить флаг модификации, вызвав метод
SetModifiedFlag с параметром bModified, равным TRUE, или без параметра. В случае необходимости можно убрать установленный флаг. Для этого надо вызвать метод SetModifiedFlag с параметром bModified, равным FALSE. Устанавливать флаг модификации нужно в методах, выполняющих модификацию документа.Создание нового класса документа
Некоторые многооконные приложения позволяют одновременно работать с документами различных типов.
Для создания нового класса документа, например
COtherDoc, на основе базового класса CDocument используется ClassWizard. Это средство создает файл otherdoc.h с описанием класса COtherDoc и файл otherdoc.cpp, содержащий реализацию нового класса документа.Для создания класса окна просмотра (
COtherView) также используется средство ClassWizard, при помощи которого можно создать класс COtherView на базе CView или производных от него, определенных в библиотеке MFC.Создание шаблона документа
Шаблоны документов, с которыми работает приложение, определяют все характеристики данного типа документа. Они включают информацию о классе документа, классе окна просмотра, классе окна рамки, а также о ресурсах, связанных с данным типом документа.
Шаблоны документов создаются объектом приложения во время его инициализации. Если, например, посмотреть на метод
InitInstance главного класса приложения multi, то видно, что в нем создается только один объект класса CMultiDocTemplate, который представляет графический документ и средства работы с ним. Если необходимо, чтобы многооконное приложение работало и с другим типами документов, необходимо в методе InitInstance создать объект шаблона документов этих типов и добавить его в список шаблонов. Например :BOOL CMultiApp::InitInstance() { ....... CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_MULTITYPE,RUNTIME_CLASS(CMultiDoc), RUNTIME_CLASS(CChildFrame),RUNTIME_CLASS(CMultiView)); AddDocTemplate(pDocTemplate); pDocTemplate = new CMultiDocTemplate( IDR_OTHERTYPE,RUNTIME_CLASS(COtherDoc), RUNTIME_CLASS(CChildFrame),RUNTIME_CLASS(COtherView)); AddDocTemplate(pDocTemplate); .... return TRUE; }
При создании шаблона документов указывается идентификатор, который определяет меню, пиктограмму и некоторую другую полезную информацию, связанную с документами данного типа. В методе
InitInstance класса в качестве шаблона текстового документа указан идентификатор IDR_OTHERTYPE (такой идентификатор еще не определен, о ресурсах текстового документа речь пойдет далее).Чтобы созданный шаблон текстовых документов добавить к списку шаблонов документов приложения, надо вызвать метод
AddDocTemplate, указав ему адрес объекта шаблона.Ресурсы документа
Необходимо создать меню, пиктограмму и строковый ресурс с идентификаторами
IDR_OTHERTYPE. Самый простой путь - скопировать и изменить ресурсы с идентификатором IDR_MULTITYPE.На первом этапе разработки приложения скопированные меню и пиктограмму можно оставить без изменения. В дальнейшем их можно изменить по своему усмотрению.
Строковый ресурс
IDR_OTHERTYPE, описывающий документ, желательно изменить сразу. Для графического документа строковый ресурс IDR_MULTITYPE выглядит следующим образом :IDR_MULTITYPE "nMultinMultinnnMulti.DocumentnMulti Document"
Чтобы текстовый документ имел другое название типа документа, расширения файлов, принятое по умолчанию, нужно изменить строковый ресурс с идентификатором
IDR_OTHERTYPE (например, для текствых файлов ):IDR_OTHERTYPE "nTextnTextnText Files (*.txt)n.txtnText.DocumentnText Document"
Итак, теперь приложение готово к построению и запуску исполняемого файла. При запуске приложения на экране появится диалоговая панель
New, в которой перечислены типы документов, с которыми работает приложение. Такая же диалоговая панель будет выводиться и при создании нового документа при помощи строки New меню File.Одновременно можно открыть несколько документов различного типа, причем каждый документ может иметь несколько просмотра. Документы каждого типа имеют различные названия, используемые по умолчанию.