© Виталий В. Губанов,
НФ "УТЕЛ", Украина
В своей статье я хочу рассказать о ключевых моментах создания приложений с использованием мультидокументного интерфейса (Multiple Document Interface, MDI) библиотеки Microsoft Foundation Classes (MFC) компании Microsoft.
Во многих учебниках по MFC созданию MDI-приложений уделяется недостаточно внимания. Обычно ограничиваются описанием шагов его создания с помощью AppWizard и примером работы с текстовым документом: открытие документа в нескольких дочерних окнах, правка и сохранение.
Сложнее дело обстоит, если создается MDI проект, в котором дочерние окна должны отображать различные данные (например, из таблиц баз данных) и в то же время, предоставлять возможность пользователю вводить и корректировать данные. Эти окна должны иметь набор GUI (Graphic User Interface) элементов: кнопки (Button), поля ввода (EditBox), списки (ListBox) и пр.
Библиотека MFC поддерживает приложения двух принципиально разных типов – на базе однодокументного (Single Document Interface, SDI) и MDI интерфейса. У SDI-приложения всего одно окно и загрузить одновременно можно только один документ. Дополнительно для работы с документом возможно использование модальных и немодальных диалогов. Хорошим примером SDI-приложения является программа Notepad. В отличие от SDI, MDI приложение имеет несколько так называемых дочерних (child) окон, каждое из которых работает с отдельным документом. Примером MDI приложения может служить MS Word.
Главное окно MDI приложения напоминает рабочий стол, на котором размещаются различные документы (дочерние окна). Эти окна с документами можно свернуть в значки внутри главного окна, разместить различным образом на «столе» (каскадом, мозаикой и пр.).
Базовое MDI приложение легко создается с помощью AppWizard. При запуске последнего, в диалоговом окне AppWizard - Step 1 тип приложения MDI установлен по умолчанию (рис. 1).
Рис. 1. Первое диалоговое окно AppWizard при создании MFC приложения.
Процесс запуска SDI и MDI приложений во многом одинаков. Объект-приложение производного от CWinApp класса имеет переопределенную функцию-член InitInstance. Эта функция отлична от аналогичной функции SDI приложения. Начинается эта функция с вызова AddDocTemplate. Это выглядит так:
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CmultiDocTemplate(
IDR_XXTYPE,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(CMyView));
AddDocTemplate(pDocTemplate);
В SDI приложениях существует один класс окна-рамки и один объект этого класса и AppWizard генерирует класс с именем CMainFrame, производный от CFrameWnd. В MDI приложении присутствуют два класса окна-рамки и множество объектов-рамок.
MDI приложение имеет класс дочернего окна CСhildFrame и при каждом открытии нового документа приложение создает новый экземпляр этого класса для отображения документа внутри главного окна.
Рис. 2. Взаимосвязь окна-рамки и окна отображения в MDI приложении.
В SDI-приложении объект CMainFrame обрамляет приложение и содержит объект «вид». В MDI-приложении эти две вещи разделены: в InitInstance создается объект CMainFrame, а внутри объекта CChildFrame содержится окно отображения. Код, генерируемый AppWizard, имеет вид:
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
Элемент данных m_pMainWnd принадлежит классу CWinApp. Функция InitInstance присваивает этому элементу данных указатель на основное окно-рамку. Поэтому, если такой указатель понадобится, мы можем получить доступ к m_pMainWnd через глобальную функцию AfxGetApp.
В MDI-приложении существуют два вида основного меню: один вид используется при открытии только основного окна-рамки (без открытых дочерних окон), второй - при открытии хотя бы одного дочернего окна. При этом используются и два отдельных ресурса меню: IDR_MAINFRAME и IDR_TESTTYPE (где TEST является названием приложения). Вот как выглядят эти два ресурса с разбивкой на подстроки:
IDR_MAINFRAME
“Test” // заголовок основного окна приложения
IDR_TESTTYPE
\n // заголовок окна приложения (появляется в заголовке
// дочернего окна)
Test\n // это имя документа по умолчанию
Test\n // имя типа документа
Test Files (*.dat)\n // описание и фильтр для типа документа
.dat\n // расширение для файлов документов этого типа
Test.Document\n // идентификатор типа файла в реестре
Test Document // описание типа файла в реестре
Если посмотреть на файл ресурса Test.rc, то мы увидим, что эти подстроки объединены в одну длинную строку. Заголовок приложения берется из ресурса IDR_MAINFRAME и при открытии документа к этому заголовку добавляется имя файла этого документа.
При создании пустого документа MDI-приложение вызывает OnFileNew. Основное окно-рамка уже создана, поэтому OnFileNew вызывает функцию OpenDocumentFile класса CwinApp. При этом происходит следующее:
Рассмотрим создание MDI-приложения на практике. Запускаем Microsoft Visual Studio и создаем новый проект с именем Test. Из списка предложенных типов проектов выберем тип MFC AppWizard (exe). AppWizard предложит вам пройти шесть шагов для создания проекта. На первом шаге оставляем по умолчанию тип создаваемого приложения Multiple Documents, как изображено на рис. 1. На следующих четырех шагах оставляем опции по умолчанию. На шестом шаге базовый класс CView изменим на CFormView, как изображено на рис. 3. Нажав клавишу Finish, AppWizard автоматически создаст каркас MDI-приложения.
Рис. 3. Шаг 6. Изменение базового класса приложения на CFormView.
Если вы не предполагаете сразу загружать какой-нибудь документ при запуске приложения, тогда можно ресурс меню IDR_MAINFRAME удалить, а IDR_TESTTYPE переименовать в IDR_MAINFRAME. В этом случае необходимо также добавить следующий код в раздел инициализации класса CTestApp:
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
//Don't show a new MDI child window during startup
if (cmdInfo.m_nShellCommand == CCommandLineInfo::FileNew)
cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;
// Этим кодом мы сообщаем каркасу приложения не открывать новое окно при
//старте приложения.
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
Изменим свойства (Properties) пункта меню New на Test, ID_FILE_NEW на ID_OPEN_TEST, а Open… на Data и ID_FILE_OPEN на ID_OPEN_DATA, как показано на рис. 4.
Рис. 4. Свойства нового пункта меню Test.
Добавим два диалоговых ресурса (два дочерних окна). Стиль этих окон необходимо установить, как показано на рис. 5.
Рис. 5. Свойства дочерних диалоговых окон.
В списке ресурсов String Table необходимо создать два идентификатора: IDR_TESTOPEN и IDR_DATAOPEN. Примерные свойства этих ресурсов показаны на рис. 6.
Рис. 6. Свойства дочерних диалоговых окон.
На созданные два окна можно поместить необходимые элементы управления (EditBox, ListBox, Radio Buttons, Check Buttons и пр.).
Теперь необходимо определить классы для новых окон. Для первого окна это будет класс CTestClass, а для второго – CDataClass. Обратите внимание, что по умолчанию ClassWizard предлагает базовый класс для диалога CDialog. Наши же окна наследованы от базового класса CFormView.
Далее, необходимо создать обработчики командных сообщений для каждого типа документа. На закладке ClassView выбираем класс CTestApp, нажимаем правую кнопку мыши, из контекстного меню выбираем пункт Add Windows Message Handler…. В окошке Class or object to handle находим идентификатор ID_OPEN_TEST и нажимаем кнопку Add and Edit . Сообщение Command будет добавлено к классу CTestApp и редактор кода предоставит нам возможность ввести код для этого командного сообщения. Это будет единственная функция OpenNewDoc. В качестве параметра этой функции передается подстрока типа документа из каждого шаблона:
void CTestApp::OnOpenTest()
{
OpenNewDoc("TestOpen");
}
void CTestApp::OnOpenData()
{
OpenNewDoc("DataOpen");
}
MDI-приложение поддерживает множественные шаблоны документов. Для этого выполняется вызов AddDocTemplate для каждого из шаблонов:
CMultiDocTemplate* pDocTemplateTest;
pDocTemplateTest = new CMultiDocTemplate(
IDR_TESTOPEN,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CChildFrame), // класс окна-рамки
RUNTIME_CLASS(CTestOpen)); // класс дочернего окна
AddDocTemplate(pDocTemplateTest);
CMultiDocTemplate* pDocTemplateData;
pDocTemplateData = new CMultiDocTemplate(
IDR_DATAOPEN,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CChildFrame), // класс окна-рамки
RUNTIME_CLASS(CDataOpen)); // класс дочернего окна
AddDocTemplate(pDocTemplateData);
При вызове команды New из меню File каркас приложений выводит на экран список шаблонов, предоставляя возможность выбрать необходимый шаблон по имени, заданному в строковом ресурсе (подстрока типа документа). При выполнении программы объект-документ хранит список объектов (активных шаблонов документов). Перебирать этот список позволяют функции-члены GetFirstDocTemplatePosition и GetNextDocTemplate класса CWinApp:
BOOL CTestApp::OpenNewDoc(const CString& strTarget)
{
CString strDocName;
CDocTemplate* pSelectedTemplate;
POSITION pos = GetFirstDocTemplatePosition();
while (pos != NULL)
{
pSelectedTemplate = (CDocTemplate*) GetNextDocTemplate(pos);
pSelectedTemplate->GetDocString(strDocName, CDocTemplate::docName);
if (strDocName == strTarget) // выбирается из строкового ресурса шаблона
{
pSelectedTemplate->OpenDocumentFile(NULL);
return TRUE;
}
}
return FALSE;
}
Загрузка и сохранение документов м MDI-приложении осуществляется по аналогии с SDI, но с двумя отличиями: когда документ загружается, с диска создается новый объект-документ, а при закрытии последнего дочернего окна отображения документа – разрушается.
Запустив приложение, мы увидим два активних подпункта меню из меню File. Выбрав их, загрузятся два документа (дочерних окна) со своим набором элементов управления и отображения.
В заключение стоит отметить, что создание MDI-приложений не составляет особого труда. А в результате получаем удобное в использовании приложение, имеющее современный внешний вид и соответствующее установленным мировым стандартам. Успехов вам в программировании!
Дополнительная информация
За дополнительной информацией обращайтесь в компанию Interface Ltd.
INTERFACE Ltd. |
|