Приложение с однооконным интерфейсом не всегда может полностью удовлетворять потребностям пользователя. Если нужно одновременно работать с несколькими документами, вы, конечно, можете одновременно запустить несколько копий одного приложения, но гораздо удобнее использовать приложения с многооконным интерфейсом.
В таких приложениях вы одновременно можете открыть несколько документов. Каждому документу будет отведено собственное окно просмотра, но тем не менее все окна просмотра документов будут расположены внутри главного окна приложения, будут иметь общее меню, а также панели управления и состояния.
В этом разделе мы рассмотрим приложение Multi с многооконным интерфейсом, созданное с использованием средств MFC AppWizard. Сначала мы создадим простое приложение, а затем покажем, как его можно изменить, добавив возможности создания новых документов, рисования в окне и сохранения изменений в файле на диске.
Далее мы усовершенствуем наше приложение так, что оно сможет работать с документами двух типов - графическими и текстовыми.
В ходе своих объяснений мы иногда будем ссылаться на однооконное приложение Single, также созданное с использованием средств MFC AppWizard. Приложение Single мы рассматривали в 24 томе “Библиотеки системного программиста”. Если у вас нет под рукой 24 тома, вы можете быстро создать приложение Single, воспользовавшись MFC AppWizard.
Создайте новое приложение с многооконным интерфейсом и назовите его Multi. При определении свойств приложения оставьте все предложения по умолчанию. Приложение Multi не будет использовать технологию OLE и сетевые технологии, не будет работать с базами данных. Процедура создания приложений с использованием MFC AppWizard описана в разделе “Приложение с оконным интерфейсом” 24 тома серии “Библиотека системного программиста”, поэтому мы будем считать, что вы уже создали проект.
Постройте проект и запустите полученное приложение. На экране появится главное окно. Внутри главного окна расположены меню, панель управления и панель состояния.
Сразу после запуска приложения Multi, открывается дочернее окно, предназначенное для просмотра документа, которое получает название Multi1. Вы можете создать новые дочерние окна, выбрав из меню File строку New - открыть новый документ или строку Open - открыть файл (рис. 1.1). Для просмотра уже открытого документа можно открыть еще одно окно (рис. 1.11). В названии такого окна указывается дополнительный числовой индекс.
Если одновременно открыто несколько окон, то можно упорядочить расположение этих окон и пиктограмм, представляющих минимизированные окна. Для этого специально предназначено меню Window.
Рис. 1.1. Приложение Multi
Теперь рассмотрим внимательно сам проект Multi, подготовленный для нас MFC AppWizard. Найдите окно Project Workspace и откройте страницу FileView. Вы увидите список всех исходных файлов, входящих в проект (рис. 1.2). В отдельную папку Dependencies будут помещены названия вспомогательных файлов проекта. Эти файлы не входят в проект непосредственно, но используются либо для хранения ресурсов, либо как включаемые файлы, указанные директивой #include в одном или нескольких основных файлах проекта.
Рис. 1.2. Окно Project Workspace, файлы проекта
В следующей таблице кратко описаны основные файлы проекта Multi. Ниже мы подробно рассмотрим ресурсы приложения Multi, а также опишем составляющие его классы и их методы.
Имя файла |
Описание |
ChildFrm.cpp |
Файле содержит определение методов класса CChildFrame |
ChildFrm.h |
В файле находится определение класса дочернего окна MDI - CChildFrame |
MainFrm.cpp |
Файл содержит определения методов класса CMainFrame |
MainFrm.h |
Содержит описание класса главного окна приложения, который называется CMainFrame. Класс CMainFrame наследуется от базового класса CFrameWnd, определенного в библиотеке классов MFC |
Multi.cpp |
Основной файл приложения. В нем определены методы основного класса приложения CMultiApp |
Multi.h |
В этом файле перечислены другие включаемые файлы и описан главный класс приложения CMultiApp |
Multi.pch |
Файл создается во время первой трансляции программы, если вы используете предварительную компиляцию включаемых файлов |
Multi.rc |
Файл ресурсов. В этом файле описаны все ресурсы приложения. Сами ресурсы могут быть записаны в каталоге RES, расположенном в главном каталоге проекта |
MultiDoc.cpp |
Включает определение методов класса CMultiDoc |
MultiDoc.h |
Содержит определение класса документов приложения - CMultiDoc |
MultiView.cpp |
Включает определение методов класса CMultiView |
MultiView.h |
Содержит описание класса окна просмотра приложения - CMultiView |
ReadMe.txt |
Текстовый файл, содержащий описание проекта. В нем кратко рассмотрен каждый файл, входящий в проект, перечислены классы приложения, а также представлена некоторая другая дополнительная информация |
res\Multi.ico |
Пиктограмма приложения |
res\Multi.rc2 |
В этом файле определены ресурсы, которые нельзя редактировать с помощью редактора ресурсов среды Visual C++ |
res\MultiDoc.ico |
Пиктограмма для документов приложения |
res\Toolbar.bmp |
Файл содержит растровое изображение кнопок панели управления |
Resource.h |
Файл содержит определения идентификаторов ресурсов приложения, например, идентификаторы строк меню |
StdAfx.h, StdAfx.cpp |
Использование этих файлов позволяет ускорить процесс повторного построения проекта. Более подробное описание файлов представлено ниже |
Рассмотрим ресурсы, которые MFC AppWizard создал для нашего приложения. Откройте страницу RecourceView в окне проекта Project Workspace. В нем отображается полный список всех ресурсов приложения (рис. 1.3).
Рис. 1.3. Окно Project Workspace, ресурсы приложения
Сравните эти ресурсы с ресурсами приложения с однооконным интерфейсом (том 24 серии “Библиотека системного программиста”). Вы заметите, что в состав приложения с многооконным интерфейсом входит больше ресурсов. Так, например, для многооконного приложения определены два меню, две пиктограммы, больше размер таблицы текстовых строк.
Обратите внимание, что все ресурсы, представленные на странице RecourceView в окне проекта Project Workspace, английские. К сожалению, MFC AppWizard из доступных нам версий Microsoft Visual C++ не позволяет выбрать для создаваемого приложения русские ресурсы (язык для ресурсов выбирается в первой панели MFC AppWizard - Step 1, во время определения свойств приложения). Поэтому для приложения Multi и всех других приложений, созданных с помощью MFC AppWizard, мы выбрали английский язык.
Если вы в Control Panel с помощью приложения Regional Settings выбрали русский язык, то в некоторых случаях ClassWizard может работать неправильно. Например, если вы добавите к английской диалоговой панели новые органы управления, то ClassWizard не позволит автоматически привязать к ним переменные. Возникнут также сложности при использовании русского текста в строковых ресурсах, помеченных как английские. Чтобы избежать этих проблем, измените язык, используемый для ресурсов. Для этого достаточно в окне Project Workspace щелкнуть по идентификатору ресурса правой кнопкой мыши и выбрать из открывшегося контекстного меню строку Properties. На экране появится диалоговая панель со свойствами выбранного ресурса. Измените в ней язык ресурса, выбрав из списка Language строку Russian.
Для многооконного приложения в ресурсах проекта определены два меню с идентификаторами IDR_MAINFRAME и IDR_MULTITYPE. Приложение использует одно из этих меню, в зависимости от того, открыт документ или нет.
Меню, с идентификатором IDR_MAINFRAME используется, если в приложении не открыт ни один документ. Как видите, идентификатор меню совпадает с идентификатором меню приложения с однооконным интерфейсом, однако строки этих меню различаются:
//////////////////////////////////////////////////////////////// Меню IDR_MAINFRAME IDR_MAINFRAME MENU PRELOAD DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New\tCtrl+N", ID_FILE_NEW MENUITEM "&Open...\tCtrl+O", ID_FILE_OPEN MENUITEM SEPARATOR MENUITEM "P&rint Setup...", ID_FILE_PRINT_SETUP MENUITEM SEPARATOR MENUITEM "Recent File", ID_FILE_MRU_FILE1, GRAYED MENUITEM SEPARATOR MENUITEM "E&xit", ID_APP_EXIT END POPUP "&View" BEGIN MENUITEM "&Toolbar", ID_VIEW_TOOLBAR MENUITEM "&Status Bar", ID_VIEW_STATUS_BAR END POPUP "&Help" BEGIN MENUITEM "&About Multi...", ID_APP_ABOUT END END
Меню, имеющее идентификатор IDR_MULTITYPE, отображается, когда пользователь создает новый документ или открывает документ, уже записанный в файле на диске.
Как видите, в этом меню определено временное меню Window, строки которого служат для управления MDI окнами приложения:
////////////////////////////////////////////////////////////// // Меню IDR_MULTITYPE IDR_MULTITYPE MENU PRELOAD DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New\tCtrl+N", ID_FILE_NEW MENUITEM "&Open...\tCtrl+O", ID_FILE_OPEN MENUITEM "&Close", ID_FILE_CLOSE MENUITEM "&Save\tCtrl+S", ID_FILE_SAVE MENUITEM "Save &As...", ID_FILE_SAVE_AS MENUITEM SEPARATOR MENUITEM "&Print...\tCtrl+P", ID_FILE_PRINT MENUITEM "Print Pre&view", ID_FILE_PRINT_PREVIEW MENUITEM "P&rint Setup...", ID_FILE_PRINT_SETUP MENUITEM SEPARATOR MENUITEM "Recent File", ID_FILE_MRU_FILE1,GRAYED MENUITEM SEPARATOR MENUITEM "E&xit", ID_APP_EXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", ID_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "Cu&t\tCtrl+X", ID_EDIT_CUT MENUITEM "&Copy\tCtrl+C", ID_EDIT_COPY MENUITEM "&Paste\tCtrl+V", ID_EDIT_PASTE END POPUP "&View" BEGIN MENUITEM "&Toolbar", ID_VIEW_TOOLBAR MENUITEM "&Status Bar", ID_VIEW_STATUS_BAR END POPUP "&Window" BEGIN MENUITEM "&New Window", ID_WINDOW_NEW MENUITEM "&Cascade", ID_WINDOW_CASCADE MENUITEM "&Tile", ID_WINDOW_TILE_HORZ MENUITEM "&Arrange Icons", ID_WINDOW_ARRANGE END POPUP "&Help" BEGIN MENUITEM "&About Multi...", ID_APP_ABOUT END END
Большинство строк меню приложения Multi имеет стандартные идентификаторы. Соответствующие им стандартные командные сообщения обрабатываются различными классами библиотеки MFC. Так, например, стандартное командное сообщение с идентификатором ID_FILE_NEW от строки New меню File, по умолчанию обрабатывается методом OnFileNew класса CWinApp. Мы уже рассказывали о стандартных командных сообщениях в 24 томе из серии “Библиотека системного программиста”. Более подробное описание стандартных командных сообщений вы можете найти в документации Microsoft Visual C++.
В этой книге мы посвятили меню приложений отдельную главу, которая называется “Меню, панели управления и панели состояния”. Дополнительную информацию об использовании меню вы можете получить в 13 томе серии “Библиотека системного программиста”.
В файле ресурсов приложения Multi определены две пиктограммы IDR_MULTITYPE и IDR_MAINFRAME. Каждая из этих пиктограмм содержит по два изображения размером 32х32 и 16х16 пикселов. Внешний вид пиктограмм соответствует пиктограммам, используемым приложением с однооконным интерфейсом. Для однооконного приложения пиктограмма, представляющая документ, называлась IDR_SINGLETYPE, а не IDR_MULTITYPE. Такая разница в названиях возникла исключительно из-за разницы в названиях проектов приложений:
////////////////////////////////////////////////////////////// // Пиктограммы IDR_MAINFRAME ICON DISCARDABLE "res\\Multi.ico" IDR_MULTITYPE ICON DISCARDABLE "res\\MultiDoc.ico"
Пиктограмма IDR_MAINFRAME представляет приложение, когда оно минимизировано (рис. 1.4). Эта же пиктограмма отображается в левом верхнем углу главного окна приложения.
Рис. 1.4. Пиктограмма IDR_MAINFRAME
Пиктограмма IDR_MULTITYPE используется для представления документа с которым работает приложение (рис. 1.5). В отличие от приложения с однооконным интерфейсом, которое не использует эту пиктограмму, приложение с многооконным интерфейсом отображает пиктограмму IDR_MULTITYPE в левом верхнем углу окна документа.
Рис. 1.5. Пиктограмма IDR_MULTITYPE
Не смотря на то, что приложение имеет два меню, для него определена только одна панель управления IDR_MAINFRAME. Идентификаторы первых трех и последних двух кнопок этой панели соответствуют идентификаторам строк и меню IDR_MAINFRAME и меню IDR_MULTITYPE. А вот вторые три идентификатора имеют соответствие только в меню IDR_MULTITYPE. Пока ни один документ не открыт и отображается меню IDR_MAINFRAME, эти кнопки недоступны и отображаются серым цветом:
////////////////////////////////////////////////////////////// // Панель управления Toolbar IDR_MAINFRAME TOOLBAR DISCARDABLE 16, 15 BEGIN BUTTON ID_FILE_NEW BUTTON ID_FILE_OPEN BUTTON ID_FILE_SAVE SEPARATOR BUTTON ID_EDIT_CUT BUTTON ID_EDIT_COPY BUTTON ID_EDIT_PASTE SEPARATOR BUTTON ID_FILE_PRINT BUTTON ID_APP_ABOUT END
Образ кнопок панели управления расположен в файле Toolbar.bmp, записанном в подкаталоге res каталога проекта (рис. 1.6):
////////////////////////////////////////////////////////////// // Изображение Bitmap, определяющее кнопки приложения IDR_MAINFRAME BITMAP MOVEABLE PURE "res\\Toolbar.bmp"
Панелям управления мы уделили в этой книге отдельную главу, которая имеет название “Меню, панели управления и панели состояния”. В ней описаны принципы устройства и работы панелей управления, приведены простые примеры создания дополнительных панелей управления, в том числе панелей управления на основе шаблонов диалоговых панелей.
Рис. 1.6. Панель управления
Таблица акселераторов IDR_MAINFRAME приложения полностью соответствует таблице акселераторов, добавленной MFC AppWizard к ресурсам приложения с однооконным интерфейсом:
////////////////////////////////////////////////////////////// // Таблица акселераторов IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE PURE BEGIN "N", ID_FILE_NEW, VIRTKEY,CONTROL "O", ID_FILE_OPEN, VIRTKEY,CONTROL "S", ID_FILE_SAVE, VIRTKEY,CONTROL "P", ID_FILE_PRINT, VIRTKEY,CONTROL "Z", ID_EDIT_UNDO, VIRTKEY,CONTROL "X", ID_EDIT_CUT, VIRTKEY,CONTROL "C", ID_EDIT_COPY, VIRTKEY,CONTROL "V", ID_EDIT_PASTE, VIRTKEY,CONTROL VK_BACK, ID_EDIT_UNDO, VIRTKEY,ALT VK_DELETE, ID_EDIT_CUT, VIRTKEY,SHIFT VK_INSERT, ID_EDIT_COPY, VIRTKEY,CONTROL VK_INSERT, ID_EDIT_PASTE, VIRTKEY,SHIFT VK_F6, ID_NEXT_PANE, VIRTKEY VK_F6, ID_PREV_PANE, VIRTKEY,SHIFT END
Мы расскажем вам подробнее о таблице акселераторов в разделе “Таблица акселераторов”. Дополнительную информацию вы сможете получить в 13 томе серии “Библиотека системного программиста”.
В ресурсах приложения определена диалоговая панель с идентификатором IDD_ABOUTBOX. Она содержит краткую информацию о приложении и отображается на экране, когда пользователь выбирает из меню Help строку About Multi:
////////////////////////////////////////////////////////////// // Диалоговая панель IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 217, 55 CAPTION "About Multi" STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20 LTEXT "Multi Version 1.0",IDC_STATIC,40,10,119,8, SS_NOPREFIX LTEXT "Copyright \251 1996",IDC_STATIC,40,25,119,8 DEFPUSHBUTTON "OK",IDOK,178,7,32,14,WS_GROUP END
Приложение Multi включает ресурс, описывающий версию приложения. В этом ресурсе содержится информация о приложении и ее версии, данные о фирме-разработчике, авторские права:
////////////////////////////////////////////////////////////// // Версия VS_VERSION_INFO VERSIONINFO FILEVERSION 1,0,0,1 RODUCTVERSION 1,0,0,1 ILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "CompanyName", "Solaris\0" VALUE "FileDescription", "MULTI MFC Application\0" VALUE "FileVersion", "1, 0, 0, 1\0" VALUE "InternalName", "MULTI\0" VALUE "LegalCopyright", "Copyright © 1996 Frolov G.V.\0" VALUE "OriginalFilename", "MULTI.EXE\0" VALUE "ProductName", "MULTI Application\0" VALUE "ProductVersion", "1, 0, 0, 1\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END
Ресурсы приложения содержат несколько блоков, описывающих таблицы текстовых строк. Эти таблицы практически совпадают с таблицами текстовых строк, включенных MFC AppWizard в ресурсы приложения с однооконным интерфейсом.
Блоки текстовых строк, описывающие тип документов приложения, и основные характеристики главного окна приложения совпадают с соответствующими блоками однооконного приложения, за исключением строки с названием проекта:
////////////////////////////////////////////////////////////// // Таблица текстовых строк STRINGTABLE PRELOAD DISCARDABLE BEGIN IDR_MAINFRAME "Multi" IDR_MULTITYPE "\nMulti\nMulti\n\n\nMulti.Document\nMulti Document" END STRINGTABLE PRELOAD DISCARDABLE BEGIN AFX_IDS_APP_TITLE "Multi" AFX_IDS_IDLEMESSAGE "Ready" END
Блок текстовых строк, которые используются в панели состояния ststus bar, полностью совпадает с аналогичным блоком в ресурсах однооконного приложения:
STRINGTABLE DISCARDABLE BEGIN ID_INDICATOR_EXT "EXT" ID_INDICATOR_CAPS "CAP" ID_INDICATOR_NUM "NUM" ID_INDICATOR_SCRL "SCRL" ID_INDICATOR_OVR "OVR" ID_INDICATOR_REC "REC" END
В блоке текстовых строк, описывающих элементы меню, добавлен ряд текстовых строк, которые относятся к меню Window. Для однооконного приложения эти строки не определены, так как меню Window есть только у многооконных приложений:
STRINGTABLE DISCARDABLE BEGIN ID_FILE_NEW "Create a new document\nNew ID_FILE_OPEN "Open an existing document\nOpen" ... ID_PREV_PANE "Switch back to the previous window pane\nPrevious Pane" ID_WINDOW_NEW "Open another window for the active document\nNew Window" ID_WINDOW_ARRANGE "Arrange icons at the bottom of the window\nArrange Icons" ID_WINDOW_CASCADE "Arrange windows so they overlap\nCascade Windows" ID_WINDOW_TILE_HORZ "Arrange windows as non-overlapping tiles\nTile Windows" ID_WINDOW_TILE_VERT "Arrange windows as non-overlapping tiles\nTile Windows" ID_WINDOW_SPLIT "Split the active window into panes\nSplit" ... ID_EDIT_CLEAR "Erase the selection\nErase" ID_VIEW_TOOLBAR "Show or hide the toolbar\nToggle ToolBar" ID_VIEW_STATUS_BAR "Show or hide the status bar\nToggle StatusBar" END
По сравнению с ресурсами однооконного приложения, для приложений с многооконным пользовательским интерфейсом, добавлен еще один блок текстовых строк. В нем содержатся строки, имеющие отношение к многооконному интерфейсу приложения:
STRINGTABLE DISCARDABLE BEGIN AFX_IDS_SCSIZE "Change the window size" AFX_IDS_SCMOVE "Change the window position" AFX_IDS_SCMINIMIZE "Reduce the window to an icon" AFX_IDS_SCMAXIMIZE "Enlarge the window to full size" AFX_IDS_SCNEXTWINDOW "Switch to the next document window" AFX_IDS_SCPREVWINDOW "Switch to the previous document window" AFX_IDS_SCCLOSE "Close the active window and prompts to save the documents" AFX_IDS_SCRESTORE "Restore the window to normal size" AFX_IDS_SCTASKLIST "Activate Task List" AFX_IDS_MDICHILD "Activate this window" AFX_IDS_PREVIEW_CLOSE "Close print preview mode\nCancel Preview" END
MFC AppWizard создает для приложения Multi, обладающего многооконным интерфейсом, шесть основных классов, что на один класс больше, чем для однооконного приложения. Пять классов из шести представляют основу любого многооконного приложения, созданного MFC AppWizard. Шестой класс управляет информационной диалоговой панелью About.
Список названий классов, а также входящие в них методы и элементы данных можно просмотреть на странице ClassView окна Project Workspace (рис. 1.7). В отдельной папке Globals представлены глобальные объекты и переменные приложения. Приложение Multi имеет только один глобальный объект theApp. Это объект главного класса приложения.
Рис. 1.7. Окно Project Workspace, классы приложения
В следующей таблице кратко описано назначение отдельных классов приложения Multi. Более подробный рассказ об этих классах и их методах расположен ниже.
"Класс приложения |
"Базовый класс |
"Описание |
"CMultiApp |
"CWinApp |
"Главный класс приложения |
"CMainFrame |
"CMDIFrameWnd |
"Класс главного окна приложения |
"CChildFrame |
"CMDIChildWnd |
"Класс дочернего окна MDI |
"CMultiDoc |
"CDocument |
"Класс документа приложения |
"CMultiView |
"CView |
"Класс окна просмотра документа |
Кроме пяти основных классов создается также класс CAboutDlg, наследованный от базового класса CDialog XE "CDialog" . Он отвечает за диалоговую панель About. Если во время определения характеристик приложения вы включите возможность работы с базами данных, работу с сетевыми протоколами или использование технологии OLE, список классов приложения может стать значительно шире.
Главный класс приложения CMultiApp управляет работой всего приложения. Методы этого класса выполняют инициализацию приложения, обработку цикла сообщений и вызываются при завершении приложения. Через окно Project Workspace можно просмотреть названия методов класса и загрузить их в текстовый редактор (рис. 1.8).
Рис. 1.8. Окно Project Workspace, класс CMultiApp
Класс CMultiApp определен в файле Multi.h следующим образом:
//////////////////////////////////////////////////////////////// Класс CMultiApp class CMultiApp : public CWinApp { public: CMultiApp(); // Overrides //{{AFX_VIRTUAL(CMultiApp) public: virtual BOOL InitInstance(); //}}AFX_VIRTUAL // Implementation //{{AFX_MSG(CMultiApp) afx_msg void OnAppAbout(); //}}AFX_MSG // Класс CMultiApp может получать сообщения DECLARE_MESSAGE_MAP() };
В приложении определен только один объект базового класса приложения theApp. Этот объект должен быть один вне зависимости от того, какой интерфейс имеет приложение - однооконный, многооконный или основанный на диалоговой панели:
CMultiApp theApp;
Конструктор класса, созданный MFC AppWizard, не выполняет никаких действий. В нем вы можете разместить код для инициализации объекта CMultiApp:
////////////////////////////////////////////////////////////// // Конструктор класса CMultiApp CMultiApp::CMultiApp() { // TODO: }
Основную работу по инициализации приложения выполняет метод InitInstance главного класса приложения, определенный в файле Multi.cpp. Как видите, он отличается от метода InitInstance, который используется для однооконных приложений:
////////////////////////////////////////////////////////////// // Метод InitInstance BOOL CMultiApp::InitInstance() { #ifdef _AFXDLL Enable3dControls(); #else Enable3dControlsStatic(); #endif // Загружаем файл конфигурации LoadStdProfileSettings(); // Создаем шаблон документа CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_MULTITYPE, RUNTIME_CLASS(CMultiDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CMultiView)); // Регистрируем шаблон документа AddDocTemplate(pDocTemplate); // Создаем главное окно приложения (MDI Frame window) CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd = pMainFrame; // Выполняем стандартную обработку командной строки // приложения CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // Обрабатываем командную строку приложения if (!ProcessShellCommand(cmdInfo)) return FALSE; // Отображаем окно pMainFrame->ShowWindow(m_nCmdShow); pMainFrame->UpdateWindow(); return TRUE; }
В начале InitInstance вызываются методы Enable3dControls и LoadStdProfileSettings. Они уже были описаны в предыдущем томе серии “Библиотека системного программиста”, посвященном MFC, поэтому мы не станем на них останавливаться и перейдем к рассмотрению шаблонов документа приложения.
Затем создается указатель pDocTemplate на объекты класса шаблона документов. Для однооконных приложений это класс CSingleDocTemplate, а для многооконных - CMultiDocTemplate. Создается новый объект класса и указатель на него записывается в переменную pDocTemplate. Для создания шаблона документа используется оператор new.
Конструктору класса CMultiDocTemplate передаются четыре параметра:
CMultiDocTemplate( UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );
Первый параметр nIDResource определяет идентификатор ресурсов, используемых совместно с типом документов, управляемых шаблоном. К таким ресурсам относятся меню, пиктограмма, строковый ресурс, таблица акселераторов. Для приложения Multi в этом параметре указан идентификатор IDR_MULTITYPE.
Остальные три параметра pDocClass, pFrameClass и pViewClass содержат указатели на объекты класса CRuntimeClass, полученные с помощью макрокоманд RUNTIME_CLASS из классов документа CMultiDoc, дочернего окна MDI CChildFrame и окна просмотра CMultiView. Таким образом, шаблон документа объединяет всю информацию, относящуюся к данному типу документов.
Созданный шаблон документов заносится в список шаблонов, с которыми работает приложение. Для этого указатель на созданный шаблон документа передается методу AddDocTemplate из класса CWinApp. Указатель на шаблон документов передается через параметр pTemplate:
void AddDocTemplate(CDocTemplate* pTemplate);
Указатель pTemplate должен указывать на объект класса CDocTemplate, однако мы передаем через него указатель на объект класса CMultiDocTemplate. Это допустимо, так как класс CDocTemplate является базовым классом для CMultiDocTemplate.
Если вы разрабатываете приложение, основанное на однооконном или многооконном интерфейсе, объект главного класса приложения управляет одним или несколькими объектами класса шаблона документа. Они, в свою очередь, управляют созданием документов. Один шаблон используется для всех документов данного типа.
После создания шаблона документа создается главное окно MDI (главное окно приложения).
Для создания главного окна приложения мы формируем объект класса CMainFrame и записываем указатель на него в pMainFrame. Класс CMainFrame определен в нашем приложении. Мы расскажем о нем немного позже:
// Создаем главное окно MDI (главное окно приложения) CMainFrame* pMainFrame = new CMainFrame;
Затем для только что созданного объекта вызывается метод LoadFrame класса CFrameWnd. Он создает окно, загружает ресурсы, указанные первым параметром, и связывает их с объектом класса CMainFrame. Параметр метода LoadFrame определяет меню, пиктограмму, таблицу акселераторов и таблицу строк главного окна приложения:
if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE;
Указатель на главное окно приложения, которым является главное окно MDI, записывается в элемент данных m_pMainWnd главного класса приложения. Элемент данных m_pMainWnd, определенн в классе CWinThread. Когда окно, представленное указателем m_pMainWnd закрывается, приложение автоматически будет завершено (в случае если приложение включает в себя несколько задач, завершается только соответствующая задача):
m_pMainWnd = pMainFrame;
Метод LoadFrame не отображает главное окно приложения на экране. Для этого надо вызвать методы ShowWindow и UpdateWindow:
// Отображаем главное окно приложения pMainFrame->ShowWindow(m_nCmdShow); // Обновляем содержимое окна pMainFrame->UpdateWindow();
В заключение метода InitInstance обрабатывается командная строка приложения. Для этого создается объект cmdInfo класса CCommandLineInfo и для него вызываются методы ParseCommandLine и ProcessShellCommand:
// Просматриваем командную строку приложения в поиске // стандартных команд и обрабатываем их CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // Распределяем команды, указанные в командной строке // приложения if (!ProcessShellCommand(cmdInfo)) return FALSE;
Класс CMultiApp может получать сообщения и имеет таблицу сообщений. Таблицу сообщений класса CMultiApp расположена в файле Multi.cpp. Она содержит четыре макрокоманды для обработки командных сообщений от меню приложения:
////////////////////////////////////////////////////////////// // Таблица сообщений класса CMultiApp BEGIN_MESSAGE_MAP(CMultiApp, CWinApp) //{{AFX_MSG_MAP(CMultiApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout) //}}AFX_MSG_MAP ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup) END_MESSAGE_MAP()
Только для одного командного сообщения, имеющего идентификатор ID_APP_ABOUT XE "ID_APP_ABOUT" , вызывается метод обработчик OnAppAbout, определенный в классе CMultiApp. Остальные три командных сообщения ID_FILE_NEW XE "ID_FILE_NEW" , ID_FILE_OPEN XE "ID_FILE_OPEN" и ID_FILE_PRINT_SETUP XE "ID_FILE_PRINT_SETUP" передаются для обработки методам класса CWinApp, который является базовым классом для CMultiApp.
Метод-обработчик OnAppAbout вызывается объектом главного класса приложения, когда пользователь выбирает из меню Help строку About. OnAppAbout создает объект класса CAboutDlg, представляющий модальную диалоговую панель About и вызывает для него метод DoModal XE "CDialog:DoModal" , отображающий панель на экране (рис. 1.9):
void CMultiApp::OnAppAbout() { CAboutDlg aboutDlg; aboutDlg.DoModal(); }
Рис. 1.9. Окно Project Workspace, класс CMainFrame
Внутри главного окна приложения отображаются панели управления и состояния, дочерние MDI окна, используемые для просмотра документов. Для управления главным окном приложения используется класс CMainFrame, определенный в файле MainFrm.h.
Вы можете изучить класс CMDIFrameWnd, просмотрев его структуру в окне Project Workspace, на странице ClassView (рис. 1.10). Выполните двойной щелчок левой кнопкой мыши по названию класса или по названию интересующего вас метода, и соответствующий программный код загрузится в окно редактора Microsoft Visual C++.
Рис. 1.10. Окно Project Workspace, класс CMainFrame
Ниже мы привели определение класса CMainFrame:
class CMainFrame : public CMDIFrameWnd { DECLARE_DYNAMIC(CMainFrame) public: CMainFrame(); // Attributes public: // Operations public: // Overrides //{{AFX_VIRTUAL(CMainFrame) virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //}}AFX_VIRTUAL // Implementation public: virtual ~CMainFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; protected: //{{AFX_MSG(CMainFrame) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Класс главного окна многооконного приложения CMainFrame практически полностью соответствует классу главного окна однооконного приложения. Даже названия этих классов одинаковы. Однако обратите внимание, что класс CMainFrame наследуется от базового класса CMDIFrameWnd XE "CMDIFrameWnd" , а не от CFrameWnd XE "CFrameWnd" , как это было для однооконного приложения.
Ниже представлены конструктор и деструктор класса CMainFrame. Изначально они не содержат программного кода и представляют собой простые заготовки. Вы можете использовать их для дополнительной инициализации объекта класса:
// Конструктор класса CMainFrame CMainFrame::CMainFrame() { // TODO: } // Деструктор класса CMainFrame CMainFrame::~CMainFrame() { }
Таблица сообщений класса CMainFrame содержит только одну макрокоманду ON_WM_CREATE XE "ON_WM_CREATE" , которая устанавливает для обработки сообщения WM_CREATE XE "WM_CREATE" метод OnCreate XE "CWnd:OnCreate" . Сообщения WM_CREATE приходит во время создания главного окна приложения.
Непосредственно перед таблицей сообщений класса CMainFrame располагается макрокоманда IMPLEMENT_DYNAMIC XE "IMPLEMENT_DYNAMIC" . Она указывает, что объекты класса CMainFrame могут создаваться динамически во время работы приложения:
// Объекты класса CMainFrame могут создаваться автоматически IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd) // Таблица сообщений BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() //}}AFX_MSG_MAP END_MESSAGE_MAP()
Метод OnCreate класса CMainFrame создает и отображает на экране панели управления и состояния:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndToolBar.Create(this) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { // Ошибка создания панели управления TRACE0("Failed to create toolbar\n"); return -1; } if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { // Ошибка создания панели состояния TRACE0("Failed to create status bar\n"); return -1; } m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC); m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); return 0; }
Структура indicators, описывающая индикаторы панели состояния, определена в файле MainFrm.h следующим образом:
static UINT indicators[] = { ID_SEPARATOR, ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, };
Сейчас мы не станем подробно останавливаться на процедуре создания панелей состояния и управления. Во первых, в 24 томе мы уже рассматривали метод OnCreate однооконного приложения Single. Он фактически полностью повторяет метод OnCreate приложения Multi. Во вторых мы посвятили проблеме использования меню, панелей состояния и панелей управления отдельный раздел “Меню, панели управления и панели состояния”. Прочитав его, вы полностью поймете как устроен метод OnCreate класса CMainFrame.
Метод PreCreateWindow вызывается перед созданием окна и позволяет изменить его характеристики. В нашем приложении метод PreCreateWindow не используется и просто выполняет обрработку по умолчанию:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { // TODO: return CMDIFrameWnd::PreCreateWindow(cs); }
В отладочной версии приложения класс CMainFrame содержит переопределения виртуальных методов AssertValid и Dump. Эти методы определены в базовом классе CObject и используются при отладке приложения:
////////////////////////////////////////////////////////////// // Диагностические методы класса CMainFrame #ifdef _DEBUG void CMainFrame::AssertValid() const { CMDIFrameWnd::AssertValid(); } void CMainFrame::Dump(CDumpContext& dc) const { CMDIFrameWnd::Dump(dc); }
Многооконное приложение строится с использованием большего числа классов, чем однооконное приложение. Помимо классов главного окна приложения и классов окна просмотра документа, в нем определен еще один класс, непосредственно связанный с отображением дочерних окон MDI. Этот класс называется CChildFrame XE "CChildFrame" и он наследуется от базового класса CMDIChildWnd XE "CMDIChildWnd" , определенного в библиотеке MFC:
class CChildFrame : public CMDIChildWnd { DECLARE_DYNCREATE(CChildFrame) public: CChildFrame(); // Attributes public: // Operations public: // Overrides //{{AFX_VIRTUAL(CChildFrame) virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //}}AFX_VIRTUAL // Implementation public: virtual ~CChildFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: //{{AFX_MSG(CChildFrame) //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Элементы класса CChildFrame вы можете просмотреть в окне Project Workspace на странице ClassView (рис. 1.11).
Рис. 1.11. Окно Project Workspace, класс CChildFrame
Объекты класса CChildFrame представляют дочерние окна MDI главного окна приложения. Внутри этих окон отображаются окна просмотра документа.
MFC AppWizard определяет для класса CChildFrame конструктор и деструктор. По умолчанию они не выполняют никаких действий. Вы можете изменить их для выполнения инициализации объектов класса дочернего окна MDI:
////////////////////////////////////////////////////////////// // Конструктор и деструктор класса CChildFrame CChildFrame::CChildFrame() { // TODO: } CChildFrame::~CChildFrame() { }
Таблица сообщений класса CChildFrame не содержит обработчиков сообщений:
// Объекты класса CChildFrame создаются динамически IMPLEMENT_DYNCREATE(CChildFrame, CMDIChildWnd) // Таблица сообщений BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWnd) //{{AFX_MSG_MAP(CChildFrame) //}}AFX_MSG_MAP END_MESSAGE_MAP()
Метод PreCreateWindow вызывается перед созданием дочернего окна MDI. Вы можете использовать его, чтобы переопределить стили этого окна:
BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs) { // TODO: return CMDIChildWnd::PreCreateWindow(cs); }
Методы AssertValid и Dump переопределяются в классе CMainFrame только для отладочной версии приложения и используются при отладке приложения:
////////////////////////////////////////////////////////////// // Диагностические методы класса CChildFrame #ifdef _DEBUG void CChildFrame::AssertValid() const { CMDIChildWnd::AssertValid(); } void CChildFrame::Dump(CDumpContext& dc) const { CMDIChildWnd::Dump(dc); }
Класс документа приложения CMultiDoc наследуется от базового класса CDocument библиотеки MFC. Определение этого класса вы можете найти в файле MultiDoc.h. Мы привели структуру класса CMultiDoc на рисунке 1.12.
Рис. 1.12. Окно Project Workspace, класс CMultiDoc
MFC AppWizard определяет класс CMultiDoc одинаково для однооконных и для многооконных приложений. Единственное исключение составляет название класса документа, которое создается на основе имени проекта:
class CMultiDoc : public CDocument { protected: CMultiDoc(); DECLARE_DYNCREATE(CMultiDoc) // Attributes public: // Operations public: // Overrides //{{AFX_VIRTUAL(CMultiDoc) public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); //}}AFX_VIRTUAL // Implementation public: virtual ~CMultiDoc(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: protected: //{{AFX_MSG(CMultiDoc) //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Конструктор и деструктор класса CMultiDoc не содержит программного кода. Вы можете добавить его по мере необходимости:
CMultiDoc::CMultiDoc() { // TODO: } CMultiDoc::~CMultiDoc() { }
Таблица сообщений класса CMultiDoc не содержит ни одного обработчика сообщений:
// Объекты класса CMultiDoc могут создаваться динамически IMPLEMENT_DYNCREATE(CMultiDoc, CDocument) // Таблица сообщений класса CMultiDoc BEGIN_MESSAGE_MAP(CMultiDoc, CDocument) //{{AFX_MSG_MAP(CMultiDoc) //}}AFX_MSG_MAP END_MESSAGE_MAP()
В классе CMultiDoc переопределены два виртуальных метода - OnNewDocument и Serialize. Виртуальный метод OnNewDocument определен в классе CDocument, от которого непосредственно наследуется класс CSingleDoc.
Метод OnNewDocument вызывается, когда надо создать новый документ для приложения. Для одноконных приложений метод OnNewDocument вызывался только один раз при запуске приложения.
Для многооконного приложения метод OnNewDocument вызывается каждый раз, когда пользователь создает новый документ. Более подробно об использовании метода OnNewDocument мы расскажем в следующих главах, когда к шаблону приложения, созданному MFC AppWizard, мы будем добавлять собственный код:
BOOL CMultiDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // TODO: Здесь можно выполнить инициализацию документа return TRUE; }
Метод Serialize вызывается в тех случаях, когда надо загрузить документ из файла на диске или наоборот, записать его в файл:
////////////////////////////////////////////////////////////// // Метод Serialize класса CMultiDoc void CMultiDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: } else { // TODO: } }
Методы AssertValid и Dump переопределяются в классе CMainFrame только для отладочной версии приложения и используются при отладке приложения:
////////////////////////////////////////////////////////////// // Диагностические методы класса CMultiDoc #ifdef _DEBUG void CMultiDoc::AssertValid() const { CDocument::AssertValid(); } void CMultiDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); } #endif //_DEBUG
Класс окна просмотра документа, также как класс документа и главный класс приложения, имеют своего двойника в однооконном приложении. Так, в приложении Single определен класс окна просмотра CSingleView, совпадающий с классом CMultiView.
Рис. 1.13. Окно Project Workspace, класс CMultiView
Вы можете просмотреть список методов, входящих в класс CMultiView, если откроете в окне Project Workspace страницу ClassView (рис. 1.13). А сейчас приведем определение класса CMultiView XE "CView" :
class CMultiView : public CView { protected: CMultiView(); DECLARE_DYNCREATE(CMultiView) // Attributes public: CMultiDoc* GetDocument(); // Operations public: // Overrides //{{AFX_VIRTUAL(CMultiView) public: virtual void OnDraw(CDC* pDC); virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); //}}AFX_VIRTUAL // Implementation public: virtual ~CMultiView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: //{{AFX_MSG(CMultiView) //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Как видите, класс CMultiView наследуется от базового класса CView. Вы, однако, можете наследовать этот класс и от некоторых других классов библиотеки MFC.
В секции атрибутов класса CMultiView после комментария Attributes объявлен метод GetDocument. Этот метод возвращает указатель на документ, связанный с данным окном просмотра. Если окно просмотра не связано ни с каким документом, метод возвращает значение NULL.
Метод GetDocument имеет две реализации. Одна используется для отладочной версии приложения, а другая - для окончательной. Окончательная версия GetDocument определена непосредственно после самого класса окна просмотра CMultiView как встраиваемый (inline) метод:
#ifndef _DEBUG inline CMultiDoc* CMultiView::GetDocument() { return (CMultiDoc*) m_pDocument; } #endif
Переменная m_pDocument является элементом класса CView, определенным как protected. В документации на класс CView описание элемента m_pDocument отсутствует. Однако, вам достаточно знать, что после инициализации документа и окна просмотра в нем записан указатель на соответствующий документ. Если вы желаете получить дополнительную информацию, обратитесь к исходным текстам библиотеки MFC. Вы найдете определение класса CView и элемента этого класса m_pDocument в файле Afxwin.h.
Метод GetDocument совпадает с одноименным методом класса окна просмотра однооконного приложения за исключением того, что тип возвращаемого им указателя CMultiDoc.
Отладочная версия GetDocument расположена в файле реализации класса окна просмотра MultiView.cpp:
#ifdef _DEBUG CMultiDoc* CMultiView::GetDocument() { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMultiDoc))); return (CMultiDoc*) m_pDocument; } #endif //_DEBUG
Таблица сообщений класса CMultiView располагается в файле MultiView.cpp. Непосредственно перед ней расположена макрокоманда IMPLEMENT_DYNCREATE:
// Объекты класса CMultiView создаются динамически IMPLEMENT_DYNCREATE(CMultiView, CView) // Таблица сообщений класса CMultiView BEGIN_MESSAGE_MAP(CMultiView, CView) //{{AFX_MSG_MAP(CMultiView) //}}AFX_MSG_MAP ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP()
Конструктор и деструктор класса CMultiView не выполняют полезной работы. MFC AppWizard создает для них только пустые шаблоны, которые вы можете “наполнить” сами:
CMultiView::CMultiView() { // TODO: } CMultiView::~CMultiView() { }
Виртуальный метод PreCreateWindow определен в классе CWnd. Он вызывается непосредственно перед созданием окна, связанного с объектом класса. MFC AppWizard переопределяет этот метод следующим образом:
BOOL CMultiView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: return CView::PreCreateWindow(cs); }
Метод OnDraw первоначально определен в классе CView как виртуальный и вызывается, когда приложение должно перерисовать документ в окне просмотра. MFC AppWizard переопределяет для вас метод OnDraw класса CView следующим образом:
void CMultiView::OnDraw(CDC* pDC) { CMultiDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: }
Первые две строки метода OnDraw получают указатель pDoc на документ, связанный с данным окном просмотра.
Виртуальные методы OnPreparePrinting, OnBeginPrinting и OnEndPrinting, определенные в классе CView, вызываются, если пользователь желает распечатать документ, отображенный в данном окне просмотра:
////////////////////////////////////////////////////////////// // Методы класса CMultiView, управляющие печатью документов BOOL CMultiView::OnPreparePrinting(CPrintInfo* pInfo) { return DoPreparePrinting(pInfo); } void CMultiView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) { // TODO: } void CMultiView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) { // TODO: }
Многооконное приложение, подготовленное MFC AppWizard, уже “умеет” выводить созданные в нем документы на печатающее устройство. Методы OnPreparePrinting, OnBeginPrinting и OnEndPrinting класса CView предназначены для расширения возможностей печати и в этой книге не рассматриваются.
Методы AssertValid и Dump переопределяются в классе CMainFrame только для отладочной версии приложения и используются при отладке приложения:
#ifdef _DEBUG void CMultiView::AssertValid() const { CView::AssertValid(); } void CMultiView::Dump(CDumpContext& dc) const { CView::Dump(dc); } #endif //_DEBUG
Процесс обработки командных сообщений значительно отличается от обработки других сообщений. Обычные сообщения обрабатываются тем объектом, которому они поступили. Если таблица сообщений класса объекта не содержит обработчика сообщения, будут просмотрены таблицы сообщений его базовых классов. В том случае, если ни один из базовых классов также не содержит обработчик сообщения, выполняется обработка сообщения по умолчанию.
Судьба командных сообщений гораздо сложнее. Командное сообщение, переданное для обработки объекту приложения, может последовательно передаваться другим объектам приложения. Один из объектов, класс (или базовый класс) которого содержит обработчик этого сообщения, выполняет его обработку. Так, например, командное сообщение, переданное главному окну приложения, в конечном счете может быть обработано активным окном просмотра.
Существует стандартная последовательность объектов приложения, которым передаются командные сообщения. Каждый объект в этой последовательности может обработать командное сообщение, если в его таблице сообщений или таблице сообщений базовых классов есть соответствующая макрокоманда. Необработанные сообщения передаются дальше, другим объектам приложения.
Объекты различных классов обрабатывают командные сообщения по-разному. Например, объекты, представляющие главное окно приложения, сначала предоставляют возможность обработать полученное сообщение другим объектам, в том числе активному окну просмотра и соответствующему ему документу. Только если сообщение остается необработанным, просматривается таблица сообщений класса главного окна приложения. Если и здесь сообщение не обрабатывается, оно направляется другим объектам приложения.
Подавляющее большинство приложений, созданных на основе MFC, использует ряд стандартных командных сообщений, как правило соответствующих элементам меню или кнопкам панели управления. К ним относятся командные сообщения для завершения работы приложения, создания нового документа, открытия документа, записанного на диске, сохранения документа на диске, вызова справочной системы, управления текстовым редактором и т. д. За каждым таким командным сообщением зарезервирован отдельный идентификатор.
MFC обеспечивает различный уровень обработки стандартных командных сообщений, начиная от простого резервирования идентификатора и кончая полной его обработкой. Информацию об использовании стандартных командных сообщений вы можете получить в документации Microsoft Visual C++. Мы также рекомендуем вам изучить реализацию обработчиков стандартных командных сообщений непосредственно в исходных текстах библиотеки MFC.
В некоторых случаях вам может понадобиться изменить порядок, в котором сообщения передаются для обработки объектам приложения. В этом случае вы должны переопределить виртуальный методом OnCmdMsg. Этот метод первоначально определен в классе CCmdTarget и переопределен в классах CView и CDocument.
Ниже описаны последовательности обработки командных сообщений объектами различных классов.
Большинство командных сообщений передаются главному окну приложения. Если приложение имеет многооконный интерфейс, то главное окно приложения представляет объект класса CMDIFrameWnd или класса, наследованного от базового класса CMDIFrameWnd.
Получив сообщение, главное окно приложения сначала предоставляет возможность обработать сообщение активному дочернему окну MDI. Окна MDI представляют собой объекты класса CMDIChildWnd или класса наследованного от него.
Только если окно MDI не может обработать сообщение, будет просмотрена таблица сообщений класса главного окна приложения. Следует сразу заметить, что в свою очередь, окно MDI передает сообщения другим объектам (см. ниже).
Если главное окно приложения не может обработать сообщение, оно передается объекту главного класса приложения. Напомним, что главный класс приложения наследуется от базового класса CWinApp и приложение имеет только один объект этого класса.
Для приложений, имеющих однооконный интерфейс, роль главного окна приложения выполняет объект класса CFrameWnd или класса наследованного от базового класса CFrameWnd.
Главное окно однооконного приложения и дочерние MDI окна многооконного приложения обрабатывают командные сообщения одинаковым образом. Объект класса CFrameWnd или CMDIChildWnd, которому поступило командное сообщение, передает его соответствующему окну просмотра. Если окно просмотра не может обработать сообщение, проверяется таблица сообщений класса CFrameWnd или CMDIChildWnd.
Если главное окно однооконного приложения или окно MDI многооконного приложения не может обработать сообщение, оно передается объекту главного класса приложения.
В отличие от объектов, представляющих окна типа frame (объекты классов CMDIFrameWnd, CFrameWnd и CMDIChildWnd) окно просмотра в первую очередь проверяет собственную таблицу сообщений. И только в том случае, если командное сообщение не может быть обработано, оно передается документу, связанному с данным окном просмотра.
Также как и окно просмотра, объект, представляющий документ, сначала проверяет свою таблицу сообщений. Только в том, случае если в классе документа отсутствует обработчик командного сообщения, оно передается для обработки шаблону данного документа.
Диалоговые панели представляются объектами классов, наследованных от базового класса СDialog. Если командное сообщение, поступившее объекту диалоговой панели, не может быть обработано, оно передается его родительскому окну.
Если родительское окно диалоговой панели также не может обработать командное сообщение, оно передается главному объекту приложения.
Доработаем приложение Multi так, чтобы оно обладало возможностями приложения Single, описанного в томе 24 серии “Библиотека системного программиста”. Приложение Single представляет собой простейший графический редактор, в котором можно рисовать изображения, содержащие маленькие квадраты, а также сохранять эти рисунки в файлах на диске.
Добавьте в определение класса CMultiDoc новый элемент pointFigCenter, который будет хранить графический документ. Как и в приложении Single, этот элемент сделан на основе шаблона CArray. Однако вместо разработанного нами класса CFigure, здесь мы используем стандартный класс CPoint, входящий в состав MFC. Тип фигуры запоминать не надо, так как приложение Multi будет рисовать фигуры только одного типа:
class CMultiDoc : public CDocument { // Attributes public: CArray<CPoint, CPoint&> pointFigCenter;
Шаблоны классов CArray, CMap и CList определены во включаемом файле afxtempl.h. Так как мы используем класс CArray, добавьте файл afxtempl.h в конце включаемого файла stdafx.h:
#include <afxtempl.h>
Добавьте обработчик сообщения от левой кнопки мыши. Для этого лучше всего воспользоваться средствами MFC ClassWizard:
////////////////////////////////////////////////////////////// // CMultiView message handlers void CMultiView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: // Получаем указатель на документ (объект класса CSingleDoc) CMultiDoc* pDoc = GetDocument(); // Проверяем указатель pDoc ASSERT_VALID(pDoc); // Отображаем на экране квадрат CClientDC dc(this); dc.Rectangle( point.x-10, point.y-10, point.x+10, point.y+10); // Добавляем к массиву, определяющему документ, новый // элемент pDoc->pointFigCenter.Add(point); // Устанавливаем флаг изменения документа pDoc->SetModifiedFlag(); // Вызываем метод OnLButtonDown базового класса CView CView::OnLButtonDown(nFlags, point); }
Обработчик этого сообщения рисует квадрат. Для отображения квадрата используется метод Rectangle. Первые два параметра этого метода определяют расположение левого верхнего угла параллелепипеда. Третий и четвертый параметры задают размеры по горизонтали и вертикали.
Затем добавляем к документу новый элемент, определяющий координаты верхнего левого угла квадрата. В данном случае графический документ приложения представляется массивом pointFigCenter, содержащим объекты класса CPoint.
Так как метод OnLButtonDown изменяет документ, устанавливаем флаг модификации документа, для чего вызываем метод SetModifiedFlag. Затем вызываем метод OnLButtonDown базового класса CView. На этом обработка сообщения завершается.
Приложение должно отображать документ, когда в окно просмотра поступает сообщение WM_PAINT. Для этого следует изменить метод OnDraw окна просмотра документа. MFC AppWizard определяет шаблон этого метода, вам остается только “наполнить” готовый шаблон.
Метод OnDraw должен уметь отображать документ в любой момент времени. Так как документ записан в массиве pointFigCenter класса документа, сначала надо определить указатель на документ, а потом последовательно отобразить на экране все его элементы:
////////////////////////////////////////////////////////////// // CMultiView drawing void CMultiView::OnDraw(CDC* pDC) { CMultiDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); int i; for (i=0; i<pDoc->pointFigCenter.GetSize(); i++) pDC->Rectangle( pDoc->pointFigCenter[i].x-10, pDoc->pointFigCenter[i].y-10, pDoc->pointFigCenter[i].x+10, pDoc->pointFigCenter[i].y+10 ); }
Переопределите метод DeleteContents класса CMultiDoc так, чтобы он удалял содержимое документа. Для этого достаточно удалить все элементы массива pointFigCenter, воспользовавшись методом RemoveAll класса CArray. После очистки документа необходимо вызвать метод DeleteContents базового класса CDocument XE "CDocument" .
Чтобы вставить в класс CMultiDoc метод DeleteContents используйте MFC ClassWizard, а затем модифицируйте его в соответствии со следующим фрагментом кода:
////////////////////////////////////////////////////////////// // CMultiDoc commands void CMultiDoc::DeleteContents() { // Очищаем документ, удаляя все элементы массива arrayFig. pointFigCenter.RemoveAll( ); // Вызываем метод DeleteContents базового класса CDocument CDocument::DeleteContents(); }
Теперь, когда документ создан и приложение умеет отображать его на экране, остается доработать приложение, чтобы оно могло сохранять документ в файле на диске и загружать уже существующие документы из файлов. Для этого переопределите метод Serialize класса документа. Шаблон для этого метода уже определен в приложении:
////////////////////////////////////////////////////////////// // CMultiDoc serialization void CMultiDoc::Serialize(CArchive& ar) { pointFigCenter.Serialize(ar); }
Постройте измененное приложение и запустите полученный выполнимый файл. У вас получился настоящий многооконный графический редактор. Вы можете одновременно открыть несколько окон с документами. Каждый документ можно сохранить в отдельном файле на диске и загрузить при следующем запуске приложения.
Как правило, многооконные приложения позволяют открыть для одного документа несколько окон просмотра. Наше приложение тоже не составляет исключения. Чтобы открыть дополнительное окно для просмотра уже открытого документа, выберите из меню Window строку New.
Откроется новое окно. Заголовки окон просмотра одного документа будут одинаковыми за исключением того, что каждое такое окно имеет дополнительный числовой идентификатор, означающий номер окна. На рисунке 1.14 мы показали как будет выглядеть приложение Multi, если в нем создать два новых документа Multi1 и Multi2, а затем открыть два дополнительных окна для просмотра документа Multi2.
Рис. 1.14. Окно Project Workspace, класс CMultiView
К сожалению, окна просмотра документа несогласованны. Если вы внесете в документ изменения через одно окно, они не появятся во втором до тех пор, пока содержимое окна не будет перерисовано. Чтобы избежать рассогласования между окнами просмотра одного и того же документа, необходимо сразу после изменения документа в одном окне вызвать метод 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 и могут использовать их, чтобы сократить перерисовку документа.
Мы изменяем документ только в методе OnLButtonDown. Поэтому добавьте вызов UpdateAllViews в нем. Разместите его после добавления нового элемента в массив pointFigCenter и установки флага модификации документа (метод UpdateAllViews следует вызывать после метода SetModifiedFlag):
void CMultiView::OnLButtonDown(UINT nFlags, CPoint point) { // ... // Устанавливаем флаг изменения документа pDoc->SetModifiedFlag(); // Сообщаем всем окнам просмотра кроме данного об // изменении документа pDoc->UpdateAllViews(this); // Вызываем метод OnLButtonDown базового класса CView CView::OnLButtonDown(nFlags, point); }
Постройте проект и запустите приложение. Теперь все окна просмотра документа синхронизированы. Когда вы меняете документ в одном окне, автоматически происходит изменения во всех остальных окнах просмотра.
Вы можете заметить, что при изменении документа содержимое других окон просмотра перерисовывается полностью, несмотря на то что дорисовать надо только одну фигуру. Перерисовка всех объектов может заметно замедлить работу приложения, особенно на слабых компьютерах.
Выход из этого положения существует. При вызове метода UpdateAllViews можно указать, какой объект надо дорисовать. А потом надо переопределить метод OnUpdate так, чтобы приложение дорисовывало в окнах просмотра одну только новую фигуру. Для передачи информации от метода UpdateAllViews методу OnUpdate используют параметры lHint и pHint.
Рассмотрим более подробно, как работает метод OnUpdate:
virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
Первый параметр pSender содержит указатель на объект класса окна просмотра, который вызвал изменение документа. Если обновляются все окна просмотра, этот параметр содержит значение NULL.
Второй и третий параметры - lHint и pHint содержат дополнительную. информацию, указанную во время вызова метода UpdateAllViews. Если дополнительная информация не определена, тогда эти параметры содержат значения 0L и NULL соответственно.
Метод OnUpdate вызывается, когда после изменения документа вызывается метод CDocument::UpdateAllViews. Метод также вызывается методом OnInitialUpdate (если вы не переопределите метод OnInitialUpdate).
Реализация OnUpdate из класса CView, определяет, что вся внутренняя область окна просмотра подлежит обновлению и передает данному окну сообщение WM_PAINT (для этого вызывается функция InvalidateRect). Это сообщение обрабатывается методом OnDraw.
Параметр lHint имеет тип LPARAM и может содержать любое 32-битное значение. В нашем случае мы можем передавать через этот параметр индекс элемента массива документа, который надо перерисовать.
Параметр pHint является указателем на объект типа CObject. Поэтому если вы желаете его использовать, вы должны определить собственный класс, наследованный от базового класса CObject, создать объект этого класса и передать указатель на него методу UpdateAllViews.
Указатель на объект класса, наследованного от CObject, можно присвоить указателю на объект класса CObject, поэтому такая операция разрешена. Следовательно, через этот указатель можно передавать объекты различных типов, наследованных от CObject.
Когда вы будете разрабатывать метод OnUpdate, вы должны проверять тип объекта, передаваемого через параметр pHint. Для этого можно воспользоваться методом IsKindOf класса CObject. Метод IsKindOf позволяет узнать тип объекта уже на этапе выполнения приложения.
В нашем приложении новые фигуры добавляются в документ во время обработки сообщения WM_LBUTTONDOWN методом OnLButtonDown класса окна просмотра. Модифицируем этот метод так, чтобы после изменения документа метод UpdateAllViews передавал остальным окнам просмотра индекс добавленного элемента в массиве pointFigCenter редактируемого документа:
////////////////////////////////////////////////////////////// // Метод для обработки сообщения WM_LBUTTONDOWN void CMultiView::OnLButtonDown(UINT nFlags, CPoint point) { // ... // Добавляем к массиву, определяющему документ, новый // элемент pDoc->pointFigCenter.Add(point); // Устанавливаем флаг изменения документа pDoc->SetModifiedFlag(); // Записываем в переменную nNewFig индекс последнего // элемента массива pointFigCenter int nNewFig; nNewFig = pDoc->pointFigCenter.GetUpperBound(); // Сообщаем всем окнам просмотра кроме данного об // изменении документа, указывая индекс нового элемента // массива, представляющего документ pDoc->UpdateAllViews(this, nNewFig); // Вызываем метод OnLButtonDown базового класса CView CView::OnLButtonDown(nFlags, point); }
Теперь мы должны переопределить метод OnUpdate так, чтобы при вызове через метод UpdateAllViews он отображал на экране только последний элемент массива pointFigCenter.
Для переопределения метода OnUpdate лучше всего воспользоваться средствами ClassWizard. ClassWizard создаст шаблон метода OnUpdate, который вы должны изменить, добавив операции для отображения новой фигуры в окне просмотра.
Когда вы переопределяете метод OnUpdate, вы должны иметь в виду, что этот метод вызывается не только методом UpdateAllViews. В некоторых случаях он может быть вызван, если надо перерисовать все изображение в окне просмотра. В этом случае параметр lHint содержит 0, а параметр pHint - NULL Вы должны обрабатывать эту ситуацию отдельно, вызывая метод InvalidateRect и обновляя все окно целиком:
////////////////////////////////////////////////////////////// // CMultiView void CMultiView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint) { // Получаем указатель на документ, относящийся к данному // окну просмотра CMultiDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // Если lHint равен нулю, выполняем обработку по умолчанию if (lHint==0L) CView::OnUpdate(pSender, lHint, pHint); // В противном случае отображаем заданный элемент документа else { // Получаем контекст отображения окна просмотра CClientDC dc(this); // Отображаем фигуру, определенную элементом массива // pointFigCenter с индексом lHint dc.Rectangle( pDoc->pointFigCenter[lHint].x-10, pDoc->pointFigCenter[lHint].y-10, pDoc->pointFigCenter[lHint].x+10, pDoc->pointFigCenter[lHint].y+10 ); } }
Постройте проект и запустите полученное приложение. Откройте несколько окон просмотра одного документа. Теперь все эти окна синхронизированы. Изменения, вносимые в документ через одно окно просмотра автоматически отображаются в других окнах. При этом обновление происходит быстрее, чем когда мы не использовали возможности метода OnUpdate.
В нашем примере мы отображаем новый объект непосредственно из метода OnUpdate. Однако лучше если метод OnUpdate только укажет методу OnDraw область окна просмотра, которую надо перерисовать. Для этого можно воспользоваться методом CWnd::InvalidateRect.
Некоторые многооконные приложения позволяют одновременно работать с документами различных типов. Примером такого приложения является сама среда разработки Visual C++. В ней вы одновременно можете открыть окно редактирования исходного текста приложения и редактор ресурсов. Большинство систем управления базами данных также позволяют работать с документами различных типов. Так FoxPro и Access позволяют в одном окне просматривать поля базы данных, а в другом разрабатывать диалоговую форму для отображения информации из этой базы данных.
Ранее мы уже научились создавать приложения, которые могут отображать графические объекты (окружности, прямоугольники и т. д.) и текстовую информацию. Теперь мы приступим к разработке наиболее сложного приложения, которое может одновременно работать с документами двух различных типов - графическими и текстовыми.
Для упрощения возьмите уже готовое многооконное приложение, которое уже может работать с простейшими графическими документами - Multi.
Создайте два новых класса - класс документа и класс окна отображения для хранения и отображения на экране текстовой информации. Для создания новых классов используйте MFC ClassWizard.
Сначала откройте панель ClassWizard и нажмите кнопку Add Class. Откроется новая диалоговая панель Create New Class. Введите в поле Name имя нового класса - CEditorDoc, а в поле Base Class выберите имя базового класса CDocument. Нажмите кнопку Create. ClassWizard создаст класс CEditorDoc, определение которого он разместит в файле CEditorDoc.h, а реализацию методов класса в файле CEditorDoc.cpp.
Не закрывая ClassWizard, создайте класс окна просмотра текстового документа. Нажмите кнопку Add Class. В поле Name диалоговой панели Create New Class введите имя класса окна просмотра CEditorView, а в поле Base Class выберите имя его базового класса CEditView. Нажмите кнопку Create. ClassWizard создаст класс CEditorView, определение которого он разместит в файле CEditorView.h, а реализацию методов класса в файле CEditorView.cpp.
Теперь вы можете определить, как записывать и считывать текстовый документ из файла на диске. Когда ClassWizard создает для вас класс документа, наследованный от базового класса CDocument, он сразу создает шаблон метода Serialize. К сожалению, этот шаблон придется переделать.
Мы уже изучали класс CEditView в предыдущей книге, посвященной MFC, и вы должны знать, что объекты этого класса сами хранят данные редактируемого документа. Поэтому для записи и чтения документа метод Serialize класса документа должен вызвать соответствующий метод класса окна просмотра. Измените метод Serialize следующим образом:
////////////////////////////////////////////////////////////// // Метод класса Serialize CEditorDoc void CEditorDoc::Serialize(CArchive& ar) { ((CEditView*)m_viewList.GetHead())->SerializeRaw(ar); }
На этом реализация классов для хранения и отображения текстового документа закончена и можно перейти к самому интересному - к созданию шаблона нового документа.
Шаблоны документов, с которыми работает приложение определяют все характеристики данного типа документа. Они включают информацию о классе документа, классе окна просмотра, классе окна рамки, а также о ресурсах, связанных с данным типом документов.
Шаблоны документов создаются объектом приложения во время его инициализации. Откройте метод InitInstance главного класса приложения CMultiApp. В нем создается только один объект класса CMultiDocTemplate, который представляет графический документ и средства для работы с ним.
Так как наше приложение должно работать не только с графическими, но и с текстовыми документами, вы должны создать еще один объект шаблона документов, представляющий текстовый документ. Класс текстового документа называется CEditorDoc, класс окна для его просмотра CEditorView. В качестве окна рамки мы используем класс CChildFrame. Ниже представлен соответствующий фрагмент метода InitInstance:
////////////////////////////////////////////////////////////// // CMultiApp initialization 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_EDITORTYPE, RUNTIME_CLASS(CEditorDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CEditorView)); AddDocTemplate(pDocTemplate); // ... return TRUE; }
При создании шаблона документов указывается идентификатор, который определяет меню, пиктограмму и некоторую другую полезную информацию, связанную с документами данного типа. Мы указали для шаблона текстового документа идентификатор IDR_EDITORTYPE. Здесь мы несколько забежали вперед, так как такой идентификатор еще не определен. Мы вернемся к ресурсам текстового документа в следующем разделе.
Чтобы созданный шаблон текстовых документов добавить к списку шаблонов документов приложения, надо вызвать метод AddDocTemplate, указав ему адрес объекта шаблона.
Откройте редактор ресурсов приложения. Вам требуется создать меню, пиктограмму и строковый ресурс с идентификатором IDR_EDITORTYPE. Самый простой путь для этого заключается в копировании и изменении идентификатора ресурсов, относящихся к графическим документам. Идентификатор этих ресурсов IDR_MULTITYPE. Чтобы скопировать ресурс, вы можете выбрать интересующий вас ресурс из окна проекта, записать его в обменный буфер clipboard, вставить его, а затем изменить идентификатор на IDR_EDITORTYPE.
На первом этапе разработки приложения скопированные меню и пиктограмму вы можете оставить без изменения. Потом вы можете изменить их по своему усмотрению.
Строковый ресурс IDR_EDITORTYPE, описывающий документ, желательно изменить сразу. Для графического документа строковый ресурс IDR_MULTITYPE выглядит следующим образом:
\nGraph\nGraph\nGraph Files (*.LIS)\n.LIS\nGraph.Document\n Graph Document
Чтобы текстовый документ имел другое название типа документа, расширение файлов принятое по умолчанию, измените строковый ресурс с идентификатором IDR_EDITORTYPE:
\nText\nTexti\nTexti Files (*.TXT)\n.TXT\nText.Document\n Text Document
Все, приложение готово. Постройте проект и запустите полученный выполняемый файл. На экране появится диалоговая панель New, представленная на рисунке 1.15.
Рис. 1.15. Выбор типа нового документа
В списке New этой панели перечислены типы документов, с которыми работает приложение. Так как наше приложение работает с документами двух типов - текстовыми и графическими, то этот список содержит всего два элемента. Если в последствии вы добавите к приложению новые типы документов, их названия также появятся в этой панели.
Выберите из списка тип документа, который вы будете создавать и нажмите на кнопку OK. Откроется главное окно приложения и окно MDI с новым документом выбранного типа.
Когда вы будете создавать новые документы через меню (строка New меню File), или панель управления (кнопка ), вам будет предложено выбрать тип нового документа. Для этого также будет использоваться диалоговая панель New, описанная выше.
Одновременно можно открыть несколько документов различного типа, причем каждый документ может иметь несколько окон просмотра. Документы каждого типа имеют различные названия и расширения имени файлов, используемые по умолчанию.