Шаблоны многодокументных приложений


За решение этой задачи в библиотеке 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

IDR_MAINFRAME "NoteDraw"

IDR_NOTETYPE "nNotenNotenNote Files

(*.tnd)n.tndnNote.DocumentnNote Document"

IDR_DRAWTYPE "nDrawnDrawnDraw Files

(*.dnd)n.dndnDraw.DocumentnDraw 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.

Наибольший интерес здесь представляет тот факт, что приложение может одновременно поддерживать несколько шаблонов (или типов) документов.

Для выбора необходимого шаблона при обработке команды ID_FILE_NEW создается и выводится на экран специальный блок диалога на основе реализованного в библиотеке MFC (файл <docmgr.cpp>) класса CNewTypeDlg.

Он позволяет пользователю просматривать список имеющихся у приложения (добавленных) шаблонов документов и -выбирать один из них.

Примечание

В принципе, вместо класса CNewTypeDlg можно использовать и созданный самостоятельно, но я не вижу для этого серьезных причин.

<>

Несколько документов двух типов, используемых одним приложением

<>

Блок диалога для выбора типа вновь создаваемого документа

<>

Процесс создания шаблона документа

Таким образом, если внимательно посмотреть на перечисленные функции и переменные класса CWinApp, при работе в рамках архитектуры "документ/представление" роль объекта-приложения заключается в создании, хранении и организации доступа к списку доступных для использования шаблонов документов. Фактически его роль сводится лишь к передаче управления "диспетчеру документов", представленному классом CDocManager, который, собственно, и выполняет всю необходимую для этого работу.

Роль фреймов в архитектуре "документ/представление"

Фрейм документа, который мы будем здесь рассматривать, имеет две основные составляющие — собственно фрейм и его окно, представляющее на экране данные документа. Эти два компонента представлены и управляются различными группами классов библиотеки MFC:

  • классы фреймов (CFrameWndи CMDIChildWnd);
  • классы представлений (CView, CScrollView и другие).

Рассмотрим несколько фрагментов кода из приложения NoteDraw:

// Описание класса (файл Childfrm.h)

class CNoteFrame : public CMDIChildWnd

{

// Для возможности динамического создания объекта

// класса CNoteFrame используется макрос

DECLARE_DYNCREATE(CNoteFrame)

protected:

CNoteFrameО; // конструктор

...

};

и

// Реализация класса (файл Childfrm.cpp)

// Для возможности динамического создания объекта

// класса CNoteFrame используется макрос

IMPLEMENT_DYNCREATE(CNoteFrame, CMDIChildWnd)

Как вы уже поняли, интерес здесь представляют два макроса 1 - DECLARE_DYNCREATE и IMPLEMENT_DYNCREATE -и защищенный (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* m_pCurrentDoc;

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:: GetActiveFrame ()

Возвращает указатель на активное дочернее окно 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 для создания статического разделенного окна с двумя горизонтальными областями, в каждой из которых создается свое представление.

<>

Результат выполнения переопределенной функции 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 или текущего фокуса ввода.

Последняя из описанных выше функций показывает, что фрейм имеет также доступ к документу, ассоциированному с текущим активным представлением. Кроме того, фрейм занимается координацией взаимодействия между различными представлениями, ассоциированными с определенным документом, направляя им команды и получая от них извещения.

С каждым документом ассоциируются определенные ресурсы меню и командных клавиш. Так вот, задачу разделения этих ресурсов среди документов, созданных на основе одного шаблона, и замены ресурсов при активизации документа другого типа также с успехом решают главный фрейм и фрейм документа. В заключение коротко перечислим те шаги, которые необходимо проделать для организации работы в рамках архитектуры "документ/представление":

  • создать объект-приложение;
  • создать объекты-документы;
  • создать необходимое число представлений каждого документа;
  • в функции Initlnstance объекта-приложения создать объекты-шаблоны документов и добавить их к его списку;
  • создать необходимые ресурсы для каждого из них;
  • переопределить функцию CFrameWnd::OnCreateClient, в которой присоединить к фрейму документа необходимые представления;
  • создать и вывести на экран главное окно приложения с присоединенными к нему фреймами документа.

Теперь, когда мы разобрались в общей структуре рассматриваемой архитектуры, перейдем к вопросам создания и работы с ее основными составляющими — документами и ассоциированными с ними представлениями.

Создание каркаса приложения на базе архитектуры

"документ/представление"

В этом разделе мы познакомимся с тем, как создать проект, в котором реализована поддержка всего того, о чем мы только что говорили:

1. Вызвав диалог New, выберите в нем тип приложения MFC AppWizard (ехе), а в поле Project Name — имя создаваемого приложения NoteDraw. После нажатия кнопки ОК запустится мастер AppWizard и вы увидите его первое окно.

В этом окне представлены все три основных типа приложений Windows: однодокументное, многодокументное и на базе блока диалога.

Первое окно мастера AppWizard

2. Установите флажок Document/View architecture support, выберите переключатель Multiple documents, и нажмите кнопку Next.

3. Смело дважды нажимайте кнопку Next, чтобы перейти к третьему окну мастера, поскольку во втором окне задается поддержка для работы с базами данных.

4. В этом окне предлагается выбор некоторых дополнительных возможностей. Можно спокойно оставить предложенные по умолчанию установки, поскольку именно этими возможностями мы и будем заниматься.

Четвертое окно мастера AppWizard

 

5. Дважды нажмите кнопку Next, чтобы перейти к последнему окну мастера, в котором можно изменить имена создаваемых классов, а также сами базовые классы.

 

Последнее окно мастера AppWizard

6. После того как все изменения выполнены, нажмите кнопку Finish (Готово). На экране появится окно с информацией о проекте. После нажатия кнопки ОК мастер создаст для вас каркас приложения и завершит свою работу.

Именно в этот созданный каркас приложения мы и будем добавлять новый код.

 



Дата добавления: 2017-01-26; просмотров: 2264;


Поиск по сайту:

Воспользовавшись поиском можно найти нужную информацию на сайте.

Поделитесь с друзьями:

Считаете данную информацию полезной, тогда расскажите друзьям в соц. сетях.
Poznayka.org - Познайка.Орг - 2016-2024 год. Материал предоставляется для ознакомительных и учебных целей.
Генерация страницы за: 0.044 сек.