Глава 17.
Основы архитектуры документ/представление
Нешто я да не пойму
При моем-то при уму?..
Леонид Филатов
Каждое приложение, написанное для Windows, имеет одно главное окно, которое состоит из двух компонентов — фрейма и клиентской (рабочей) области. Оно может быть как стандартным окном Windows (например, при построении SDI- и MDI-приложений), так и обычным блоком диалога. В любом случае внутри главного окна приложение предоставляет пользователю всю необходимую для работы информацию, представляющую собой не только данные, т. е. текст, рисунки и т. п., но и элементы интерфейса — меню, полосы прокрутки, панели инструментов, строка состояния и т. д. Каждый из этих элементов (по крайней мере, для приложений, созданных с использованием библиотеки MFC) является объектом, в состав которого входят функции, определяющие его поведение, т. е. взаимодействие как с другими объектами, так и с данными. Любые действия пользователя во время выполнения приложения вызывают генерацию сообщений, содержащих в себе информацию о том, что конкретно сделал пользователь. Все объекты приложения обладают способностью реагировать на те или иные сообщения. Во многих случаях эта способность заложена в библиотеке MFC, а для других реализуются специальные обработчики. Повторю — все перечисленные свойства характерны (с той или иной степенью полноты) для каждого Windows-приложения.
Оставим в стороне приложения, базирующиеся на блоках диалога, и сосредоточимся на тех, которые в качестве своего главного окна имеют стандартное окно Windows. Основным для таких окон является объект, созданный на базе класса CFrameWnd (для SDI-приложений) или CMDlFmmeWnd (для
MDI-приложений). То, что делалось до сих пор, заключалось в создании некоторых необходимых оконных объектов и самостоятельной организации взаимодействия между ними. Мы полностью (более того — сознательно) игнорировали такое мощное средство, заложенное разработчиками в библиотеку MFC, как архитектура "документ/представление" (document/view).
В основе этой архитектуры лежат три глобальных понятия — фрейм, документ и представление. И если с фреймом мы уже достаточно близко познакомились (некоторые его свойства, связанные с архитектурой "документ/представление", будут рассмотрены чуть ниже), то два других необходимо рассмотреть подробно, что мы и сделаем в следующей главе. Здесь же просто определим их место в архитектуре "документ/представление".
Что же включают в себя эти понятия? В самом общем случае, под документом фирма Microsoft понимает те данные, с которыми работает приложение. Это может быть все что угодно, — "простой" текст, картинка и т. п. Отображение этих данных на экране осуществляется в так называемом фрейме документа. Разработчики библиотеки MFC создали для фреймов специальные классы окон — представления, которые отображают данные документа и управляют взаимодействием пользователя с ними. Другими словами, способ хранения данных в памяти или на диске никоим образом не влияет на их внешнее представление пользователю.
Рассматриваемая архитектура основывается на создании соответствующих фреймов документов, содержащих специальные окна — представления. Кроме того, фрейм осуществляет координацию взаимодействия между документами различных типов и их представлениями, направляя им команды и получая от них извещения. Маршруты, по которым осуществляется передача сообщений, подробно описаны нами в части 4 данного руководства.
Архитектура "документ/представление" разделяет между собой данные-документы и их изображения. Каждый документ представлен в виде объекта, который обеспечивает пространство для хранения данных в памяти и отвечает за такие операции, как запись и чтение документа с диска. Очевидно, что большая часть таких операций выполняется самим приложением, а не классами библиотеки MFC. Кроме того, для каждого используемого представления также создается отдельный объект, который представляет собой экранное окно, отвечающее за взаимодействие с пользователем и соответствующим объектом документа, печать и т. д. Совместную работу перечисленных объектов координирует фрейм, который в большинстве случаев является главным окном приложения (рис. 17.1).
Когда запускается приложение под Windows, пользователь взаимодействует с документами посредством их изображений во фреймах. Фреймы документов имеют два основных компонента: собственно фрейм и его содержимое. Для них библиотека MFC использует два различных класса. Класс фрейма управляет непосредственно фреймом, а класс представления — его содержимым. При этом окно представления является дочерним по отношению к фрейму, т. е. размещается в его рабочей области. Сам фрейм документа может быть окном SDI-приложения или дочерним окном MDI-приложения.
Рис. 17.1. Взаимосвязь между фреймом, документом и представлением
Работа с основными компонентами фрейма документа осуществляется также по-разному. За взаимодействие пользователя непосредственно с фреймом (изменение размеров, перемещение и т. д.) полностью отвечает сама система Windows. Управление же содержимым фрейма, т. е. представлениями, ложится на плечи разработчика.
Для координации создания и взаимодействия трех основных объектов рассматриваемой архитектуры в библиотеке MFC используются специальные классы шаблонов документов, которые создаются и управляются объектом "приложение".
Таким образом, архитектура "документ/представление" охватывает следующие основные классы:
Примечание
В представленный список не включены классы, предназначенные для работы с базами данных, OLE и Internet.
Нам осталось рассмотреть вопросы создания и взаимодействия фреймов, документов и представлений.
Приложение может поддерживать произвольное число типов документов. Единственное, что для этого нужно сделать — это создать и "зарегистрировать" во время инициализации объекта-приложения (при выполнении функции Initlnstance) необходимое число шаблонов документов. Причем для каждого типа документа, с которым предполагается работа, используется свой шаблон. Например, если приложение поддерживает документы двух разных типов — табличные и текстовые, то оно должно создать для них два разных шаблона.
В библиотеке классов MFC для работы с шаблонами документов реализованы специальный класс — CDocTemplate и два производных от него — CSingleDocTemplate и CMultiDocTemplate. Рассмотрим возможности, которые в них заложены.
Создание различных типов документов
Из иерархии классов, представленной на рис. 17.2, наглядно видно, что базовым классом для создания шаблонов является CDocTemplate. Это абстрактный класс, в котором реализованы основные функциональные возможности для работы с шаблонами документов — организация и управление взаимодействием между классами трех типов:
Рис. 17.2. Место классов для создания шаблонов документов в иерархии библиотеки MFC
Рассмотрим основные компоненты класса CDocTemplate. Начнем, как обычно, с конструктора, который фиксирует типы документа, фрейма и представления, доступные для работы с данным шаблоном.
CDocTemplate::CDocTemplate (
UINT nIDResource,
CRuntimeClass *pDocClass,
CRuntimeClass *pFrameClass,
CRuntimeClass *pViewClass)
Создает объект класса. В качестве аргументов используются: nIDResource — идентификатор ресурсов (меню, командные клавиши и строка описания типа шаблона документа), используемых с этим типом документов; pDocClass— указатель на объект CRuntimeClass, отвечающий за данные документа; pFrameClass — указатель на объект CRuntimeClass, характеризующий фрейм документа; pViewClass— указатель на объект CRuntimeClass, отвечающий за представление документа. Эти объекты должны быть уже определены к моменту создания объекта "шаблон документа". Память под этот объект следует распределять динамически, а полученный указатель передать в качестве параметра в функцию CWinApp::AddDocTemplate.
virtual void CDocTemplate::AddDocument (CDocument *pDoc),
virtual void CDocTemplater:RemoveDocument (CDocument *pDoc)
Добавляет (удаляет) документ, на который указывает pDoc, в список документов, ассоциированных с этим шаблоном. В производных классах CSingleDocTemplate и CMultiDocTemplate эти функции переопределены.
virtual void CDocTemplate::LoadTemplate ()
Загружает ресурсы для данного шаблона документа. Обычно вызывается библиотекой MFC во время создания объекта и практически не требует прямого вызова. Исключение составляет случай, когда объект класса создается на глобальном уровне. В этом случае во время выполнения функции CWinApp:: AddDocTemplate необходим явный вызов LoadTemplate.
Ресурсами, используемыми документами этого типа, могут быть меню, командные клавиши и единственный строковый ресурс. Рассмотрим более подробно структуру строки описания типа.
// Строка описания типа шаблона документа
STRINGTABLE PRELOAD
DISCARDABLE BEGIN
IDR_NOTETYPE "\nNote\nNote\nNote Files (*.tnd)\n.tnd
\nNote.Document\nNote Document"
END
Как видите, строка состоит из семи подстрок (первая — пустая), разделенных символом "\n", которые содержат дополнительную информацию о типе документа:
Примечание
В скобках указаны названия соответствующих полей диалога Advanced Options (Дополнительные параметры) мастера создания каркаса приложения AppWizard.
Если какая-либо из этих подстрок не включается в строку, то вместо нее обязательно используется разделяющий символ "\n". Заключительный символ "\n" в строке является необязательным.
Доступ к этой информации осуществляется с помощью функции
virtual BOOL CDocTemplate::GetDocString (
CStringS rString,
enum DocStringlndex index)
Позволяет получить определенную подстроку, описывающую тип документа, которая хранится в шаблоне и получена из соответствующей строки файла ресурсов приложения. Найденная подстрока записывается в rString. Параметр index определяет индекс подстроки, информацию о котором необходимо получить. Он может принимать одно из следующих значений:
CDocTemplate::windowTitle
Имя, отображаемое в заголовке главного окна приложения; представлено только в шаблоне документа для SDI-приложений
CDocTemplate::docName
Основа имени документа; имя нового документа этого типа складывается из основы, к которой добавляется число; если значение не определено, то по умолчанию используется Untitled (для русской версии Windows — Без Имени)
CDocTemplate::fileNewName
Имя типа документа; если значение не определено, то этот тип документа недоступен при выполнении команды File\New (Файл\Создать)
CDocTemplate::filterNewName
Описание типа документа и нейтрального символа для фильтра, сопоставляемого с документами этого типа. Данная строка изображается в списке типов файлов в блоке диалога File Open (Открытие файла); если значение не определено, то этот тип документа недоступен при выполнении команды File\Open (Файл/Открыть)
CDocTemplate::filterExt
Расширение документов этого типа; еслизначение не определено, то данный тип документа недоступен при выполнении команды File\Open (Файл/Открыть)
CDocTemplate::regFileTypeld
Идентификатор типа документа, хранящийся в реестре Windows. Эта строка предназначена только для внутреннего использования; если не определена, то тип документа не может быть зарегистрирован в File Manager (Диспечер файлов)
CDocTemplate::regFileTypeName
Имя типа документа, хранящегося в регистрационной базе данных; эта строка может быть отображена в блоках диалога приложения, вызываемых для доступа к регистрационной базе данных
Кроме того, в классе CDocTemptate объявлены еще четыре "чистые" функции: GetFirstDocPosition, GetNextDoc, OpenFileName и SetDefaultTitle. Наличие этих функций превращает CDocTemplate в абстрактный класс и, следовательно, его нельзя использовать непосредственно. Обычно приложения используют один из двух производных от него классов, предоставляемых библиотекой MFC: CSingleDocTemplate — для SDI- и CMultiDocTemplate — для MDI-приложений.
Примечание
Если по каким-либо причинам может потребоваться интерфейс, который фундаментально отличается от SDI и MDI, то можно образовать свой класс непосредственно из CDocTemplate.
Для полноты картины приведу прототипы еще нескольких функций этого класса, которые в явном виде используются достаточно редко, но в некоторых случаях могут быть полезны.
Если шаблон поддерживает несколько типов файлов, то, используя функцию MatchDocType для каждого шаблона, можно определить, какие шаблоны документов подходят для данного файла, и выбрать тот, который нужен:
virtual Confidence CDocTemplate::MatchDocType (
LPCTSTR IpszPathName,
CDocument *&rpDocMatch)
Служит для определения типа шаблона документа, используемого для открытия файла. Параметры: IpszPathName — полное имя файла; rpDocMatch — указатель на уже открытый документ, если файл, определенный в IpszPathName, открыт. Функция возвращает значение, определенное в перечислении Confidence:
enum Confidence {
noAttempt,
maybeAttemptForeign,
maybeAttemptNative,
yesbeAttemptForeign,
yesbeAttemptNative,
yesAlreadyOpen
};
Если файл, определенный в параметре IpszPathName, уже открыт, то функция возвращает CDocTemplate::yesAlreadyOpen и копирует объект CDocument файла в объект rpDocMatch. Если файл еще не открыт, но расширение имени в IpszPathName совпадает со значением, определенным в CDocTemplate::filterExt, то функция возвращает CDocTemplate::yesAttemptNative и устанавливает rpDocMatch в NULL. В остальных случаях функция возвращает значение CDocTemplate:: yesAttemptForeign. Другие члены перечисления можно использовать при переопределении этой функции.
virtual CDocument* CDocTemplate::CreateNewDocument ()
Создает новый документ, тип которого определяется текущим шаблоном. Функция возвращает указатель на вновь созданный документ или NULL в случае ошибки.
Как я уже сказал, CDocTemplate — абстрактный класс, и на его основе объекты шаблонов не создаются. Его основная роль заключается в обеспечении шаблонов основными свойствами, а для создания конкретных объектов в библиотеке MFC реализованы два другие класса — СSingleDoTemplate и CMultiDocTemplate, которые специально адаптированы для работы соответственно в SDI- и MDI-приложениях.
Шаблоны однодокументных приложений
Для создания шаблона однодокументного интерфейса в библиотеке MFC реализован класс СSingleDocTemplate. Как вы помните, в SDI-приложениях главный фрейм является одновременно и фреймом документа, другими еловами — в каждый конкретный момент времени может быть открыт только один документ. Обычно SDI-приложения поддерживают один тип документа (хотя это и не является обязательным условием), т. е. они содержат только один объект класса CSingleDocTemplate.
Объекты этого класса обрабатываются внутри библиотеки- MFC. В своей защищенной части класс содержит член, в котором хранится указатель на документ, присоединенный к шаблону:
class CSingleDocTemplate : public CDocTemplate
{
...
protected:
CDocument *m_pOnlyDoc;
...
}
Принцип действия и параметры конструктора полностью повторяют параметры базового класса CDocTemplate. Помимо конструктора, в классе реализованы все четыре "чистые" функции класса CDocTemplate.
virtual POSITION CDocTemplate::GetFirstDocPosition () .
Возвращает позицию первого документа из списка документов, ассоциированных с данным шаблоном, или NULL, если список пуст. Она переопределена в обоих производных классах— CSingleDocTemplate и CMultiDocTemplate. Если возникло желание создать свой класс на базе CDocTemplate, то эту функцию следует в нем переопределить.
virtual CDocument* CDocTemplate::GetlfextDoc (POSITION SrPos)
Возвращает указатель на объект-документ из общего списка документов, ассоциированных с шаблоном, который хранится непосредственно за документом, заданным его позицией в списке (параметр rPos). Позиция полученного документа записывается в rPos. Если возвращен указатель на последний элемент списка, то в параметр rPos функция записывает NULL. Для получения указателя на первый документ списка необходимо вызвать эту функцию до GetFirstDocPosition. При использовании в качестве параметра rPos недопустимого значения библиотека MFC генерирует исключение.
virtual CDocument* CDocTemplate::OpenDocumentFile (
LPCTSTR IpszPathName,
BOOL bMakeVisible = TRUE)
Позволяет открыть файл, заданный полным именем IpszPathName, где содержится необходимый документ. Если значение этого параметра равно NULL, то вызывается функция CreateNewDocument, которая создает новый файл, содержащий документ типа, ассоциированного с этим шаблоном. При успешном завершении функция возвращает указатель на документ, в противном случае NULL.
virtual void CDocTemplate;.:SetDefaultTitle (CDocument *pDoc)
Загружает заголовок документа, используемый по умолчанию и определенный в строке ресурса, и выводит его в полосу заголовка фрейма документа.
Кроме перечисленных "чистых" функций, в классе переопределены также функции базового класса AddDocument и RemoveDocument.
Шаблоны многодокументных приложений
За решение этой задачи в библиотеке MFC отвечает класс CMultiDocTemplate, который определяет шаблон документа для многодокументного интерфейса. MDI-приложения используют главный фрейм как рабочее место, в котором пользователь может открывать произвольное число окон, отображающих данные того или иного документа. Возможна поддержка нескольких шаблонов документов, и документы различных типов могут быть открыты одновременно.
Приложение использует шаблон(ы) документа, когда пользователь создает новый документ. Если приложение поддерживает больше одного типа документов, то библиотека получает имена поддерживаемых типов из шаблона и отображает их в списке блока диалога New, реализованного на базе класса CNewTypeDlg. Как только пользователь выбрал тип документа, приложение создает объекты "документ", "фрейм" и "представление" и сопоставляет их друг другу.
В классе определены два общедоступных члена, отвечающих за совместное использование меню и таблицы командных клавиш:
HMEND CMultiDocTemplate::m_hMenuShared;
HACCEL CMultiDocTemplate::m_hAccelTable;
Поскольку в многодокументных приложениях одновременно могут быть открыты несколько документов, в классе CMultiDocTemplate предусмотрен защищенный член:
CPtrList CMultiDocTemplate::m_docList;
хранящий список открытых документов данного типа. Доступ к элементам списка осуществляется посредством функций GetFirstDocPosition и GetNextDoc.
Число непоименованных открытых документов хранится в специальном защищенном члене класса (нумерация начинается с нуля):
UINT CMultiDocTemplate: :m_nUntitledCount;
Значение, хранящееся в этой переменной, используется для генерации имени по умолчанию и автоматически увеличивается при создании новых документов.
Для MDI-приложений определен, конечно же. конструктор класса. Помимо него, в классе CMultiDocTemplate реализованы также все четыре "чистые" функции базового класса: GetFirstDocPosition, GetNextDoc, OpenFileName и SetDefaultTitle, описание которых приведено выше. Кроме того, "наполнены новым содержанием" еще три функции: LoadTemplate, AddDocument и Remove Document.
В заключение темы создания и использования шаблонов документов рассмотрим, каким образом с документом ассоциируется некоторая пиктограмма. Начнем с того, что написано в документации. "Пиктограмма, зарегистрированная для каждого шаблона документа, базируется на его позиции в списке шаблонов приложения. Порядок расположения шаблонов определяется порядком его занесения в список, т. е. порядком вызова функций AddDocTemplate. При этом библиотека MFC присваивает первый ресурс пиктограммы самому приложению, следующий — первому документу и т. д.". А теперь попытаемся разобраться, что же все-таки здесь имеется в виду, ведь при работе пиктограмма большей частью определяется своим числовым идентификатором, а отнюдь не "...позицией в списке...". Рассмотрим фрагмент файла <resource.h> приложения NoteDraw:
...
#define IDR_MAINFRAME 128 // идентификатор главного окна
#define IDR_NOTETYPE 129 // идентификатор окна текстового документа
#define IDR_DRAWTYPE 130 // идентификатор окна графического
// документа
#define IDI_NOTEICON 129 // идентификатор пиктограммы
// текстового документа
#define IDI__DRAWICON 130 // идентификатор пиктограммы
// графического документа
...
Прежде всего следует обратить внимание на то. что числа, характеризующие идентификаторы ресурсов документов и пиктограмм, принадлежат одному ряду. Это условие является обязательным, т. к. в противном случае на экране вы увидите пиктограмму по умолчанию вместо ожидаемой. И, наконец, числовые значения идентификаторов ресурсов документов и пиктограмм должны совпадать, иначе можно в окне текстового документа увидеть пиктограмму графического и наоборот. Во избежание подобных недоразумений рекомендуем для пиктограмм использовать тот же идентификатор, что и для ресурсов документов: IDR_NOTETYPE и IDR_DRAWTYPE.
Место объекта-приложения в архитектуре "документ/представление"
Класс CWinApp был нами достаточно подробно рассмотрен, и нет смысла повторяться. Однако мы пока не касались его свойств, связанных с архитектурой "документ/представление", отложив обсуждение этого вопроса до более удобного момента. Теперь пришло время заполнить этот пробел. Рассмотрим кратко функции класса CWinApp, которые отвечают за взаимодействие с другими объектами, входящими в архитектуру "документ/представление".
void CWinApp::AddDocTemplate (CDocTemplate *pTemplate)
Добавляет шаблон документа pTemplate в список доступных шаблонов, которые поддерживает приложение. Все необходимые добавления в шаблон документа следует производить в функции Initlnstence до вызова функции RegisterShellFileTypes, т. к. в противном случае типы документов не будут представлены в реестре.
В качестве примера рассмотрим фрагмент кода приложения NoteDraw, отвечающий за создание двух шаблонов документов, один из которых позволяет создавать документы для работы с текстом, а второй — с графическими объектами:
// Фрагмент кода из файла NoteDraw.срр
...
CMultiDocTemplate* pDocTemplate;
// Создаем объект шаблона документа для работы с текстом
pDocTemplate = new CMultiDocTemplate(IDR_NOTETYPE,
RUNTIME_CLASS(CNoteFrame),
RUNTIME_CLASS(CNoteDoc),
RUNTIME_CLASS(CNoteView));
// Добавляем шаблон в список
AddDocTemplate(pDocTemplate);
// Создаем объект шаблона документа для работы с графикой
pDocTemplate = new CMultiDocTemplate(IDR_DRAWTYPE,
RUNTIME_CLASS(CDrawDoO,
RUNTIME_CLASS(CDrawFrame),
RUNTIME_CLASS(CDrawView));
// Добавляем и этот шаблон в список
AddDocTemplate(pDocTemplate);
// Фрагмент кода из файла NoteDraw.гс
STRINGTABLE PRELOAD DISCARDABLE BEGIN
IDRjyiAINFRAME "NoteDraw"
IDR_NOTETYPE "\nNote\nNote\nNote Files
(*.tnd)\n.tnd\nNote.Document\nNote Document"
IDR_DRAWTYPE "\nDraw\nDraw\nDraw Files
(*.dnd)\n.dnd\nDraw.Document\nDraw Document"
END
...
Как вы прекрасно понимаете, если к созданным и добавленным шаблонам не будет доступа, то зачем они вообще нужны. Естественно, в библиотеке есть соответствующие функции:
POSITION CWinApp::GetFirstDocTenplatePosition ()
Позволяет получить позицию первого шаблона документа приложения. Если список пуст, то возвращается NULL.
CDocTemplate* CWinApp::GetNextDocTemplate (POSITION Spos)
Позволяет получить шаблон документа, определяемый его позицией (параметр ров) в списке шаблонов приложения. После возврата из функции в роз содержится значение позиции следующего шаблона документа. Если полученный шаблон был последним в списке, то ров устанавливается в NULL.
Использование этих двух функций позволяет просматривать список доступных шаблонов документов приложения. При этом порядок их расположения в списке определяется порядком их записи туда посредством вызова функции AddDocTemplate.
virtual CDocument* CWinApp::OpenDocumentFile (LPCTSTR IpszFileName)
Открывает файл документа, задаваемый именем IpszFileName, и возвращает указатель на соответствующий объект-документ. Если к моменту вызова функции файл уже был открыт, то активизируется фрейм, содержащий этот документ. Если приложение поддерживает несколько шаблонов документов, то для поиска соответствующего шаблона библиотека MFC использует расширение файла. В случае успешного выполнения шаблон создает фрейм и представление соответствующего документа.
void CWinApp::CloseAllDocuments (BOOL bEndSession)
Закрывает все открытые документы до выхода из приложения. Если при этом параметр bEndSession равен TRUE, то завершается и сеанс работы с Windows. Функцию следует вызывать после функции HideApplication.
virtual BOOL CWinApp::SaveAllModified ()
Вызывается библиотекой MFC для того, чтобы сохранить все документы, когда закрывается главное окно приложения или обрабатывается сообщение WM_ QUERYENDSESSION. Реализация этой функции по умолчанию вызывает функцию CDocument::SaveModified для сохранения всех изменений в документах, открытых внутри приложения.
afx_msg void CWinApp::OnFileNew ()
И
afxjnsg void CWinApp::OnFileOpen ()
Обработчики команд ID_FILE_NEW и ID_FILE_OPEN. Если обработка этих команд возлагается на библиотеку MFC, в карту сообщений класса приложения необходимо добавить операторы
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
ИЛИ
ON_COMMAND!ID_FILE_NEW, OnNameCommandFileNew)
ON_COMMAND(ID_FILE_OPEN, OnNameCoiranandFileOpen)
если вы хотите обрабатывать команды самостоятельно. При этом на программиста ложится также реализация обработчика OnNameCommandFileNew (имя, естественно, произвольное).
В свете архитектуры "документ/представление" основным назначением объекта-приложения является то, что он содержит полный список шаблонов документов, используя для этого общедоступный член класса CWinApp.
CDocManager* CWinApp::m_pDocManager;
Фактически список хранится в защищенном члене класса библиотеки MFC — CDocManager
CPtrList CDocManager::m_templateList;
и доступ к нему осуществляется при помощи рассмотренных функций объекта-приложения GetFirstDocTemplatePosition и GetNextDocTemplate, которые после предварительной обработки передают управление соответствующей функции класса CDocManager.
Наибольший интерес здесь представляет тот факт, что приложение может одновременно поддерживать несколько шаблонов (или типов) документов (рис. 17.3).
Для выбора необходимого шаблона при обработке команды ID_FILE_NEW создается и выводится на экран специальный блок диалога (рис. 17.4) на основе реализованного в библиотеке MFC (файл <docmgr.cpp>) класса CNewTypeDlg.
Он позволяет пользователю просматривать список имеющихся у приложения (добавленных) шаблонов документов и -выбирать один из них.
Примечание
В принципе, вместо класса CNewTypeDlg можно использовать и созданный самостоятельно, но я не вижу для этого серьезных причин.
Рис. 17.3. Несколько документов двух типов, используемых одним приложением
Рис. 17.4. Блок диалога для выбора типа вновь создаваемого документа
Рис. 17.5. Процесс создания шаблона документа
Таким образом, если внимательно посмотреть на перечисленные функции и переменные класса CWinApp, при работе в рамках архитектуры "документ/представление" роль объекта-приложения заключается в создании, хранении и организации доступа к списку доступных для использования шаблонов документов. Фактически его роль сводится лишь к передаче управления "диспетчеру документов", представленному классом CDocManager, который, собственно, и выполняет всю необходимую для этого работу. Схематично процесс создания шаблона документа изображен на рис. 17.5.
Роль фреймов в архитектуре "документ/представление"
Фрейм документа, который мы будем здесь рассматривать, имеет две основные составляющие — собственно фрейм и его окно, представляющее на экране данные документа. Эти два компонента представлены и управляются различными группами классов библиотеки MFC:
Процесс создания главного и дочерних фреймов подробно обсуждался в главах 6 и 13, и нет смысла повторяться. Вместо этого рассмотрим несколько фрагментов кода из приложения NoteDraw:
// Описание класса (файл Childfrm.h)
class CNoteFrame : public CMDIChildWnd
{
// Для возможности динамического создания объекта
// класса CNoteFrame используется макрос
DECLARE_DYNCREATE(CNoteFrame)
protected:
CNoteFrameО; // конструктор
...
};
и
// Реализация класса (файл Childfrm.cpp)
// Для возможности динамического создания объекта
// класса CNoteFrame используется макрос
IMPLEMENT_DYNCREATE(CNoteFrame, CMDIChildWnd)
Как вы уже поняли, интерес здесь представляют два макроса, подробно описанные в главе 1 - DECLARE_DYNCREATE и IMPLEMENTJDYNCREATE -и защищенный (protected) конструктор по умолчанию. Дело в том, что все вышеперечисленные составляющие архитектуры "документ/представление" (документ, фрейм и представление) создаются шаблоном документов динамически, т. е. во время выполнения программы. Кроме того, эти макросы охватывают также все возможности двух других групп макросов — DECLARE_DYNAMIC, IMPLEMENT^ DYNAMIC, DECLARE_SERIAL и IMPLEMENT^ SERIAL, что гарантирует также поддержку получения информации во время выполнения программы и сериализации. Забыв включить эти макросы в объявление и, соответственно, в реализацию некоторого класса, вы тем самым исключаете для него возможность корректной работы механизмов RUNTIME_CLASS и сериализации.
Рассмотрим теперь функции класса CFrameWnd, которые связаны с архитектурой "документ/представление".
void BOOL CFrameWnd::OnCreateClient(
LP-CREATESTRUCT Ipcs,
CCreateContext *pContext)
Вызывается только библиотекой MFC из обработчика OnCreate и не должна вызываться непосредственно. В качестве параметров в функцию передаются:
/pcs— указатель на структуру CREATESTRUCTи pContext— указатель на структуру CCreateContext
struct CCreateContext{
CRuntimeClass* m_pNewViewClass;
CDocument* mjpCurrentDoc;
CDocTemplate* m_pNewDocTemplate;
CView* m_pLastView;
CFrameWnd* m_pCurrentFrame
CCreateContext() ;
} ;
Эта структура создается шаблоном документа (при создании документа и ассоциированных с ним компонентов). Перечислим ее поля (все поля могут содержать NULL):
m_pNewViewClass
Указатель на структуру CRuntimeClass вновь создаваемого представления
m_pCurrentDoc
Указатель на уже существующий документ, с которым ассоциируется новое представление
m_pNewDocTemplate
Указатель на шаблон документа, с которым ассоциирован создаваемый фрейм
m_pLastView
Указатель на начальное представление, к которому добавляется создаваемое, например, при работе с разделенным окном или при создании дополнительного представления документа
m_pCurrentFrame
Указатель на текущий фрейм, к которому добавляется создаваемый, например, при создании дополнительного фрейма документа
Библиотекой MFC эта структура используется в следующих функциях: CWnd::Create, CFrameWnd::CreateView, CFrameWnd::LoadFrame, CFrameWnd::OnCreateClient, CFrameWnd::Create, CSplitterWnd::Create и CSplitterWnd::CreateView.
virtual void CFrameWnd::ActivateFrame (int nCmdShow = -1.)
Активизирует фрейм, т. е. делает его видимым и доступным для пользователя. Параметр nCmdShow принимает такие же значения, как и посылаемые в функцию CWnd::ShowWindow. По умолчанию функция делает фрейм видимым и переводит его на самый верх Z-порядка и при необходимости выполняет то же самое для главного окна приложения. При переопределении после проведения необходимых действий, например, разворачивания дочернего окна, необходимо вызвать соответствующую функцию базового класса с явным указанием параметра nCmdShow, как сделано в приложении NoteDraw.
void CNoteFrame::ActivateFrame(int nCmdShow)
{
// Разворачиваем фрейм документа
CMDIChildWnd::ActivateFrame(SW_MAXIMIZE);
}
void 'CFrameWnd::InitialUpdateFrame(
CDocument *pDoc,
BOOL bMakeVisible)
Вызывается после создания нового фрейма и инициирует вызов обработчика OnlnitialUpdate для всех своих представлений. При этом активизируется представление, у которого идентификатор дочернего окна установлен в AFX_ IDW_PANE_FIRST. Если значение параметра bMakeVisible равно TRUE, то фрейм делается видимым; в противном случае - нет. Параметр pDoc определяет документ, ассоциированный с данным фреймом.
virtual CFrameWnd* CFrameWnd:: GetActiveFraine ()
Возвращает указатель на активное дочернее окно MDI-приложения. При работе с SDI-приложениями функция возвращает указатель на текущий объект — this.
void CFrameWnd::SetActiveView(
CView *pViewNew,
BOOL bNotify = TRUE)
Переводит представление pView в активное состояние. Эта функция автоматически вызывается библиотекой MFC, когда пользователь сменил фокус представления внутри фрейма. Ее можно вызвать специально для программной смены фокуса представления.
...
// Устанавливаем фокус на область (1, 0)
SetActiveView ( (CView*)m__spltWnd.GetPane (1, 0) ) ;
...
Параметр bNotify определяет, будет ли само представление извещено об активизации. Если его значение равно TRUE, то для активизируемого представления вызывается функция OnActivateView.
Примечание
Об областях мы подробно поговорим немного позже.
CView* CFrameWnd::GetActiveView()
Возвращает указатель на объект текущего активного представления. Если такого нет, то возвращается NULL.
CWnd* CFrameWnd::CreateView(
CCreateContext *pContext,
UINT nID = AFX_IDW_PANE_FIRST)
Создает представление внутри фрейма, которое не является производным от CView, и возвращает указатель на него. После выполнения функции необходимо самостоятельно установить активность и видимость вновь созданного представления. Параметр pContext определяет тип представления и документа, а пЮ — идентификатор представления.
virtual CDocument* CFrameWnd::GetActiveDocument()
Возвращает указатель на документ, ассоциированный с текущим активным представлением. Если такого нет, что, в принципе, возможно, то функция возвращает значение NULL.
В качестве примера рассмотрим фрагмент кода из приложения NoteDraw, где показывается, как можно переопределить функцию OnCreateClient для создания статического разделенного окна с двумя горизонтальными областями, в каждой из которых создается свое представление (рис. 17.6).
Рис. 17.6. Результат выполнения переопределенной функции OnCreateClient
Примечание
Более подробно все перечисленные действия мы рассмотрим в следующей главе при обсуждении собственно представлений.
BOOL CNoteFrame::OnCreateClient(LPCREATESTRUCT Ipcs,
CCreateContext* pContext)
{
// Создаем разделенное окно Windows
//с двумя строками и одним столбцом
if(!m_spltWnd.CreateStatic(th±s, 2, 1))"
{
...
}
// Создаем представление в первой строке — горизонтальной области
if(!m_spltWnd.CreateView(0, .0, pContext->m_pNewViewClass,
CSize(lpcs->cx, lpcs->cy*2/3), pContext))
{
...
}
// Создаем представление во второй строке — горизонтальной области
if{!m_spltWnd.CreateView(l, О, RUNTIME_CLASS(CTextView),
CSize(0, 0), pContext))
{
...
}
// Устанавливаем фокус на вторую горизонтальную область
SetActiveView((CView*)m_spltWnd.GetPane(I, 0));
return TRUE;
}
Характер рассмотренных функций достаточно наглядно показывает роль фрейма в архитектуре "документ/представление". Прежде всего, фрейм хранит путь к текущему активному представлению. В случае, когда он содержит более одного представления, например при работе с разделенным окном, то в качестве активного принимается самое последнее используемое представление.
Примечание
Активность представления не зависит от активного окна Windows или текущего фокуса ввода.
Последняя из описанных выше функций показывает, что фрейм имеет также доступ к документу, ассоциированному с текущим активным представлением. Кроме того, фрейм занимается координацией взаимодействия между различными представлениями, ассоциированными с определенным документом, направляя им команды и получая от них извещения. Маршруты, по которым осуществляется обмен сообщениями, подробно рассмотрены нами в главе 5.
И, наконец, последнее. Мы уже говорили, что с каждым документом ассоциируются определенные ресурсы меню и командных клавиш. Так вот, задачу разделения этих ресурсов среди документов, созданных на основе одного шаблона, и замены ресурсов при активизации документа другого типа также с успехом решают главный фрейм и фрейм документа. В заключение коротко перечислим те шаги, которые необходимо проделать для организации работы в рамках архитектуры "документ/представление":
Теперь, когда мы разобрались в общей структуре рассматриваемой архитектуры, перейдем к вопросам создания и работы с ее основными составляющими — документами и ассоциированными с ними представлениями.
Создание каркаса приложения на базе архитектуры
"документ/представление"
В этом разделе мы познакомимся с тем, как создать проект, в котором реализована поддержка всего того, о чем мы только что говорили:
1. Вызвав диалог New, выберите в нем тип приложения MFC AppWizard (ехе), а в поле Project Name — имя создаваемого приложения NoteDraw. После нажатия кнопки ОК запустится мастер AppWizard и вы увидите его первое окно — рис. 17.7.
В этом окне представлены все три основных типа приложений Windows: однодокументное, многодокументное и на базе блока диалога.
Рис. 17.7. Первое окно мастера AppWizard
2. Установите флажок Document/View architecture support?, если вы работаете с Visual C++ 6.0, выберите переключатель Multiple documents, и нажмите кнопку Next.
3. Смело дважды нажимайте кнопку Next, чтобы перейти к третьему окну мастера, поскольку во втором окне задается поддержка для работы с базами данных. В третьем окне сбросьте флажок ActiveX Controls (элементы управления ActiveX) и переходите к следующему окну.
4. В этом окне (рис. 17.8) предлагается выбор некоторых дополнительных возможностей. Можно спокойно оставить предложенные по умолчанию установки, поскольку именно этими возможностями мы и будем заниматься.
Рис. 17,8. Четвертое окно мастера AppWizard
Рис. 17.9. Последнее окно мастера AppWizard
5. Дважды нажмите кнопку Next, чтобы перейти к последнему окну мастера (рис. 17.9), в котором можно изменить имена создаваемых классов, а также сами базовые классы.
6. После того как все изменения выполнены, нажмите кнопку Finish (Готово). На экране появится окно с информацией о проекте. После нажатия кнопки ОК мастер создаст для вас каркас приложения и завершит свою работу.
Именно в этот созданный каркас приложения мы и будем добавлять новый код.