На сегодня существует более десятка версий библиотеки MFC. Практически каждая новая версия среды Microsoft Visual C++ XE "Microsoft Visual C++" (MSVC) поставляется с обновленной версией библиотеки MFC, в которой исправлены обнаруженные ошибки и добавлены новые классы.
Все версии библиотеки MFC XE "библиотека MFC" можно разделить на две группы. К первой относятся 16-разрядные версии MFC, предназначенные для операционных систем Windows 3.1 и 3.11. Вторая группа включает версии MFC, предназначенные для 32-разрядных операционных систем Windows NT и Windows 95. В следующей таблице перечислены все основные версии Microsoft Visual C++ и соответствующие им версии MFC.
Среда разработки |
Версия MFC |
Разрядность |
Microsoft C/C++ версии 7.0 |
1.0 |
16 |
MSVC 1.0 |
2.0 |
16 |
MSVC 1.1 |
2.1 |
32 |
MSVC 1.5 |
2.5 |
16 |
MSVC 2.0 |
2.51 |
16 |
MSVC 2.1 |
2.52 |
16 |
MSVC 2.2 |
2.52b |
16 |
MSVC 4.0 |
2.5c |
16 |
MSVC 2.0 |
3.0 |
32 |
MSVC 2.1 |
3.1 |
32 |
MSVC 2.2 |
3.2 |
32 |
MSVC 4.0 |
4.0 |
32 |
MSVC 4.1 |
4.1 |
32 |
Вы легко можете определить версию библиотеки MFC, установленной на вашем компьютере. Для этого достаточно просмотреть включаемый файл afxver_.h, расположенный в каталоге include библиотеки MFC. В одной из первых строк этого файла определена константа _MFC_VER XE "_MFC_VER", содержащая версию MFC:
// Microsoft Foundation Classes версии 4.00 #define _MFC_VER 0x0400
Мы будем рассматривать библиотеку MFC версий 3.0, 4.0 и 4.1, однако приведенная информация верна и для других версий MFC. В тех случаях, когда эти версии имеют существенные отличия мы будем на это специально указывать.
Библиотека классов MFC XE "классы MFC" содержит большое количество разнообразных классов. Так MFC версии 4.0 включает немного меньше 200 классов. Каждый класс, как правило, содержит от нескольких единиц до нескольких десятков различных методов и элементов данных. Конечно это значительно усложняет освоение программирования с помощью MFC. Но не стоит расстраиваться и опускать руки. Вам вовсе не обязательно знать как устроены все 200 классов. Большинство приложений можно построить на основе нескольких основных классов. Более того, самое простое приложение, которое мы предложим вашему вниманию, будет использовать только один класс из библиотеки MFC.
Мы не станем приводить здесь всю иерархию классов MFC. Вы можете изучить ее, воспользовавшись документацией или справочной системой среды Visual C++. Чтобы просмотреть иерархию классов в справочной системе Visual C++, выберите из окна Project Workspace страницу InfoView, откройте описание MFC 4.0, а затем из раздела Class Library Reference выберите статью Hierarcy Chart (рис. 2.1).
Рис. 2.1. Просмотр иерархии классов MFC
Сейчас мы кратко рассмотрим назначение основных классов библиотеки MFC и их связь друг с другом.
Подавляющее большинство классов библиотеки MFC наследовано от базового класса CObject, лежащего в основе всей иерархии классов этой библиотеки. Методы и элементы данных класса CObject XE "CObject" представляют наиболее общие свойства наследованных из него классов MFC.
Класс CObject, а также все классы, наследованные от него, обеспечивают возможность сохранения объектов класса в файлах на диске с их последующим восстановлением.
Для объектов классов, наследованных от базового класса CObject уже во время работы приложения можно получить разнообразную информацию о классе объекта.
Ряд методов класса CObject предназначен для получения дампа объектов класса во время отладки приложения. Эта особенность класса может ускорить процесс поиска ошибок в приложении.
Непосредственно от класса CObject наследуются ряд классов, которые сами являются базовыми для остальных классов MFC. В первую очередь это класс CCmdTarget XE "CCmdTarget", представляющий основу структуры любого приложения. Основной особенностью класса CCmdTarget и классов, наследованных от него является то, что объекты этих классов могут получать от операционной системы сообщения и обрабатывать их.
Структура классов, связанных с классом CCmdTarget представлена на рисунке 2.2.
Рис. 2.2. Класс CCmdTarget
От класса CCmdTarget наследуется класс CWinThread XE "CWinThread", представляющий подзадачи приложения. Простые приложения, которые мы будем рассматривать в первой книге, посвященной MFC, имеют только одну подзадачу. Эта подзадача, называемая главной, представляется классом CWinApp XE "CWinApp", наследованным от класса CWinThread.
Большинство приложений работают с данными или документами, хранимыми на диске в отдельных файлах. Класс CDocument XE "CDocument", наследованный от базового класса CCmdTarget, служит для представления документов приложения.
Еще один важный класс, наследуемый от CCmdTarget, называется CDocTemplate XE "CDocTemplate". От этого класса наследуются два класса CSingleDocTemplate XE "CSingleDocTemplate" и CMultiDocTemplate XE "CMultiDocTemplate". Все эти классы предназначены для синхронизации и управления основными объектами представляющими приложение - окнами, документами и используемыми ими ресурсами.
Практически все приложения имеют пользовательский интерфейс, построенный на основе окон. Это может быть диалоговая панель, одно окно или несколько окон, связанных вместе. Основные свойства окон представлены классом CWnd XE "CWnd", наследованным от класса CCmdTarget.
Вы очень редко будете создавать объекты класса CWnd. Класс CWnd сам является базовым классом для большого количества классов, представляющих разнообразные окна. На рисунке 2.3 представлена только небольшая часть дерева наследования класса CWnd.
Рис. 2.3. Класс CWnd
Перечислим классы, наследованные от базового класса CWnd.
Класс CFrameWnd XE "CFrameWnd" представляет окна, выступающие в роли обрамляющих окон (frame window), в том числе главные окна приложения. От этого класса также наследуются классы CMDIChildWnd XE "CMDIChildWnd" и CMDIFrameWnd XE "CMDIFrameWnd", используемые для отображения окон многооконного интерфейса MDI. Класс CMDIFrameWnd представляет главное окно приложения MDI, а класс CMDIChildWnd - его дочерние окна MDI. Класс CMiniFrameWnd XE "CMiniFrameWnd" применяется для отображения окон уменьшенного размера. Такие окна обычно используются для отображения в них панели управления.
В предыдущих томах серии “Библиотека системного программиста” мы рассказывали о том, что существует ряд органов управления, встроенных в операционную систему. К ним относятся кнопки, полосы прокрутки, редакторы текста, переключатели и т. д.
Для работы с этими органами управления в библиотеке MFC предусмотрены специальные классы, наследованные непосредственно от класса CWnd XE "CWnd".
Класс |
Орган управления |
CAnimateCtrl XE "CAnimateCtrl" |
Используется для отображения видеоинформации |
CBitmapButton XE "CBitmapButton" |
Кнопка с рисунком |
CButton XE "CButton" |
Кнопка |
CComboBox XE "CComboBox" |
Список с окном редактирования |
CEdit XE "CEdit" |
Поле редактирования |
CHeaderCtrl XE "CHeaderCtrl" |
Заголовок для таблицы |
CHotKeyCtrl XE "CHotKeyCtrl" |
Предназначен для ввода комбинации клавиш акселераторов |
CListBox XE "CListBox" |
Список |
CListCrtl XE "CListCrtl" |
Может использоваться для отображения списка пиктограмм |
CProgressCtrl XE "CProgressCtrl" |
Линейный индикатор |
CPropertySheet XE "CPropertySheet" |
Блокнот. Может состоять из нескольких страниц |
CRichEditCtrl XE "CRichEditCtrl" |
Окно редактирования, в котором можно редактировать форматированный текст |
CScrollBar XE "CScrollBar" |
Полоса просмотра |
CSliderCtrl XE "CSliderCtrl" |
Движок |
CSpinButtonCtrl XE "CSpinButtonCtrl" |
Обычно используется для увеличения или уменьшения значения какого-нибудь параметра |
CStatic XE "CStatic" |
Статический орган управления |
CTabCtrl XE "CTabCtrl" |
Набор “закладок” |
CToolBarCtrl XE "CToolBarCtrl" |
Панель управления |
CToolTipCtrl XE "CToolTipCtrl" |
Маленькое окно содержащее строку текста |
CTreeCtrl XE "CTreeCtrl" |
Орган управления, который позволяет просматривать иерархические структуры данных |
Класс CControlBar и классы, наследуемые от него, предназначены для создания управляющих панелей. Такие панели могут содержать различные органы управления и отображаются как правило в верхней или нижней части главного окна приложения.
Так, класс CStatusBar предназначен для создания панели управления. Эта панель обычно содержит ряд кнопок, дублирующих действие меню приложения.
Класс CStatusBar управляет панелью состояния. Панель состояния отображается в виде полосы в нижней части экрана. В ней приложение может отображать всевозможную информацию, например краткую подсказку о выбранной строке меню.
Большие возможности представляет управляющая панель, созданная на основе класса CDialogBar. Такая панель использует обычный шаблон диалоговой панели, который вы можете разработать в редакторе ресурсов Visual C++.
Класс CPropertySheet XE "CPropertySheet" представляет блокнот - диалоговую панель, содержащую несколько страниц. Отдельные страницы такого блокнота управляются объектами другого класса - CPropertyPage. Класс CPropertyPage XE "CPropertyPage" наследуется от базового класс CDialog, который мы рассмотрим ниже.
Большой интерес представляет класс CView и классы, наследуемые от него (рис. 2.4). Эти классы представляют окно просмотра документов приложения. Именно окно просмотра используется для вывода на экран документа, с которым работает приложения. Через это окно пользователь может изменять документ.
Разрабатывая приложение, вы будете наследовать собственные классы просмотра документов либо от базового класса CView, либо от одного из нескольких порожденных классов, определенных в библиотеке MFC.
Классы, наследованные от CCtrlView XE "CCtrlView", используют для отображения документа готовые органы управления. Например, класс CEditView XE "CEditView" использует орган управления edit (редактор). Более подробно эти классы будут описаны позже, когда мы будем рассказывать о средствах автоматизированного программирования MFC AppWizard и ClassWizard.
Класс CScrollView XE "CScrollView" представляет окно просмотра, которое имеет полосы свертки. В классе определены специальные методы, управляющие полосами просмотра
Класс CFormView XE "CFormView" позволяет создать окно просмотра документа, основанное на диалоговой панели. От этого класса наследуются еще два класса CRecordView XE "CRecordView" и CDaoRecordView XE "CDaoRecordView". Эти классы используются для просмотра записей баз данных.
Рис. 2.4. Класс CView
Кроме перечисленных классов от базового класса CWnd наследуются классы, управляющие диалоговыми панелями. Если вы желаете создать диалоговую панель, вы можете наследовать класс от CDialog (рис. 2.5).
Вместе с диалоговыми панелями обычно используется класс CDataExchange XE "CDataExchange". Класс CDataExchange XE "CDataExchange" обеспечивает работу процедур обмена данными DDX XE "DDX" (Dialog Data Exchange XE "Dialog Data Exchange" ) и проверки данных DDV XE "DDV" (Dialog Data Validation XE "Dialog Data Validation" ) используемых для диалоговых панелей. В отличие от класса CDialog, класс CDataExchange не наследуется от какого-либо другого класса.
Когда вы создаете блокнот, состоящий из нескольких страниц, то каждая такая страница является объектом класса, наследованного от CPropertyPage XE "CPropertyPage".
От класса CDialog XE "CDialog" наследуется ряд классов, представляющих собой стандартные диалоговые панели для выбора шрифта, цвета, вывода документа на печать, поиска в документе определенной последовательности символов, а также поиска и замены одной последовательности символов другой последовательностью.
Чтобы создать стандартный диалог, вы можете просто определить объект соответствующего класса. Дальнейшее управление такой панелью осуществляется методами класса.
Рис. 2.5. Класс CDialog
Для реализации механизма исключений в MFC определен специальный класс CException XE "CException", наследованный от базового класса CObject. Все исключения, определенные в MFC, наследуются от этого класса. Вот список классов, наследованных от CException и их краткое описание. Более полное описание классов, связанных с исключениями, вы можете найти в разделе “Исключения - класс CException” главы “Вспомогательные классы MFC”.
Класс |
Описание |
CArchiveException XE "CArchiveException" |
Исключение, вызванное ошибкой при использовании объекта класса CArchive. Класс CArchive применяется для сохранения и загрузки документа из файла на диске |
CDaoException XE "CDaoException" |
Ошибка при работе с базами данных (при использовании классов DAO) |
CDBException XE "CDBException" |
Ошибка при работе с базами данных (при использовании ODBC) |
CFileException XE "CFileException" |
Ошибка, связанная с файловой системой |
CMemoryException XE "CMemoryException" |
Недостаточно оперативной памяти |
CNotSupportedException XE "CNotSupportedException" |
Попытка выполнить неопределенную операцию |
COleDispatchException XE "COleDispatchException", COleException |
Ошибка OLE |
CResourceException XE "CResourceException" |
Не найден ресурс |
CUserException XE "CUserException" |
Ошибка приложения, вызванная действиями пользователя |
В состав MFC включен целый набор классов, предназначенных для хранения информации в массивах, списках и словарях. Все эти классы наследованы от базового класса CObject.
Не смотря на то, что в языке Си определено понятие массива, классы MFC обеспечивают вам более широкие возможности. Вы, например, можете динамически изменять размер массива, определенного с помощью соответствующего класса.
Для представления массивов предназначены следующие классы.
Класс |
Массив содержит |
CByteArray XE "CByteArray" |
Байты |
CDWordArray XE "CDWordArray" |
Двойные слова |
CObArray XE "CObArray" |
Указателей на объекты класса CObject |
CPtrArray XE "CPtrArray" |
Указателей типа void |
CStringArray XE "CStringArray" |
Объекты класса CString XE "CString" |
CUIntArray XE "CUIntArray" |
Элементы класса unsigned integer или UINT |
CWordArray XE "CWordArray" |
Слова |
Фактически все перечисленные в таблице классы различаются только типом элементов массива. Поэтому вместо использования этих классов гораздо проще воспользоваться шаблоном XE "шаблон" CArray XE "CArray". Используя шаблон CArray, вы можете определять массивы из элементов любых типов и классов. Шаблон CArray наследует свойства класса CObject.
Для построения массивов вы можете также воспользоваться шаблоном CTypedPtrArray XE "CTypedPtrArray". Этот шаблон не наследуется от базового класса CObject, поэтому использовать методы класса CObject для него нельзя.
Для решения многих задач используются такие структуры хранения данных, как списки. MFC включает ряд классов, наследованных от базового класса CObject, которые предоставляют программисту готовое средство для создания собственных списков. В этих классах определены все методы необходимые при работе со списками - добавление нового элемента, вставка нового элемента, определение следующего или предыдущего элемента в списке, удаление элемента и т. д.
Класс |
Список содержит элементы |
CObList XE "CObList" |
Указатели на объекты класса CObject |
CPtrList XE "CPtrList" |
Указатели типа void |
CStringList XE "CStringList" |
Объекты класса CString |
Перечисленные в таблице классы позволяют построить списки из элементов любых типов и объектов любых классов. Однако удобнее пользоваться шаблоном CList XE "CList", также наследованным от базового класса CObject. Для построения списков вы можете также использовать шаблон CTypedPtrList XE "CTypedPtrList". Этот шаблон не наследуется от базового класса CObject.
В библиотеке классов MFC определена еще одна группа классов, позволяющая создавать словари. Словарь представляет собой таблицу из двух колонок, устанавливающую соответствие двух величин. Первая величина представляет ключевое значение и записывается в первую колонку таблицы, а вторая связанное с ней значение, хранящееся во второй колонке. Словарь позволяет добавлять в него пары связанных величин и осуществлять выборку значений по ключевому полю.
Класс |
Ключевое поле |
Поле, связанное с ключевым |
CMapPtrToPtr XE "CMapPtrToPtr" |
Указатель типа void |
Указатель типа void |
CMapPtrToWord XE "CMapPtrToWord" |
Указатель типа void |
Слово |
CMapStringToOb XE "CMapStringToOb" |
Объекты класса CString |
Указатели на объекты класса CObject |
CMapStringToPtr XE "CMapStringToPtr" |
Объекты класса CString |
Указатель типа void |
CMapStringToString XE "CMapStringToString" |
Объекты класса CString |
Объекты класса CString |
CMapWordToOb XE "CMapWordToOb" |
Слово |
Указатели на объекты класса CObject |
CMapWordToPtr XE "CMapWordToPtr" |
Слово |
Указатель типа void |
Вы можете создавать словари, имеющие поля любых типов и классов, если воспользуетесь шаблоном CMap XE "CMap". Шаблон CMap наследуется от базового класса CObject. Для построения словарей можно также использовать шаблон CTypedPtrMap XE "CTypedPtrMap". Шаблон CTypedPtrMap не наследуется от базового класса CObject.
Библиотека MFC включает класс для работы с файловой системой компьютера. Он называется CFile XE "CFile" и также наследуется от базового класса CObject. Непосредственно от класса CFile наследуются еще несколько классов - CMemFile XE "CMemFile", CStdioFile XE "CStdioFile", CSocketFile XE "CSocketFile".
При работе с файловой системой вам может потребоваться получить различную информацию о некотором файле - например, дату создания, размер и т. д. Для хранения этих данных предназначен специальный класс CFileStatus. Класс CFileStatus один из немногих классов, которые не наследуются от базового класса CObject.
Для отображения информации в окне или на любом другом устройстве приложение должно получить так называемый контекст отображения. Основные свойства контекста отображения определены в классе CDC XE "CDC". От него наследуется четыре различных класса, представляющие контекст отображения различных устройств (рис. 2.6).
Рис. 2.6. Класс CDC
В следующей таблице приведено краткое описание классов, наследованных от CDC.
Класс |
Описание |
CClientDC XE "CClientDC" |
Контекст отображения, связанный с внутренней областью окна (client area). Для получения контекста конструктор класса вызывает функцию программного интерфейса GetDC XE "GetDC", а деструктор - функцию ReleaseDC XE "ReleaseDC" |
CMetaFileDC XE "CMetaFileDC" |
Класс предназначен для работы с метафайлами |
CPaintDC XE "CPaintDC" |
Конструктор класса CPaintDC для получения контекста отображения вызывает метод CWnd::BeginPaint, деструктор метод CWnd::EndPaint. Объекты данного класса могут использовать только при обработке сообщения WM_PAINT. Это сообщение обычно обрабатывает метод OnPaint |
CWindowDC XE "CWindowDC" |
Контекст отображения, связанный со всем окном. Для получения контекста конструктор класса вызывает функцию программного интерфейса GetWindowDC, а деструктор - функцию ReleaseDC |
Для отображения информации используются различные объекты графического интерфейса - GDI объекты. Для каждого из этих объектов библиотека MFC содержит описывающий его класс, наследованный от базового класса CGdiObject XE "CGdiObject" (рис. 2.7).
Рис. 2.7. Класс CGdiObject
Класс |
Описание |
CBitmap XE "CBitmap" |
Растровое изображение bitmap |
CBrush XE "CBrush" |
Кисть |
CFont XE "CFont" |
Шрифт |
CPalette XE "CPalette" |
Палитра цветов |
CPen XE "CPen" |
Перо |
CRgn XE "CRgn" |
Область внутри окна |
Практически каждое приложение имеет собственное меню. Оно как правило отображается в верхней части главного окна приложения. Для управления меню в состав MFC включен специальный класс CMenu XE "CMenu", наследованный непосредственно от базового класса CObject.
Для управления меню и панелями управления используется также класс CCmdUI XE "CCmdUI". Этот класс не наследуется от базового класса CObject.
Объекты класса CCmdUI создаются, когда пользователь выбирает строку меню или нажимает кнопки панели управления. Методы класса CCmdUI позволяют управлять строками меню и кнопками панели управления. Так например, существует метод, который делает строку меню неактивной.
В MFC включены несколько классов, обеспечивающую поддержку приложений, работающих с базами данных. В первую очередь это классы ориентированные на работу с ODBC драйверами - CDatabase XE "CDatabase" и CRecordSet XE "CRecordSet". Поддерживаются также новые средства для работы с базами данных DAO (Data Access Object). Для этого предназначены классы CDaoDatabase XE "CDaoDatabase", CDaoRecordSet XE "CDaoRecordSet",. CDaoQueryDef XE "CDaoQueryDef", CDaoTableDef XE "CDaoTableDef", CDaoWorkspace XE "CDaoWorkspace" и CLongBinary XE "CLongBinary".
Для работы с базами данных также предназначены классы CFieldExchange и CDaoFieldExchange. Это самостоятельные классы, они не наследуются от базового класса CObject.
Классы CFieldExchange XE "CFieldExchange" и CDaoFieldExchange XE "CDaoFieldExchange" работают с процедурами обмена данными RFX XE "RFX" (Record Field Exchange XE "Record Field Exchange" ) для классов управляющих базами данных.
Библиотека MFC позволяет создавать многозадачные приложения. Для синхронизации отдельных задач приложения предусмотрен ряд специальных классов. Все они наследуются от класса CSyncObject XE "CSyncObject", представляющего собой абстрактный класс (рис. 2.8).
Рис. 2.8. Класс CSyncObject
В некоторых случаях требуется, чтобы участок программного кода мог выполняться только одной задачей. Такой участок называют критической секцией кода. Для создания и управления критическими секциями предназначены объекты класса CCriticalSection XE "CCriticalSection".
Объекты класса CEvent XE "CEvent" представляют событие. При помощи событий одна задача приложения может передать сообщение другой.
Объекты класса CMutex XE "CMutex" позволяют в данный момент времени представить ресурс в пользование только одной задачи. Остальным задачам доступ к ресурсу запрещается.
Объекты класса CSemaphore XE "CSemaphore" представляют собой семафоры. Семафоры позволяют ограничить количество задач, которые имеют доступ к какому-либо ресурсу.
Для тех, кто занимается сетевыми коммуникациями, в состав библиотеки MFC включены классы CAsyncSocket XE "CAsyncSocket" и наследованный от него класс CSocket XE "CSocket" (рис. 2.9). В класс CAsyncSocket включен программный интерфейс Windows Socket.
Класс CSocket предоставляет программисту более высокий уровень для работы с сокетами. Это значительно облегчает задачу программирования сетевых приложений.
Программирование на уровне программного интерфейса Windows с использованием сокетов описано в двадцать третьем томе серии “Библиотека системного программиста”, который называется “Глобальные сети компьютеров”.
Рис. 2.9. Класс CAsyncSocket
Кроме классов, наследованных от базового класса CObject, библиотека MFC включает ряд самостоятельных классов. У них нет общего базового класса и они имеют различное назначение.
Несколько классов, которые не наследуются от базового класса CObject, мы уже описали. К ним относятся класс CCmdUI XE "CCmdUI", CFileStatus XE "CFileStatus", CDataExchange XE "CDataExchange", CFieldExchange XE "CFieldExchange" и CDaoFieldExchange XE "CDaoFieldExchange".
MFC содержит классы, соответствующие объектам типа простых геометрических фигур, текстовых строк и объектам, определяющим дату и время. В следующей таблице перечислены названия этих классов и их краткие описания.
Класс |
Описание |
CPoint XE "CPoint" |
Объекты класса описывают точку |
CRect XE "CRect" |
Объекты класса описывают прямоугольник |
CSize XE "CSize" |
Объекты класса определяют размер прямоугольника |
CSrting XE "CSrting" |
Объекты класса представляют собой текстовые строки переменной длинны |
CTime XE "CTime" |
Объекты класса служат для хранения даты и времени. Большое количество методов класса позволяют выполнять над объектами класса различные преобразования |
CTimeSpan XE "CTimeSpan" |
Объекты класса определяют период времени |
Класс CArchive XE "CArchive" используется для сохранения и восстановления состояния объектов в файлах на диске. Перед использованием объекта класса CArchive он должен быть привязан к файлу - объекту класса CFile XE "CFile".
Более подробно о процессе сохранения и восстановления объектов вы можете прочитать в разделе “Сохранение и восстановление объектов”. Пример использования класса CArchive для записи и восстановления документов в файлах представлен в разделе “Простейший графический редактор” главы “Однооконный интерфейс”.
Во многих случаях бывает необходимо уже во время работы приложения получить информацию о классе объекта и его базовом классе. Для этого любой класс, наследованный от базового класса CObject связан с структурой CRuntimeClass XE "CRuntimeClass". Она позволяет определить имя класса объекта, размер объекта в байтах, указатель на конструктор класса, не имеющий аргументов и деструктор класса. Можно также узнать подобную информацию о базовом классе и некоторые дополнительные сведения.
В отладочной версии приложения вы можете использовать класс CDumpContext XE "CDumpContext". Он позволяет выдавать состояние различных объектов в текстовом виде.
Класс CMemoryState XE "CMemoryState" позволяет локализовать проблемы, связанные с динамическим выделением оперативной памяти. Такие проблемы обычно возникают, когда пользователь выделяет память, используя оператор new, а затем забывает ввернуть эту память операционной системе.
Класс CPrintInfo предназначен для управления печатью документов на принтере. Когда пользователь отправляет документ на печать или выполняет предварительный просмотр документа перед печатью, создается объект класса CPrintInfo XE "CPrintInfo". Он содержит различную информацию о том, какие страницы документа печатается и т. д.
Кроме описанных нами классов библиотека MFC включает большое количество классов, предназначенных для организации технологии OLE. Из-за ограниченного объема книги мы не будем рассматривать приложения, поддерживающие OLE технологию.
Первое приложение MFHello, которое мы создадим с использованием библиотеки классов MFC будет очень простое. Единственное, что оно будет делать - это отображать на экране маленькую диалоговую панель, содержащую строку “Hello, MFC!”.
Исходный текст приложения, представленный в листинге 2.1, состоит всего из двенадцати строк, не считая строк комментариев. В нашем первом приложении мы использовали единственный класс библиотеки MFC, наследованный от базового класса CWinApp XE "CWinApp".
Чтобы создать новый проект, выберите из меню File строку New. На экране появится диалоговая панель New, содержащая одноименный список New и две кнопки. Выберите из списка New строку Project Workspace. Откроется диалоговая панель New project workspace (рис. 4.1 из главы “Приложение с главной диалоговой панелью”). В ней вы должны указать тип приложения, который будет разрабатываться, имя проекта и расположение каталога для записи в него файлов проекта.
Самые простые приложения с использованием библиотеки классов MFC мы будем создавать без использования автоматизированных средств разработки приложений MFC AppWizard. Поэтому в качестве типа приложения выберите из списка Type строку Application.
В поле Name введите имя нового проекта. Наш первый проект мы назвали именем MFHello. Расположение каталога для размещения файлов проекта отображается в поле Location. По умолчанию каталог проекта называется точно также как сам проект и будет размещен в каталоге Projects среды разработки Visual C++. Вы можете выбрать для проекта любой другой каталог, если нажмете на кнопку Browse.
Теперь нажмите кнопку Create. Будут созданы служебные файлы проекта. Они получат названия MFHello.mak, MFHello.ncb и MFHello.mdp. Файл MFHello.mdp является основным файлом проекта. В этих файлах определяется, какие исходные файлы содержит проект, указываются характеристики создаваемого приложения, конфигурация самой среды разработки Visual C++.
Microsoft Visual C++ версии 4.0 имеет удобные средства для просмотра исходных текстов файлов проекта, кодов классов приложения, ресурсов, а также для получения справочной информации.
Откройте окно Project Workspace. Обычно оно расположено в левой части экрана, но вы можете переместить его в другое место и даже закрыть. Если окно закрыто, откройте его. Для этого выберите из меню View строку Project Workspace. Окно Project Workspace состоит из нескольких страниц. Вы можете открыть их, нажимая на соответствующие закладки. Количество страниц зависит от того, установлена ли справочная система и открыт ли проект.
Закладка |
Описание |
|
ClassView XE "ClassView". Средство для просмотра и редактирования классов приложения |
|
ResourceView XE "ResourceView". Позволяет просматривать и редактировать ресурсы приложения |
|
FileView XE "FileView". Выполняет просмотр файлов приложения |
|
InfoView XE "InfoView". Справочная система Microsoft Visual C++. В нее включена информация о языке Си, Си++, библиотеки классов MFC, функций програмного интерфейса Windows |
Откройте окно Project Workspace и выберите страницу FileView. Так как сначала в проекте нет ни одного файла, вы увидите пустую папку проекта. Теперь надо создать новый текстовый файл и набрать в нем исходный текст нашего первого приложения.
Чтобы создать новый файл, вы можете нажать кнопку New Source File () в стандартной панели управления или выбрать из меню File строку New, а затем из списка в открывшейся панели New выбрать строку Text File. Откроется новое окно текстового редактора. В нем надо набрать исходный текст приложения, представленного листингом 2.1. Сохраните набранный текст в файле под именем MFHello.cpp в каталоге проекта. Для этого выберите из меню File строку Save As.
Листинг 2.1. Файл MFHello.cpp
// Включаемый файл для MFC #include <afxwin.h> //===================================================== // Класс CMFHelloApp // Наследуем от базового класса CWinApp главный // класс приложения CMFHelloApp //===================================================== class CMFHelloApp : public CWinApp { public: // Мы будем переопределять метод InitInstance, // предназначенный для инициализации приложения virtual BOOL InitInstance(); }; // Создаем объект приложение класса CMFHelloApp CMFHelloApp MFHelloApp; //===================================================== // Метод InitInstance класса CMFHelloApp // Переопределяем виртуальный метод InitInstance // класса CWinApp. Он вызывается каждый раз при запуске // приложения //===================================================== BOOL CMFHelloApp::InitInstance() { AfxMessageBox("Hello, MFC!"); return FALSE; }
Единственный файл с исходным текстом приложения создан и его надо включить в проект. Выберите из меню Insert строку Files into Project. На экране появится диалоговая панель Insert Files into Project. Выберите файл MFHello.cpp и нажмите кнопку Add. Диалоговая панель закроется. Просмотрите еще раз папку с файлами проекта. Теперь в ней расположен файл MFHello.cpp (рис. 2.10).
Рис. 2.10. Файлы проекта MFHello
Откройте страницу ClassView XE "ClassView" в окне Project Workspace. В ней отображаются все классы, определенные в приложении и все глобальные переменные. Для каждого класса приложения можно видеть входящие в него элементы (рис. 2.11).
На странице ClassView отображается древовидная структура классов вашего приложения. Когда вы в первый раз открываете ClassView, структура классов отображается в виде закрытой папки . Чтобы ее открыть, щелкните два раза левой кнопкой мыши по изображению папки или один раз по символу , расположенному левее папки. В открытой папке символом представлены классы приложения и еще одним символом папки глобальные объекты приложения. Папку с глобальными объектами можно открыть также как папку с классами. Вы можете открыть и сами классы. Так вы сможете просмотреть элементы класса - методы и данные. Методы обозначаются символом , а данные символом . Если методы или данные объявлены как protected, перед ними отображается символ , а если как private - символ .
В нашем проекте определен только один класс CMFHelloApp. В класс CMFHelloApp входит метод InitInstance. Кроме того, определена одна глобальная переменная MFHelloApp.
Рис. 2.11. Классы проекта MFHello
Если вы используете в приложении классы или функции библиотеки классов MFC, то проект надо настроить. Выберите из меню Build строку Settings или нажмите комбинацию клавиш <Alt+F7>. На экране появится диалоговая панель Project Settings. В этой панели расположены несколько страниц, позволяющих настроить различные характеристики проекта.
Откройте страницу General. Мы привели внешний вид этой страницы на рисунке 2.12. Обратите внимание на список Microsoft Foundation Classes. По умолчанию из этого списка выбрана строка No Using MFC. Она означает, что приложение не будет использовать библиотеку MFC. Так как в приложении MFHello и всех остальных приложениях, описанных в этой книге, задействованы классы MFC, выберите из списка Microsoft Foundation Classes строку Use MFC in a Shared Dll (mfc40(d).dll) или строку Use MFC in a Static Library.
Что же выбрать - Use MFC in a Shared Dll или Use MFC in a Static Library? Оказывается программные коды библиотеки классов MFC могут использоваться приложением двумя способами. Код библиотеки MFC либо непосредственно записывается в выполнимый файл приложения, либо вызывается по мере необходимости из отдельной dll-библиотеки.
Использование для приложения dll-библиотеки немного ускоряет процесс построения проекта и позволяет создавать выполнимые файлы значительно меньшего размера. Однако сам по себе такой выполнимый файл работать не будет. Для него необходима dll-библиотека. Поэтому если приложение устанавливается и на других компьютерах, его надо распространять вместе с dll-библиотекой.
Для MFC версии 4.0 dll-библиотека хранится в файлах Mfc40d.dll и Mfc40.dll. В файле Mfc40d.dll XE "Mfc40d.dll" находится отладочная версия MFC, а в файле Mfc40.dll отладочная информация отсутствует. В ходе установки Visual C++ эти dll-библиотеки записываются в системный каталог операционной системы.
Если вы забудете указать, что приложение использует MFC, то во время построения проекта на этапе установления связи (Linking...) будут обнаружены ошибки:
error LNK2001: unresolved external symbol __endthreadex error LNK2001: unresolved external symbol __beginthreadexКогда для создания нового приложения используется MFC AppWizard, библиотека MFC подключается автоматически. Вам не надо вручную вносить изменения в диалоговой панели Project Settings. Более подробно об использовании MFC AppWizard вы можете прочитать в следующих разделах книги.
Рис. 2.12. Диалоговая панель Project Settings
Если вы все сделали, постройте проект. Для этого вы можете выбрать из меню Build строку Build MFHello.exe - создать выполнимый файл MFHello.exe или строку Rebuild All - заново оттранслировать все исходные файлы проекта. Если в ходе построения выполнимого файла приложения нашего проекта возникли ошибки, исправьте их и заново оттранслируйте проект. После успешного построения проекта запустите полученное приложение, выбрав из меню Build строку Execute MFHello.exe.
На экране появится маленькая диалоговая панель (рис. 2.13). Название этой диалоговой панели соответствует названию выполнимого файла приложения MFHELLO. В диалоговой панели отображается текстовое сообщение “Hello, MFC!”, пиктограмма с восклицательным знаком внутри треугольника и кнопка OK. Как только вы нажмете кнопку OK, диалоговая панель закрывается и приложение завершит свою работу.
Рис. 2.13. Приложение MFHello
Посмотрим, как работает приложение MFHello на уровне исходного текста. Первая строка, не считая строки комментария, содержит директиву препроцессора #include, которая включает файл afxwin.h XE "afxwin.h" :
// Включаемый файл для MFC #include <afxwin.h>
В этом файле определены классы, методы, константы и другие структуры для библиотеки классов MFC. Кроме того включаемый файл afxwin.h автоматически подключает другой включаемый файл windows.h уже известный вам по предыдущим томам серии библиотеки системного программиста. Поэтому даже если из приложения будут вызываться функции стандартного программного интерфейса Windows файл windows.h XE "windows.h" включать не надо.
В предыдущих томах серии “Библиотека системного программиста”, посвященных программированию в среде операционных систем Windows и Windows 95, мы рассказывали о функции WinMain XE "WinMain", которая является главной функцией приложения. Эта функция вызывается, когда пользователь или операционная система запускает приложение. Все приложения, которые мы рассматривали до сих пор содержали функцию WinMain:
// Самое простое приложение Windows #include <windows.h> int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) { // Отображаем на экране небольшую панель с сообщением MessageBox(NULL,"Hello, world", "Text Message", MB_OK); // Завершаем приложение return 0; }
Взгляните на исходные тексты приложения. Вы нигде не обнаружите хорошо знакомой функции WinMain. Не видно также переменных, представляющих параметры этой функции.
Оказывается, в приложениях, основанных на классах MFC, функция WinMain скрыта от программиста в определении класса CWinApp XE "CWinApp". В каждом приложении определяется главный класс приложения, наследуемый от базового класса CWinApp. Обычно этот класс называют CProjectApp, где в качестве Project указывают имя проекта. Приложение должно иметь только один объект главного класса приложения, наследованного от класса CWinApp.
Класс CWinApp выполняет все действия, которые обычно выполняла функция WinMain - инициализирует приложение, обрабатывает сообщения и завершает приложение. Для этого класс CWinApp включает виртуальные методы InitApplication XE "CWinApp:InitApplication", InitInstance XE "CWinApp:InitInstance", Run XE "CWinApp:Run" и ExitInstance XE "CWinApp:ExitInstance".
Чтобы выполнить инициализацию приложения, функция WinMain вызывает методы InitApplication и InitInstance для объекта главного класса приложения. Метод InitApplication выполняет инициализацию на уровне приложения. Вы можете переопределить этот метод в своем приложении. Метод InitInstance выполняет инициализацию каждой запущенной копии приложения. Обычно метод InitInstance создает главное окно приложения. Вы обязательно должны переопределить этот метод в своем приложении. Остальные методы, например Run, можно не переопределять.
Затем функция WinMain начинает обрабатывать цикл сообщений. Для этого вызывается метод Run. Вы можете переопределить этот метод, чтобы реализовать собственный цикл обработки сообщений.
Когда приложение заканчивает работу и цикл обработки сообщений завершается, вызывается метод ExitInstance. Вы можете переопределить этот метод, чтобы выполнить какие-либо действия перед завершением приложения.
В нашем случае главный класс приложения, который называется CMFHelloApp, определяется следующим образом:
//===================================================== // Класс CMFHelloApp // Наследуем от базового класса CWinApp главный // класс приложения CMFHelloApp //===================================================== class CMFHelloApp : public CWinApp { public: // Мы будем переопределять метод InitInstance, // предназначенный для инициализации приложения virtual BOOL InitInstance(); };
Мы наследуем класс CMFHelloApp от базового класса CWinApp. При этом базовый класс указан как public. Это означает, что в программе доступны все элементы базового класса CWinApp, объявленные как public. Мы можем вызывать методы класса CWinApp для объектов класса CMFHelloApp и обращаться к элементам данных класса CWinApp.
В определении класса CMFHelloApp мы объявляем виртуальный метод InitInstance. Он будет нами переопределен. Изначально метод InitInstance XE "CWinApp:InitInstance" определен в классе CWinApp. Метод InitInstance отвечает за инициализацию приложения. Он вызывается каждый раз, когда пользователь запускает приложение. Если пользователь запустит приложение несколько раз, то метод InitInstance будет вызываться каждый раз.
Метод InitInstance обязательно должен быть переопределен в вашем главном классе приложения. Остальные виртуальные методы можно оставить без изменения.
Сразу после объявления главного класса приложения мы создаем объект этого класса - MFHelloApp. Вы должны определить объект главного класса приложения как глобальный. В этом случае он будет создан сразу при запуске приложения и сможет управлять всей работой приложения. После создания глобальных объектов вызывается функция WinMain, определенная в классе CWinApp. Она выполняет свои обычные функции - регистрирует классы окон, создает окно, и т. д.
Нельзя создавать два и более объектов класса, наследованного от базового класса CWinApp. Каждое приложение должно иметь один и только один объект главного класса приложения:
// Создаем объект приложение класса CMFHelloApp CMFStartApp MFHelloApp;
Метод InitInstance главного класса приложения CMFHelloApp служит для инициализации. Он вызывается автоматически каждый раз, когда запускается очередная копия приложения. Мы используем метод InitInstance чтобы вывести на экран компьютера диалоговую панель с сообщением “Hello, MFC!”. Для этого вызываем функцию AfxMessageBox:
//===================================================== // Метод InitInstance класса CMFHelloApp // Переопределяем виртуальный метод InitInstance // класса CWinApp. Он вызывается каждый раз при запуске // приложения //===================================================== BOOL CMFHelloApp::InitInstance() { AfxMessageBox("Hello, MFC!"); return FALSE; }
Функция AfxMessageBox XE "AfxMessageBox" определена в библиотеке классов MFC. Вместо функции AfxMessageBox вы можете воспользоваться хорошо известной вам функцией MessageBox программного интерфейса Windows.
Когда вы вызываете из приложения, созданного с использованием классов MFC, функции программного интерфейса Windows, необходимо указывать перед именем функции символы ::. Так, вызов функции MessageBox мог бы выглядеть следующим образом:
::MessageBox(NULL,"Hello, world!", "Message", MB_OK);
В конце метода InitInstance мы вызываем оператор return и возвращаем значение FALSE. Приложение сразу завершается. Если метод InitInstance вернет значение TRUE, приложение продолжит работу и приступит к обработке очереди сообщений. Более подробно о методе InitInstance и свойствах базового класса CWinApp вы узнаете позже, когда мы будем рассматривать средства автоматизированной разработки приложений.
Конечно, вы можете продолжать набирать исходные тексты приложения вручную непосредственно в текстовом редакторе. Но во многих случаях среда VIsual C++ может оказать вам значительную помощь. Одним из средств, способных оказать вам такую помощь уже сейчас, является ClassView.
Используя ClassView можно быстро добавлять к классам новые элементы, просматривать структуру наследования классов и выполнять другие полезные операции. После того как вы разберетесь с основными принципами построения приложений Windows с использованием классов MFC, мы продолжим изучение вспомогательных средств VIsual C++. В следующих главах книги мы рассмотрим средства автоматизированного проектирования приложений MFC AppWizard и средство для разработки классов ClassWizard.
А сейчас приступим к описанию возможностей ClassView. Выберите из списка ClassView название класса, который вы желаете просмотреть или изменить, и нажмите правую клавишу мыши. На экране появится временное меню, представленное на рисунке 2.14.
Рис. 2.14. Временное меню класса
Строки этого меню позволяют выполнять над классами все основные действия. Строка Go to Definition позволяет просмотреть в окне редактирования исходный текст класса. Вы можете редактировать класс непосредственно, но для добавления в класс новых методов и данных удобнее пользоваться строками Add Function и Add Variable.
Чтобы добавить в класс новый метод, выберите из временного меню строку Add Function. На экране появится диалоговая панель Add Member Function, представленная на рисунке 2.15.
Рис. 2.15. Диалоговая панель Add Member Function
В поле Function Type следует ввести тип значения, возвращаемого методом. Объявление метода запишите в поле Function Declaration. В этой же диалоговой панели можно определить область видимости метода. Для этого предназначен переключатель с зависимой фиксацией Access. Он может находиться в трех положениях: Public XE "public", Protected XE "protected" и Private XE "private". Переключатели Static и Virtual позволяют определить, что добавляемый метод должен быть объявлен, соответственно, как статический или виртуальный.
Когда все поля диалоговой панели заполнены, нажмите кнопку OK. В объявлении класса будет добавлен новый метод. Название метода также появится в списке элементов класса в окне ClassView.
Процедура добавления в класс новых данных сходна с только что описанной процедурой добавления метода. Для этого выберите из меню строку Add Variable. На экране появится диалоговая панель Add Member Variable, представленная на рисунке 2.16.
Рис. 2.16. Диалоговая панель Add Member Variable
В поле Variable Type надо ввести тип данных, а в поле Variable Declaration - название соответствующей переменной. Область видимости переменной определяется переключателем Access.
С помощью ClassView можно просмотреть список названий файлов в которых используется данный класс. Для этого надо выбрать из временного меню строку References. На экране появится диалоговая панель Definitions and References.
ClassView предоставляет очень интересную и полезную возможность просмотра дерева наследования классов приложения. Для этого выберите название интересующего вас класса из списка классов и откройте временное меню, нажав правую кнопку мыши. Строки Derived Classes и Base Classes позволяют просмотреть последовательность классов, порожденных от данного класса или последовательность базовых классов.
На рисунке 2.17 мы воспользовались возможностями ClassView для изучения последовательности базовых классов для основного класса приложения MFHello.
Рис. 2.17. Порядок наследования, диалоговая панель Base Classes and Members
Диалоговая панель Base Classes and Members не имеет кнопок OK и Cancel. Она закрывается в том случае, если вы выберите из нее элемент класса или переключитесь на другое окно. Иногда удобно, чтобы диалоговая панель постоянно присутствовала на экране. Вы можете “прикрепить” ее к главному окну Microsoft Visual C++. Нажмите кнопку , она изменится на . Теперь в любой момент времени вы сможете открыть панель Base Classes and Members.
В левой части этой панели отображается список классов в порядке наследования. Базовые классы расположены ниже и правее наследуемых классов. Вы наглядно можете определить, что основным базовым классом для CMFHelloApp является класс CObject. Из этого класса наследуются классы CCmdTarget, CWinThread, CWinApp и только затем класс CMFHelloApp, определенный в приложении.
В правой части экрана отображаются элементы класса, выбранного из списка классов. Вы можете просмотреть не только элементы классов, определенных в приложении, но также и элементы классов из библиотеки MFC. Все элементы класса разделены на три группы в зависимости от их области видимости - Public, Protected и Private.
Панель Base Classes and Members позволяет легко перейти к редактированию любого элемента класса. Для этого выберите название этого элемента из списка и сделайте по нему двойной щелчок левой кнопкой мыши.
Чтобы вам было легче отличить методы от данных класса, перед методами располагается символ f, а перед данными d. Непосредственно перед именем метода или данных расположен символ, позволяющий отличить статические и виртуальные методы. Перед названиями статических методов расположен символ , а перед виртуальными - .
Если список методов и данных класса слишком велик, вы можете отображать в диалоговой панели только их часть. Выберите из поля Functions типы методов, имена которых надо отобразить. Доступны следующие типы методов:
Тип метода |
Отображать |
All |
Все методы |
Virtual |
Виртуальные методы |
Static |
Статические методы |
Non- Static |
Все методы, кроме статических |
Non- Virtual |
Все методы, кроме виртуальных |
Non- Static Non-Virtual |
Все методы, кроме статических и виртуальных |
None |
Не отображать методы класса |
Аналогично, из списка Data надо выбрать типы элементов данных класса, которые надо отображать на экране. В следующей таблице описаны различные типы данных, представленные в списке Data.
Тип элементов данных |
Отображать |
All |
Все элементы данных |
Static |
Статические элементы данных |
Non- Static |
Все данные, кроме статических |
None |
Не отображать элементы данных |
В нижней правой части диалоговой панели Base Classes and Members отображаются названия файлов в которых определен (группа Definitions), и в которых используется (группа References) выбранный элемент. Двойной щелчок левой кнопкой мыши позволяет открыть в редакторе соответствующий файл. Курсор при этом сразу устанавливается в то место, где объявляется или используется выбранный элемент класса.
Выберите из списка элементов класса интересующий вас метод и нажмите правую кнопку мыши. На экране появится временное меню, показанное нами на рисунке 2.18. Это меню позволяет перейти к редактированию объявления или определения метода, просмотреть те строки исходного текста приложения, в которых вызывается метод, получить список функций и методов, вызываемых выбранным методом.
Рис. 2.18. Временное меню метода
Чтобы открыть в редакторе файл, в котором объявляется метод и перейти к его редактированию, выберите из временного меню метода строку Go to Definition. После того как вы добавите в класс новый метод, ClassWizard создаст шаблон метода. Строка Go to Declaration из временного меню метода позволяет изменить этот шаблон по вашему усмотрению.
Возможности ClassView можно использовать даже на этапе отладки приложения. Так, из временного меню метода можно установить точку прерывания непосредственно на начало метода. Для этого выберите из меню строку Set Breakpoint.
Временное меню данных класса отличается от временного меню метода. Если выбрать из списка элементов класса данные и нажать правую кнопку мыши, на экране появится меню, представленное на рисунке 2.19.
Рис. 2.19. Временное меню метода
Первое созданное нами приложение не имело главного окна. Для вывода сообщения на экран мы использовали функцию AfxMessageBox, которая очень похожа на функцию MessageBox программного интерфейса операционной системы Windows.
Следующее приложение, которое мы будем рассматривать, немного сложнее. При запуске оно будет отображать на экране компьютера обычное окно, имеющее заголовок, системное меню и кнопки управления.
Точно так же как в приложении MFHello, в нашем втором приложении мы будем использовать класс CWinApp XE "CWinApp" в качестве главного класса приложения. Для управления окном приложения мы создадим еще один класс, наследуемый от базового класса CFrameWnd XE "CFrameWnd", входящего в библиотеку MFС.
Создайте новый проект, как мы рассказывали выше, и назовите его именем MFStart. В качестве типа приложения выберите из списка Type строку Application (рис. 4.1 из главы “Приложение с главной диалоговой панелью”). Автоматизированные средства разработки приложений MFC AppWizard мы рассмотрим позже.
Наберите в редакторе исходный текст приложения и сохраните его в файле MFStart.cpp (листинг 2.2). Наш пример без учета строк комментариев состоит всего из двадцати строк исходного текста, поэтому набор текста не займет у вас много времени. Включите набранный файл в проект.
Листинг 2.2. Файл MFStart.cpp
// Включаемый файл для MFC #include <afxwin.h> //===================================================== // Класс CMFStartApp // Наследуем от базового класса CWinApp главный // класс приложения CMFStartApp //===================================================== class CMFStartApp : public CWinApp { public: // Мы будем переопределять метод InitInstance, // предназначенный для инициализации приложения virtual BOOL InitInstance(); }; // Создаем объект приложение класса CMFStartApp CMFStartApp MFStartApp; //===================================================== // Класс CMFStartWindow // Наследуем от базового класса CFrameWnd класс // CMFStartWindow. Он будет представлять главное // окно нашего приложения //===================================================== class CMFStartWindow : public CFrameWnd { public: // Объявляем конструктор класса CMFStartWindow CMFStartWindow(); }; //===================================================== // Метод InitInstance класса CMFStartApp // Переопределяем виртуальный метод InitInstance // класса CWinApp. Он вызывается каждый раз при запуске // приложения //===================================================== BOOL CMFStartApp::InitInstance() { // Создаем объект класса CMFStartWindow m_pMainWnd = new CMFStartWindow(); // Отображаем окно на экране. Параметр m_nCmdShow // определяет режим в котором оно будет отображаться m_pMainWnd -> ShowWindow(m_nCmdShow); // Обновляем содержимое окна m_pMainWnd -> UpdateWindow(); return TRUE; } //===================================================== // Конструктор класса CMFStartWindow //===================================================== CMFStartWindow::CMFStartWindow() { // Создаем окно приложения, соответствующее // данному объекту класса CMFStartWindow Create(NULL, "Hello MFC"); }
Просмотрите папку с файлами проекта. Теперь в ней расположен файл MFStart.cpp. Затем откройте страницу ClassView в окне Project Workspace (рис. 2.20). В ней отображаются два класса CMFStartApp и CMFStartWindow. В класс CMFStartApp входит метод InitInstance, а в класс CMFStartWindow конструктор CMFStartWindow. Кроме того, определена глобальная переменная MFStartApp.
Рис. 2.20. Классы проекта MFStart
Постройте проект и запустите полученное приложение, выбрав из меню Build строку Execute MFStart.exe. На экране появится главное окно приложения, представлене нами на рисунке 2.21. Оно имеет стандартный заголовок с надписью Hello MFC, системное меню и кнопки для изменения размера окна. Чтобы завершить приложение, вы можете выбрать строку Close из системного меню главного окна или нажать на кнопку .
Рис. 2.21. Приложение MFStart
Приложение MFStart очень простое. Оно состоит из одного главного окна и не содержит ни меню, ни каких либо других органов управления. Тем не менее, окно приложения MFStart обладает всеми возможностями окон Windows. Оно имеет заголовок, системное меню и кнопки управления. Вы можете изменить размер этого окна, увеличить его на весь экран и даже уменьшить до размера пиктограммы.
Так же как и у приложения MFHello, первая строка исходного текста приложения MFStart, не считая строки комментария, содержит директиву препроцессора #include, которая включает файл afxwin.h. Этот файл включается в исходные тексты всех приложений, использующих библиотеку классов MFC.
Затем мы определяем главный класс приложения, который наследуется от базового класса CWinApp XE "CWinApp". Главный класс приложения MFHello, который называется CMFStartApp, определяется следующим образом:
//===================================================== // Класс CMFStartApp // Наследуем от базового класса CWinApp главный // класс приложения CMFStartApp //===================================================== class CMFStartApp : public CWinApp { public: // Мы будем переопределять метод InitInstance, // предназначенный для инициализации приложения virtual BOOL InitInstance(); };
Как видите главный класс приложения MFStart определяется точно так же как у приложения MFHello. Класс CMFStartApp наследуется от базового класса CWinApp. При этом базовый класс указан как public. Мы можем вызывать методы класса CWinApp для объектов класса CMFStartApp и обращаться к элементам данных класса CWinApp. В определении класса CMFStartApp объявлен виртуальный метод InitInstance XE "CWinApp:InitInstance". Он будет переопределен нами несколько позже, после объявления класса окна приложения.
После объявления главного класса приложения мы создаем объект этого класса - MFStartApp:
// Создаем объект приложение класса CMFStartApp CMFStartApp MFStartApp;
Второй класс, определенный в нашем приложении, наследуется от базового класса CFrameWnd XE "CFrameWnd" как public и будет представлять главное окно приложения. В классе главного окна мы определили только конструктор, который будет описан ниже:
//===================================================== // Класс CMFStartWindow // Наследуем от базового класса CFrameWnd класс // CMFStartWindow. Он будет представлять главное // окно нашего приложения //===================================================== class CMFStartWindow : public CFrameWnd { public: // Объявляем конструктор класса CMFStartWindow CMFStartWindow(); };
Метод InitInstance главного класса приложения CMFStartApp служит для инициализации. Он вызывается автоматически каждый раз, когда запускается очередная копия приложения.
Мы используем метод InitInstance, чтобы отобразить на экране окно приложения. Для этого мы создаем объект класса CMFStartWindow и записываем указатель на этот объект в элемент данных m_pMainWnd XE "CWinThread:m_pMainWnd" класса CWinThread (класс CWinThread является базовым для класса CWinApp). Таким образом, объект приложения и объект окна приложения связываются вместе.
Для создания объекта класса CMFStartWindow мы используем оператор new. Он создает новый объект указанного класса, отводит память и возвращает указатель на него. При создании нового объекта оператором new для него автоматически вызывается конструктор, описанный ниже.
Окно появится на экране только после того, как будет вызван метод ShowWindow XE "CWnd:ShowWindow". В качестве параметра методу ShowWindow передается параметр m_nCmdShow XE "CWinApp:m_nCmdShow". Переменная m_nCmdShow является элементом класса CWinApp. Его назначение соответствует параметру nCmdShow функции WinMain, то есть определяет, как должно отображаться главное окно приложения сразу после его запуска.
После того как окно появилось на экране, мы передаем ему сообщение WM_PAINT XE "WM_PAINT", вызывая метод UpdateWindow XE "CWnd:UpdateWindow". По этому сообщению приложение должно обновить содержимое окна. В нашем первом приложении мы ничего не будем отображать в окне, поэтому данный метод можно не вызывать.
В конце метода InitInstance XE "CWinApp:InitInstance" мы вызываем оператор return и возвращаем значение TRUE, означающее, что инициализация приложения завершилась успешно и можно приступать к обработке очереди сообщений.
Если метод InitInstance вернет значение FALSE, приложение немедленно завершится. Мы использовали эту возможность в приложении MFHello, описанном выше.
//===================================================== // Метод InitInstance класса CMFStartApp // Переопределяем виртуальный метод InitInstance // класса CWinApp. Он вызывается каждый раз при запуске // приложения //===================================================== BOOL CMFStartApp::InitInstance() { // Создаем объект класса CMFStartWindow m_pMainWnd = new CMFStartWindow(); // Отображаем окно на экране. Параметр m_nCmdShow // определяет режим в котором оно будет отображаться m_pMainWnd -> ShowWindow(m_nCmdShow); // Обновляем содержимое окна m_pMainWnd -> UpdateWindow(); return TRUE; }
Чтобы создать окно, мы создаем объект класса CMFStartWindow. Такой объект не является собственно окном, которое пользователь видит на экране компьютера, а представляет собой внутреннее представление окна. Для создания окна предназначается метод Create, определенный в классе CFrameWnd XE "CFrameWnd:Create". Он создает окно и связывает его с объектом Си++, в нашем случае с объектом класса CMFStartWindow:
//===================================================== // Конструктор класса CMFStartWindow //===================================================== CMFStartWindow::CMFStartWindow() { // Создаем окно приложения, соответствующее // данному объекту класса CMFStartWindow Create(NULL, "Hello MFC"); }
Для упрощения мы поместили описание классов, определения их методов и определения глобальных переменных в одном файле. На практике описания различных классов размещают в отдельных включаемых файлах. А определения методов записывают в программные файлы, имеющие расширение cpp.
Например, мы могли бы поместить описание классов CMFStartApp и CMFStartWindow в файлы MFStartApp.h и MFStartWindow.h. Метод InitInstance класса CMFStartApp и определение глобальной переменной MFStartApp можно поместить в файл MFStartApp.cpp, а определение конструктора класса CMFStartWindow - в файл MFStartWindow.cpp.
Так как в методе InitInstance класса CMFStartApp мы создаем новый объект класса CMFStartWindow, то мы должны включить в файл MFStartApp.cpp не только файл MFStartApp.h но еще и файл MFStartWindow.h. В проект MFStart мы должны записать оба программных файла MFStartApp.cpp и MFStartWindow.cpp. Листинги, представленные ниже содержат проект MFStart, разделенный на несколько файлов. Для того, чтобы сократить размер файлов, мы убрали из них комментарии.
Файл MFStartApp.h содержит описание главного класса приложения CMFStartApp. Этот файл представлен в листинге 2.3.
Листинг 2.3. Файл MFStartApp.h
#include <afxwin.h> class CMFStartApp : public CWinApp { public: virtual BOOL InitInstance(); };
Виртуальный метод InitInstance переопределен нами в файле MFStartApp.cpp. В этом же файле создается объект класса CMFStartApp, представляющий само приложение. Файл MFStartApp.cpp показан в листинге 2.4.
Листинг 2.4. Файл MFStartApp.cpp
#include <afxwin.h> #include "MFStartApp.h" #include "MFStartWindow.h" CMFStartApp MFStartApp; BOOL CMFStartApp::InitInstance() { m_pMainWnd = new CMFStartWindow(); m_pMainWnd -> ShowWindow(m_nCmdShow); m_pMainWnd -> UpdateWindow(); return TRUE; }
Класс окна приложения CMFStartWindow определяется в файле MFStartWindow.h, представленном листингом 2.5. Мы наследуем класс CMFStartWindow от базового класса CFrameWnd.
Листинг 2.5. Файл MFStartWindow.h
#include <afxwin.h> class CMFStartWindow : public CFrameWnd { public: CMFStartWindow(); };
И наконец, последний файл MFStartWindow.cpp модифицированного проекта MFStart показан в листинге 2.6. В этом файле определяется конструктор класса CMFStartWindow.
Листинг 2.6. Файл MFStartWindow.cpp
#include <afxwin.h> #include "MFStartWindow.h" CMFStartWindow::CMFStartWindow() { Create(NULL, "Hello MFC"); }
Как вы знаете из предыдущих томов серии “Библиотека системного программиста”, работа приложений операционной системы Windows основана на обработке сообщений. Когда пользователь работает с устройствами ввода/вывода компьютера, например клавиатурой или мышью, драйверы этих устройств создают сообщения, описывающие его действия. Каждое нажатие на клавиши клавиатуры вызывает генерацию ряда сообщений, определяющих, какая клавиша нажата. Перемещение мыши вызывает сообщения, описывающие траекторию перемещения указателя мыши и т. д. Другие сообщения могут вырабатываться операционной системой или самими приложениями.
Сообщения сначала попадают в системную очереди сообщений операционной системы. Из нее сообщения передаются приложениям, которым они предназначены, и записываются в очередь приложений. Каждое приложение имеет собственную очередь сообщений.
Приложение в цикле, который называется циклом обработки сообщений, получает сообщения из очереди приложения и направляет их соответствующей функции окна, которая и выполняет обработку сообщения. Цикл обработки сообщений обычно состоял из оператора while в котором циклически вызывались функции GetMessage и DispatchMessage:
MSG message; while(GetMessage(&message, 0, 0, 0)) { DispatchMessage(&message); }
Для более сложных приложений цикл обработки сообщений содержал вызовы других функций, например TranslateMessage, TranslateAccelerator. Они обеспечивали предварительную обработку сообщений.
Каждое окно приложения имеет собственную функцию окна. В процессе обработки сообщения операционная система вызывает функцию окна и передает ей структуру, описывающую очередное сообщение.
Функция обработки, сообщения опознает, какое именно сообщение поступило для обработки и выполняет соответствующие действия. Сообщения распознаются по его коду. Обычно функция окна содержит оператор switch, который служит для определения кода сообщений. Вот пример типичной функции окна:
long FAR PASCAL _export WndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam) { HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch(message) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps) ; GetClientRect(hWnd, &rect) ; DrawText(hdc, "Hello, Windows!", -1, &rect, DT_SINGLELINE | DT_CENTER) ; EndPaint(hWnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage(0) ; return 0 ; } return DefWindowProc(hwnd, message, wParam, lParam) ; }
В сложных приложениях функция окна может обрабатывать большое количество разнообразных сообщений и оператор switch может стать очень длинным. Конечно, работать с блоком программного кода, который не помещается на одном экране, очень неудобно.
Чтобы “избавиться” от длинного оператора switch, применялись всевозможные ухищрения. Например, в приложении создавался массив, каждый элемент которого содержал код сообщения и указатель на функцию для его обработки. В функции окна помещался только небольшой программный код, который при получении сообщения просматривал массив, искал для него соответствующий элемент массива и вызывал определенную в нем функцию.
Если вы используете библиотеку классов MFC, то за обработку сообщений отвечают классы. Любой класс, наследованный от базового класса CCmdTarget XE "CCmdTarget", может обрабатывать сообщения. Чтобы класс смог обрабатывать сообщения, необходимо, чтобы он имел таблицу сообщений XE "таблица сообщений" класса. В этой таблице для каждого сообщения указан метод класса, предназначенный для его обработки.
Сообщения, которые могут обрабатываться приложением, построенным на основе библиотеки классов MFC, подразделяются в зависимости от процедуры их обработки на три большие группы. Вот эти группы сообщений:
Эта группа включает сообщения, предназначенные для обработкой функцией окна. Практически все сообщения, идентификаторы которых начинаются префиксом WM_, за исключением сообщения WM_COMMAND XE "WM_COMMAND", относятся к этой группе.
Оконные сообщения предназначаются для обработки объектами, представляющими окна. Это могут быть практически любые объекты класса CWnd XE "CWnd" или классов, наследованных от него, например CFrameWnd XE "CFrameWnd", CMDIFrameWnd XE "CMDIFrameWnd", CMDIChildWnd XE "CMDIChildWnd", CView XE "CView", CDialog XE "CDialog". Характерной чертой этих классов является то, что они включают идентификатор окна.
Большинство этих сообщений имеют параметры, детально характеризующие сообщение. Например сообщение WM_SIZE XE "WM_SIZE" передается окну, когда пользователь меняет его размер и имеет параметры, определяющие новый размер окна.
Эта группа включает в себя сообщения WM_COMMAND XE "WM_COMMAND" от дочерних окон (включая окна стандартных классов), передаваемые их родительскому окну. Сообщения от органов управления обрабатываются точно таким же образом, что и оконные сообщения.
Исключение составляет сообщение WM_COMMAND с кодом извещения BN_CLICKED XE "BN_CLICKED". Это сообщение передается кнопкой, когда пользователь на нее нажимает. Обработка сообщений с кодом извещения BN_CLICKED от органов управления происходит аналогично обработке командных сообщений.
Сообщения WM_COMMAND XE "WM_COMMAND" от меню, кнопок панели управления и клавиш акселераторов. В отличие от оконных сообщений и сообщений от органов управления, командные сообщения могут быть обработаны более широким спектром объектов. Эти сообщения обрабатывают не только объекты, представляющие окна, но также объекты классов, представляющих приложение, документы и шаблон документов.
Характерной особенностью командных сообщений является идентификатор. Идентификатор командного сообщения определяет объект, который вырабатывает (посылает) данное сообщение.
В приложении MFMenu строка Beep меню Test имеет идентификатор ID_TEST_BEEP. Когда пользователь выберет данную строку, в очередь приложения поступит сообщение WM_COMMAND с идентификатором ID_TEST_BEEP или другими словами, командное сообщение ID_TEST_BEEP.
В библиотеке классов MFC для обработки сообщений используется специальный механизм, который получил название Message Map XE "message map" - таблица сообщений XE "таблица сообщений".
Таблица сообщений состоит из набора специальных макрокоманд, ограниченных макрокомандами BEGIN_MESSAGE_MAP XE "BEGIN_MESSAGE_MAP" и END_MESSAGE_MAP XE "END_MESSAGE_MAP". Между ними расположены макрокоманды, отвечающие за обработку отдельных сообщений.
Макрокоманда BEGIN_MESSAGE_MAP представляет собой заголовок таблицы сообщений. Она имеет два параметра. Первый параметр содержит имя класса таблицы сообщений. Второй параметр указывает его базовый класс.
Если в таблице сообщений класса отсутствует обработчик для сообщения, оно передается для обработки базовому классу, указанному вторым параметром макрокоманды BEGIN_MESSAGE_MAP. Если таблица сообщений базового класса также не содержит обработчик этого сообщения, оно передается следующему базовому классу и т. д.
В том случае если ни один из базовых классов не может обработать сообщение, выполняется обработка по умолчанию, зависящая от типа сообщения.
В библиотеке MFC определены несколько макрокоманд, отвечающих за обработку сообщений. Их названия представлены в следующей таблице.
Макрокоманда |
Устанавливает методы для обработки сообщений |
ON_WM_ XE "ON_WM_" <name> |
Стандартных сообщений операционной системы Windows |
ON_REGISTERED_MESSAGE XE "ON_REGISTERED_MESSAGE" |
Зарегистрированные сообщения операционной системы Windows |
ON_MESSAGE XE "ON_MESSAGE" |
Сообщений, определенных пользователем |
ON_COMMAND XE "ON_COMMAND", |
Командных сообщений |
ON_UPDATE_COMMAND_UI XE "ON_UPDATE_COMMAND_UI", |
Сообщений, предназначенных для обновления пользовательского интерфейса |
ON_ XE "ON_" <name>, |
Сообщений от органов управления |
Перечисленные в таблице макрокоманды имеют различное количество параметров в зависимости от типа обрабатываемых ими сообщений.
Обрабатывает стандартные сообщения операционной системы Windows. Вместо <name> указывается имя сообщения без префикса WM_. Так, например для обработки сообщения WM_SIZE XE "WM_SIZE" предназначена макрокоманда ON_WM_SIZE.
Для обработки сообщений, определенных в таблице сообщений макрокомандами ON_WM_<name>, вызываются одноименные методы. Имя метода обработчика соответствует названию сообщения, без учета префикса WM_.
В классе CWnd XE "CWnd" определены обработчики для стандартных сообщений. Эти обработчики будут использоваться по умолчанию. Вот некоторые из таких методов.
Сообщение |
Макрокоманда |
Метод обработчик |
WM_CHAR |
ON_WM_CHAR() |
afx_msg void OnChar(UINT, UINT, UINT); |
WM_CREATE |
ON_WM_CREATE() |
afx_msg int OnCreate(LPCREATESTRUCT ); |
WM_HSCROLL |
ON_WM_HSCROLL() |
afx_msg void OnHScroll(UINT, UINT, CWnd*); |
WM_KEYDOWN |
ON_WM_KEYDOWN() |
afx_msg void OnKeyDown(UINT, UINT, UINT); |
WM_KEYUP |
ON_WM_KEYUP() |
afx_msg void OnKeyUp(UINT, UINT, UINT); |
WM_LBUTTONDOWN |
ON_WM_LBUTTONDOWN() |
afx_msg void OnLButtonDown(UINT, CPoint); |
WM_LBUTTONUP |
ON_WM_LBUTTONUP() |
afx_msg void OnLButtonUp(UINT, CPoint); |
WM_PAINT |
ON_WM_PAINT() |
afx_msg void OnPaint(); |
WM_SIZE |
ON_WM_SIZE() |
afx_msg void OnSize(UINT, int, int); |
WM_TIMER |
ON_WM_TIMER() |
afx_msg void OnTimer(UINT); |
WM_VSCROLL |
ON_WM_VSCROLL() |
afx_msg void OnVScroll(UINT, UINT, CWnd*); |
Все методы-обработчики определены с ключевым словом afx_msg. Оно позволяет отличить эти методы от остальных методов класса. На этапе препроцессорной обработки ключевое слово afx_msg удаляется. Определение afx_msg вы можете найти в файле afxwin.h:
#define afx_msg XE "afx_msg"
Макрокоманды ON_WM_<name> не имеют параметров. Однако методы, которые вызываются для обработки соответствующих сообщений, имеют параметры, количество и назначение которых зависит от обрабатываемого сообщения.
Когда вы определяете обработчик стандартного сообщения Windows в своем классе, он будет использоваться вместо обработчика определенного в классе CWnd (или другом базовом классе). В любом случае вы можете вызвать метод обработчик базового класса из своего метода обработчика.
Макрокоманда ON_REGISTERED_MESSAGE XE "ON_REGISTERED_MESSAGE" обслуживает сообщения операционной системы Windows, зарегистрированные с помощью функции RegisterWindowMessage. Параметр nMessageVariable указывает идентификатор сообщения, для которого будет вызываться метод memberFxn.
ON_REGISTERED_MESSAGE(nMessageVariable, memberFxn)
Макрокоманда ON_MESSAGE XE "ON_MESSAGE" обрабатывает сообщения, определенные пользователем. Идентификатор сообщения (его имя) указывается параметром message. Метод, который вызывается для обработки сообщения, указывается параметром memberFxn.
ON_MESSAGE(message, memberFxn)
Макрокоманды ON_COMMAND XE "ON_COMMAND" предназначены для обработки командных сообщений. Командные сообщения поступают от меню, кнопок панели управления и клавиш акселераторов. Характерной особенностью командных сообщений является то, что с ними связан идентификатор сообщения.
Макрокоманда ON_COMMAND имеет два параметра. Первый параметр соответствует идентификатору командного сообщения, а второй имени метода, предназначенного для обработки этого сообщения. Таблица сообщений должна содержать не больше одной макрокоманды для командного сообщения.
ON_COMMAND(id, memberFxn)
В общем случае командные сообщения не имеют обработчиков, используемых по умолчанию. Существует только небольшая группа стандартных командных сообщений, имеющих методы обработчики, вызываемые по умолчанию. Эти сообщения соответствуют стандартным строкам меню приложения. Так, например, если вы (или MFC AppWizard) присвоите строке Open меню File идентификатор ID_FILE_OPEN, то для его обработки будет вызван метод OnFileOpen, определенный в классе CWinApp. Список стандартных командных сообщений и их описание представлены в главах “Однооконный интерфейс” и “Многооконный интерфейс”.
Принято, что методы обработчики командных сообщений (как и методы обработчики всех других сообщений) должны быть определены с ключевым словом afx_msg.
Макрокоманда ON_COMMAND ставит в соответствие одному командному сообщению один метод-обработчик. В некоторых случаях более удобно, когда один и тот же метод-обработчик применяется для обработки сразу нескольких командных сообщений с различными идентификаторами. Для этого предназначена макрокоманда ON_COMMAND_RANGE XE "ON_COMMAND_RANGE".
Она назначает один метод memberFxn для обработки ряда командных сообщений, идентификаторы которых лежат в интервале от id1 до id2.
ON_COMMAND_RANGE(id1, id2, memberFxn)
Макрокоманда ON_UPDATE_COMMAND_UI XE "ON_UPDATE_COMMAND_UI" обрабатывает сообщения, предназначенные для обновления пользовательского интерфейса, например меню, панелей управления и позволяет менять их состояние.
Параметр id указывает идентификатор сообщения, а параметр memberFxn имя метода для его обработки.
ON_UPDATE_COMMAND_UI(id, memberFxn)
Методы, предназначенные для обработки данного класса сообщений, должны быть определены с ключевым словом afx_msg XE "afx_msg" и иметь один параметр - указатель на объект класса CCmdUI XE "CCmdUI". Для удобства, имена методов, предназначенных для обновления пользовательского интерфейса, начинаются с OnUpdate:
afx_msg void OnUpdate<имя_обработчика>(CCmdUI* pCmdUI);
В качестве параметра pCmdUI методу передается указатель на объект класса CCmdUI. В нем содержится информация о объекте пользовательского интерфейса, который надо обновить - строке меню или кнопке панели управления. Класс CCmdUI также включает методы, позволяющие изменить внешний вид представленного им объекта пользовательского интерфейса.
Сообщения, предназначенные для обновления пользовательского интерфейса передаются, когда пользователь открывает меню приложения и во время цикла ожидания приложения, когда очередь сообщений приложения становится пуста.
При этом посылается несколько сообщений, по одному для каждой строки меню. С помощью макрокоманд ON_UPDATE_COMMAND_UI можно определить методы-обработчики, ответственные за обновление внешнего вида каждой строки меню и соответствующей ей кнопке на панели управления. Эти методы могут изменять состояние строки меню - отображать ее серым цветом, запрещать ее выбор, отображать около нее символ v и т. д.
Если вы не определите метод для обновления данной строки меню или кнопки панели управления, выполняется обработка по умолчанию. Выполняется поиск обработчика соответствующего командного сообщения и если он не обнаружен, выбор строки меню запрещается.
Дополнительную информацию об обновлении пользовательского интерфейса приложения вы можете получить в разделе “Однооконный интерфейс”.
Макрокоманда ON_UPDATE_COMMAND_UI_RANGE XE "ON_UPDATE_COMMAND_UI_RANGE" обеспечивает обработку сообщений, предназначенных для обновления пользовательского интерфейса, идентификаторы которых лежат в интервале от id1 до id2. Параметр memberFxn указывает метод используемый для обработки.
ON_UPDATE_COMMAND_UI_RANGE(id1, id2, memberFxn)
Макрокоманды ON_ XE "ON_" <name> предназначены для обработки сообщений от органов управления. Такие сообщения могут передаваться органами управления диалоговой панели. Сообщения от органов управления не имеют обработчиков, используемых по умолчанию. При необходимости вы должны определить их самостоятельно.
Все макрокоманды ON_<name> имеют два параметра. В первом параметре id указывается идентификатор органа управления. Сообщения от этого органа управления будут обрабатываться методом memberFxn.
ON_BN_CLICKED(id, memberFxn)
Макрокоманда ON_CONTROL_RANGE XE "ON_CONTROL_RANGE" обрабатывает сообщения, идентификаторы которых находятся в интервале от id1 до id2. Параметр wNotifyCode содержит код извещения. Метод-обработчик указывается параметром memberFxn.
ON_CONTROL_RANGE(wNotifyCode, id1, id2, memberFxn)
Вы, конечно, можете определить таблицу сообщений класса вручную, однако наиболее удобно воспользоваться для этой цели средствами ClassWizard. ClassWizard не только позволит в удобной форме выбрать сообщения, которые должен обрабатывать ваш класс. Он включит в состав класса соответствующие методы-обработчики. Вам останется только вставить в них необходимый код. К сожалению использовать все возможности ClassWizard XE "ClassWizard" можно только в том случае, если приложение создано с использованием средств автоматизированного программирования MFC AppWizard XE "MFC AppWizard".
Однако ClassWizard не всесилен. Так он не позволяет определить один метод для обработки нескольких сообщений. Как вы уже знаете, для этих целей предназначены макрокоманды ON_COMMAND_RANGE и ON_CONTROL_RANGE. Если вы решите воспользоваться этими макрокомандами, вам придется редактировать таблицу сообщений непосредственно, без использования ClassWizard.
Более подробно об использовании ClassWizard для создания обработчиков сообщений вы можете прочитать в разделе “Средства ClassWizard” главы “Приложение с главной диалоговой панелью”. А сейчас мы рассмотрим механизм обработки сообщений, используемый MFC, на примере нескольких приложений.
Приложения, которые мы рассматривали до сих пор, фактически никак не могли взаимодействовать с пользователем. Они не имели ни меню, ни панели управления. И самое главное - они не содержали обработчиков сообщений.
Сейчас мы рассмотрим приложение MFMenu, которое имеет меню и содержит обработчики сообщений, передаваемых приложению, когда пользователь открывает меню и выбирает из него строки. Для создания приложения MFMenu мы также не станем пользоваться средствами автоматизированной разработки MFC AppWizard, и наберем текст приложения вручную. Этот позволит вам лучше разобраться с механизмом сообщений.
Создайте новый проект под названием MFMenu. В качестве типа приложения выберите из списка Type строку Application (рис. 4.1 из главы “Приложение с главной диалоговой панелью”). Наберите в редакторе исходный текст приложения и сохраните его в файле MFMenu.cpp (листинг 2.7). Чтобы быстрее набрать текст приложения, вы можете получить его, изменив исходный текст приложения MFStart. Затем включите этот файл в проект.
Листинг 2.7. Файл MFMenu.cpp
// Включаемый файл для MFC #include <afxwin.h> #include "MFMenuRes.h" //===================================================== // Класс CMFMenuApp - главный класс приложения //===================================================== class CMFMenuApp : public CWinApp { public: // Мы будем переопределять метод InitInstance, // предназначенный для инициализации приложения virtual BOOL InitInstance(); }; // Создаем объект приложение класса CMFMenuApp CMFMenuApp MFMenuApp; //===================================================== // Класс CMFMenuWindow - представляет главное окно //===================================================== class CMFMenuWindow : public CFrameWnd { public: // Объявляем конструктор класса CMFMenuWindow CMFMenuWindow(); // Объявляем методы для обработки команд меню afx_msg void MenuCommand(); afx_msg void ExitApp(); // Макрокоманда необходима, так как класс // CMFMenuWindow обрабатывает сообщения DECLARE_MESSAGE_MAP() }; //===================================================== // Метод MenuCommand // Обрабатывает команду ID_TEST_BEEP //===================================================== void CMFMenuWindow::MenuCommand() { MessageBeep(0); } //===================================================== // Метод ExitApp //===================================================== void CMFMenuWindow::ExitApp() { DestroyWindow(); } //===================================================== // Таблица сообщений класса CMFMenuWindow //===================================================== BEGIN_MESSAGE_MAP(CMFMenuWindow, CFrameWnd) ON_COMMAND(ID_TEST_BEEP, MenuCommand) ON_COMMAND(ID_TEST_EXIT, ExitApp) END_MESSAGE_MAP() //===================================================== // Метод InitInstance класса CMFMenuApp //===================================================== BOOL CMFMenuApp::InitInstance() { // Создаем объект класса CMFMenuWindow m_pMainWnd = new CMFMenuWindow(); // Отображаем окно на экране m_pMainWnd -> ShowWindow(m_nCmdShow); // Обновляем содержимое окна m_pMainWnd -> UpdateWindow(); return TRUE; } //===================================================== // Конструктор класса CMFMenuWindow //===================================================== CMFMenuWindow::CMFMenuWindow() { // Создаем окно приложения, соответствующее // данному объекту класса CMFMenuWindow Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault, NULL, MAKEINTRESOURCE(IDR_MENU)); }
Добавить в проект новый ресурс очень просто. Вы можете для этого воспользоваться кнопками панели управления Progect или установить указатель мыши на название типа ресурса и нажать правую кнопку мыши. На экране появится временное меню свойств данного ресурса.
Если у вас уже есть ресурсы, которые разработаны ранее и записаны в отдельных файлах на диске, вы можете подключить их к своему проекту. Для этого надо выбрать из временного меню свойств ресурса строку Import. На экране появится диалоговая панель Import Resource с приглашением ввести имя файла подключаемого вами ресурса. Новый ресурс проекта будет записан в подкаталог RES, расположенный в каталоге проекта.
Сам по себе новый ресурс, подключенный к приложению, не принесет никакой пользы. Ваше приложение должно обращаться к нему - загружать в память, обрабатывать сообщения, связанные с этим ресурсом
Среда Visual C++ версии 4.0 не только позволяет просматривать и редактировать ресурсы приложения, она позволяет сразу после создания ресурса вызвать ClassWizard и подключить к ресурсу управляющий программный код. Пока мы не будем использовать ClassWizard, а код, управляющий ресурсами приложения, создадим вручную.
Так как наше приложение будет содержать меню, мы приступим к созданию ресурсов приложения. Для этого создайте новый файл ресурсов. Выберите из меню File строку New, а затем из открывшейся диалоговой панели выберите строку Resource Script и нажмите кнопку OK.
Теперь надо создать меню. Выберите из меню Insert строку Resource. На экране появится диалоговая панель Insert Resource. Выберите из нее строку Menu и нажмите кнопку OK. Вы сможете в диалоговом режиме разработать меню. Чтобы быстрее перейти к редактированию меню, вы можете нажать кнопку New Menu () из панели управления Project или просто нажать комбинацию кнопок <Ctrl+2>.
Создайте меню, содержащее единственную строку Test, при выборе которой открывается меню, содержащее три строки - Beep и Exit. Внешний вид меню во время разработки представлен на рисунке 2.22.
Рис. 2.22. Разработка меню приложения
Когда вы создаете новое меню верхнего уровня или определяете строки, входящие в это меню, на экране появляется диалоговое окно Menu Item Properties (рис. 2.23). В нем полностью описывается выбранный элемент меню. В поле Caption задается название меню или строки меню. В названии вы можете вставить символ &. Он будет означать, что символ, следующий за ним, можно будет использовать для быстрого выбора данного элемента меню. Например, если перед названием строки меню Beep поставить символ &, то во время работы приложения символ B будет отображаться с подчеркиванием, и строку Beep можно будет выбрать, нажав комбинацию клавиш <Alt+B>.
Рис. 2.23. Диалоговая панель Menu Item Properties
Как вы знаете, каждый элемент меню должен иметь уникальный идентификатор, однозначно его определяющий. Вы можете задать идентификатор в поле ID или выбрать его из списка предопределенных идентификаторов.
Редактор ресурсов сам предлагает вам название идентификатора, создавая его из названия главного меню и строки меню. Так например строке Beep меню Test по умолчанию будет присвоен идентификатор ID_TEST_BEEP.
В поле Prompt можно в нескольких словах описать данный пункт меню. Введенная вами строка будет записана в строковый ресурс. Ей будет присвоен тот же самый идентификатор, что и пункту меню. Приложение может использовать строку описания, чтобы предоставить пользователю краткую справку о меню.
Если вы разрабатываете приложение с помощью средств MFC AppWizard, то при выборе пользователем строк меню их описание будет появляться в панели состояния StatusBar. Что интересно, для этого вам не потребуется написать ни одной строки текста программы. Это будет сделано автоматически благодаря возможностям библиотеки классов MFC и MFC AppWizard.
Остальные переключатели диалоговой панели Menu Item Properties описаны в следующей таблице.
Переключатель |
Описание |
Break |
Этот переключатель может принимать три различных значения - None, Column и Bar. Он позволяет задать структуру меню. |
Checked |
Если установить переключатель, то строка меню будет выделена символом . Потом, обращаясь к специальным методам, вы сможете удалять или отображать этот символ |
Grayed |
Если включить переключатель Grayed, тогда пункт меню будет отображаться серым цветом и будет недоступен для выбора пользователем. Такая возможность удобна, если вам надо сделать недоступным какие-либо возможности приложения. Впоследствии приложение может сделать пункт меню доступным. Для этого надо вызвать соответствующий метод |
Help |
Если установить переключатель Help, тогда для него будет установлено выравнивание по правому краю. Обычно этот переключатель устанавливают для меню верхнего уровня, которое управляет справочной системной приложения |
Inactive |
Если включен переключатель Grayed, тогда переключатель недоступен. В противном случае вы можете установить переключатель Inactive. В этом случае пункт меню будет неактивен |
Pop-up |
Вы можете создавать многоуровневые меню. Если вы включите переключатель Pop-up, то данный пункт меню будет являться меню верхнего уровня, которое можно открыть. По умолчанию, все пункты главного меню имеют установленный переключатель Pop-up. Так как меню верхнего уровня служит только для объединения других пунктов меню, то оно не имеет идентификатора |
Separator |
Если переключатель установлен, тогда в меню вставляется разделитель. Для разделителя все остальные поля и переключатели диалоговой панели не используются |
Сохраните файл ресурсов в файле с именем MFMenu.rc. Редактор ресурсов создает кроме самого файла ресурсов еще включаемый файл, в котором определяются константы, используемые в файле ресурсов. В нашем случае в нем определяются идентификаторы меню приложения. По умолчанию этот файл сохраняется под именем resource.h XE "resource.h". Вы можете изменить его, выбрав из меню View строку Resource Includes. Для нашего приложения мы изменили имя включаемого файла для ресурсов на MFMenuRes.h. Содержимое этого файла представлено листингом 2.8.
Листинг 2.8. Файл MFMenuRes.h
//{{NO_DEPENDENCIES}} // Включаемый файл, созданный Microsoft Developer Studio // Используется в файле ресурсов MFMenu.rc // #define IDR_MENU 101 #define ID_TEST_BEEP 40001 #define ID_TEST_EXIT 40002 // Следующие значения идентификаторов используются по // умолчанию для новых объектов #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40003 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
В листинге 2.9 мы привели файл ресурсов MFMenuRes.rc приложения. Этот файл был подготовлен редактором ресурсов Visual C++. Одна из первых строк файла содержит директиву #include которой подключается файл MFMenuRes.h, содержащий описание идентификаторов ресурсов (листинг 2.8).
Среди прочих служебных строк, необходимых редактору ресурсов и компилятору Visual C++, вы можете обнаружить описание меню приложения IDR_MENU. Для первого приложения, использующего ресурсы мы привели файл ресурсов полностью. Впоследствии мы ограничимся словесным описанием ресурсов и будем приводить только выдержки из файла ресурсов.
Листинг 2.9. Файл MFMenuRes.rc
// Файл описания ресурсов приложения, созданный // Microsoft Developer Studio #include "MFMenuRes.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Включаем файл afxres.h, содержащий определения стандартных // идентификаторов #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Руссификацированные ресурсы #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) #ifdef _WIN32 LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT #pragma code_page(1251) #endif //_WIN32 //////////////////////////////////////////////////////////// // Меню // IDR_MENU MENU DISCARDABLE BEGIN POPUP "Test" BEGIN MENUITEM "Beep", ID_TEST_BEEP MENUITEM SEPARATOR MENUITEM "Exit", ID_TEST_EXIT END END #ifdef APSTUDIO_INVOKED //////////////////////////////////////////////////////// // Ресурсы TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "MFMenuRes.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0\" END #endif // APSTUDIO_INVOKED #endif // Руссификацированные ресурсы ////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED #endif
Когда вы создадите ресурсы приложения и включите файл ресурсов в проект обратите внимание на окно Project Workspace. В нем появится еще одна, четвертая страница ResourceView (рис. 2.24). Эта страница показывает все ресурсы, входящие в проект. В приложении MFMenu определен только один ресурс - меню, имеющее идентификатор IDR_MENU.
Вы можете быстро перейти к редактированию меню, если выберите его идентификатор и щелкните два раза левой кнопкой мыши.
Рис. 2.24. Страница ResourceView окна Project Workspace
Теперь проект готов. Вы можете построить его и запустить полученное приложение MFMenu. Внешний вид приложения представлен на рисунке 2.25. Как видите окно приложения имеет меню Test, состоящее из двух строк - Beep и Exit.
Если вы выберите строку Beep из меню Test, то услышите на внутреннем динамике компьютера звуковой сигнал. В случае если звуковой сигнал не слышен, проверьте подключен ли внутренний динамик, а если в компьютере установлена звуковая плата, правильно установите громкость сигнала.
Когда вы завершите работу с приложением, его можно закрыть. Для этого воспользуйтесь системным меню приложения или выберите из меню Test строку Exit.
Рис. 2.25. Приложение MFMenu
Чтобы объекты класса могли обрабатывать сообщения, в определении этого класса необходимо поместить макрокоманду DECLARE_MESSAGE_MAP. По принятым соглашениям эта макрокоманда должна записываться в конце определения класса в секции public.
//===================================================== // Класс CMFMenuWindow - представляет главное окно //===================================================== class CMFMenuWindow : public CFrameWnd { public: // Объявляем конструктор класса CMFMenuWindow CMFMenuWindow(); // Объявляем методы для обработки команд меню afx_msg void MenuCommand(); afx_msg void ExitApp(); // Макрокоманда необходима, так как класс // CMFMenuWindow обрабатывает сообщения DECLARE_MESSAGE_MAP() };
Однако это еще не все. Необходимо также определить таблицу сообщений. Таблица начинается макрокомандой BEGIN_MESSAGE_MAP и заканчивается макрокомандой END_MESSAGE_MAP. Между этими макрокомандами расположены строки таблицы сообщений, определяющие сообщения, подлежащие обработке данным классом и методы, которые выполняют такую обработку.
Приложение может содержать несколько классов, обладающих собственными таблицами сообщений. В следующем разделе мы приведем пример такого приложения. Чтобы однозначно определить класс, к которому относится таблица сообщений, имя этого класса записывается в первый параметр макрокоманды BEGIN_MESSAGE_MAP XE "BEGIN_MESSAGE_MAP".
Приложение MFMenu обрабатывает только две команды от меню приложения. Первая команда имеет идентификатор ID_TEST_BEEP и передается, когда пользователь выбирает из меню Test строку Beep. Для обработки этой команды вызывается метод MenuCommand. Вторая команда с идентификатором ID_TEST_EXIT передается приложению, когда пользователь выбирает из меню Test строку Exit. Обработка этого сообщения выполняется методом ExitApp.
//===================================================== // Таблица сообщений класса CMFMenuWindow //===================================================== BEGIN_MESSAGE_MAP(CMFMenuWindow, CFrameWnd) ON_COMMAND(ID_TEST_BEEP, MenuCommand) ON_COMMAND(ID_TEST_EXIT, ExitApp) END_MESSAGE_MAP()
Конечно, приложению MFMenu может поступать гораздо больше сообщений и команд, чем указано в таблице сообщений класса CMFMenuWindow. Необработанные сообщения передаются для обработки базовому классу CMFMenuWindow - классу CFrameWnd XE "CFrameWnd". Класс, который будет обрабатывать сообщения, не указанные в таблице сообщений, указывается во втором параметре макрокоманды BEGIN_MESSAGE_MAP.
В предыдущем примере мы изучили основы метода обработки сообщений, рассмотрели принципы построения таблицы сообщений класса. Теперь мы приступим к более детальному рассмотрению механизма обработки сообщений для приложений MFC.
Создайте новый проект под названием MFMessage. В качестве типа приложения выберите из списка Type строку Application (рис. 4.1). Наберите в редакторе исходный текст приложения и сохраните его в файле MFMessage.cpp (листинг 2.10). Чтобы быстрее набрать текст приложения, вы можете модифицировать исходный текст приложения MFMenu.
Листинг 2.10. Файл MFMessage.cpp
// Включаемый файл для MFC #include <afxwin.h> #include "resource.h" //===================================================== // Класс CMFMessageApp - главный класс приложения //===================================================== class CMFMessageApp : public CWinApp { public: // Мы будем переопределять метод InitInstance, // предназначенный для инициализации приложения virtual BOOL InitInstance(); afx_msg void AppMessageCommand(); // Макрокоманда необходима, так как класс // CMFMessageWindow обрабатывает сообщения DECLARE_MESSAGE_MAP() }; // Создаем объект приложение класса CMFMessageApp CMFMessageApp MFMessageApp; //===================================================== // Класс CMFMessageWindow - представляет главное окно //===================================================== class CMFMessageWindow : public CFrameWnd { public: // Объявляем конструктор класса CMFMessageWindow CMFMessageWindow(); // Объявляем методы для обработки команд меню afx_msg void FrameMessageCommand(); afx_msg void ExitApp(); // Макрокоманда необходима, так как класс // CMFMessageWindow обрабатывает сообщения DECLARE_MESSAGE_MAP() }; //===================================================== // Метод MessageCommand // Обрабатывает команду ID_TEST_BEEP //===================================================== void CMFMessageWindow::FrameMessageCommand() { ::MessageBox(NULL, "Command received in CMFMessageWindow Message Map", "Message", MB_OK); } //===================================================== // Метод MessageCommand // Обрабатывает команду ID_TEST_BEEP //===================================================== void CMFMessageApp::AppMessageCommand() { ::MessageBox(NULL, "Command received in CMFMessageApp Message Map", "Message", MB_OK); } //===================================================== // Таблица сообщений класса CMFMessageWindow //===================================================== BEGIN_MESSAGE_MAP(CMFMessageWindow, CFrameWnd) ON_COMMAND(ID_TEST_INFRAMECLASS, FrameMessageCommand) ON_COMMAND(ID_TEST_INBOTHCLASS, FrameMessageCommand) END_MESSAGE_MAP() //===================================================== // Таблица сообщений класса CMFMessageApp //===================================================== BEGIN_MESSAGE_MAP(CMFMessageApp, CWinApp) ON_COMMAND(ID_TEST_INAPPCLASS, AppMessageCommand) ON_COMMAND(ID_TEST_INBOTHCLASS, AppMessageCommand) END_MESSAGE_MAP() //===================================================== // Метод InitInstance класса CMFMessageApp //===================================================== BOOL CMFMessageApp::InitInstance() { // Создаем объект класса CMFMessageWindow m_pMainWnd = new CMFMessageWindow(); // Отображаем окно на экране m_pMainWnd -> ShowWindow(m_nCmdShow); // Обновляем содержимое окна m_pMainWnd -> UpdateWindow(); return TRUE; } //===================================================== // Конструктор класса CMFMessageWindow //===================================================== CMFMessageWindow::CMFMessageWindow() { // Создаем окно приложения, соответствующее // данному объекту класса CMFMessageWindow Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault, NULL, MAKEINTRESOURCE(IDR_MENU)); }
Используя редактор ресурсов, создайте файл ресурсов и включите в него меню Test, содержащее четыре строки, имеющие идентификаторы, описанные в следующей таблице. Присвойте меню идентификатор IDR_MENU. Затем включите файл ресурсов в проект.
Строка меню Test |
Идентификатор |
In Frame Class |
ID_TEST_INFRAMECLASS |
In App Class |
ID_TEST_INAPPCLASS |
In Both Class |
ID_TEST_INBOTHCLASS |
Exit |
ID_APP_EXIT |
В листинге 2.11 представлен фрагмент файла ресурсов MFMessage.rc, в котором определяется меню приложения. Чтобы ускорить разработку меню, вы можете скопировать меню приложения MFMenu и изменить его соответствующим образом.
Листинг 2.11. Фрагмент файла MFMessage.rc
////////////////////////////////////////////////////////////// // Меню // IDR_MENU MENU DISCARDABLE BEGIN POPUP "Test" BEGIN MENUITEM "In Frame Class", ID_TEST_INFRAMECLASS MENUITEM "In App Class", ID_TEST_INAPPCLASS MENUITEM "In Both Class", ID_TEST_INBOTHCLASS MENUITEM "Exit", ID_APP_EXIT END END
Идентификаторы, необходимые для файла ресурсов, записываются в файл resource.h, показанный в листинге 2.12. Этот файл создается автоматически редактором ресурсов. Все что вы должны сделать - это включить его в исходный текст приложения - файл MFMessage.cpp.
Листинг 2.12. Файл resource.h
//{{NO_DEPENDENCIES}} // Включаемый файл, созданный Microsoft Developer Studio // Используется в файле ресурсов MFMessage.rc // #define IDR_MENU 101 #define ID_TEST_BEEP 40001 #define ID_TEST_EXIT 40002 #define ID_TEST_INAPPCLASS 40003 #define ID_TEST_INFRAMECLASS 40004 #define ID_TEST_INBOTHCLASS 40006 // Следующие значения идентификаторов используются по // умолчанию для новых объектов #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_3D_CONTROLS 1 #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40007 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Укажите в проекте MFMessage, что приложение использует библиотеку классов MFC и постройте проект. Запустите полученный выполнимый файл. На экране появится главное окно приложение, имеющее меню Test (рис. 2.26).
Рис. 2.26. Приложение MFMessage
Когда вы выбираете строки из меню Test, приложению передаются команды с соответствующими идентификаторами. Обратите внимание, что команда ID_TEST_INFRAMECLASS обрабатывается в классе окна CMFMessageWindow, команда ID_TEST_INAPPCLASS в главном классе приложения CMFMessageApp. Команда ID_TEST_INBOTHCLASS содержит два обработчика - один в классе окна, другой в классе приложения, зато команда ID_APP_EXIT XE "ID_APP_EXIT" не имеет обработчика совсем.
Попробуйте последовательно выбирать все строки из меню Test. При выборе первых трех строк на экране появляется сообщение, содержащее название класса в котором обработано сообщение. Если выбрать из меню Test строку Exit приложение завершится не смотря на то, что команда ID_APP_EXIT, соответствующая этой строке меню вообще не имеет обработчика в исходном тексте нашего приложения.
Поэкспериментируйте с приложением MFMessage. Очень скоро вы обнаружите, что команды обрабатываются не только классом окна, как это было в приложении MFMenu, но также и главным классом приложения. Те команды которые не имеют обработчика в таблице сообщений класса окна, передаются для обработке в класс приложения.
Если же команда может быть обработана и в классе окна и в классе приложения, она обрабатывается только один раз в классе окна. Обработчик класса приложения в этом случае не вызывается.
Широкие возможности для управления обработкой сообщений предоставляет ClassWizard. Мы расскажем более подробно об обработке сообщений и средствах ClassWizard в следующей главе.
Очень удобным средством для организации взаимодействия пользователя и приложения являются диалоговые панели. Более того, многие приложения могут успешно работать и без главного окна, взаимодействуя с пользователем только через диалоговые панели. Примером такого приложения может служить приложение Scandisk, входящее в состав операционной системы Windows 95.
Библиотека классов MFC содержит класс CDialog XE "CDialog", специально предназначенный для управления диалоговыми панелями. Как мы рассказывали в предыдущих томах серии “Библиотека системного программиста”, посвященных программированию для операционных систем Windows и Windows 95, диалоговые панели XE "диалоговые панели" бывают двух типов - модальные и немодальные.
После отображения модальных диалоговых панелей блокируется родительское окно приложения и все его дочерние окна. Пользователь не может продолжить работу с приложением, пока не закроет модальную диалоговую панель.
Немодальные диалоговые панели не блокируют работу остальных окон приложения. Поэтому, открыв такую панель вы можете продолжить работать с приложением - использовать меню, открывать другие дочерние окна и диалоговые панели.
Как ни странно, и модальные и немодальные диалоговые панели обслуживаются одним (общим) классом CDialog, наследованным от базового класса CWnd XE "CWnd" (рис. 2.27).
Рис. 2.27. Класс CDialog
В библиотеке MFC версии 1.0 для немодальных диалоговых панелей был предназначен отдельный класс CModalDialog XE "CModalDialog". Однако, начиная с MFC версии 2.0 он включен в класс CDialog. Для совместимости класс CModalDialog также оставлен, но он определен макрокомандой #define как CDialog (файл Afxwin.h):
#define CModalDialog CDialog
Как создать и отобразить на экране диалоговую панель? В первую очередь необходимо добавить в файл ресурсов приложения шаблон новой диалоговой панели и при помощи редактора ресурсов изменить его по своему усмотрению.
Следующим этапом создается класс для управления диалоговой панелью. Этот класс наследуется непосредственно от базового класса CDialog.
Каждая диалоговая панель обычно содержит несколько органов управления. Работая с диалоговой панелью, пользователь взаимодействует с этими органами управления - нажимает кнопки, вводит текст, выбирает элементы списков. В результате генерируются соответствующие сообщения, которые должны быть обработаны классом диалоговой панели.
Так как класс диалоговой панели обрабатывает сообщения, то он содержит таблицу сообщений и соответствующие методы обработчики сообщений.
Чтобы создать модальную диалоговую панель, сначала необходимо создать объект определенного вами класса диалоговой панели, а затем вызвать метод DoModal, определенный в классе CDialog.
Процедура создания немодальной диалоговой панели несколько другая. Для этого используется метод Create класса CDialog XE "CDialog:Create". Мы рассмотрим создание немодальных диалоговых панелей позже.
В этом разделе мы расскажем о том, как создать простейшее приложение с единственной диалоговой панелью. Диалоговая панель будет содержать несколько кнопок, статическое текстовое поле и поле редактирования. В следующей главе мы расскажем, как создать более сложное приложение с главной диалоговой панелью при помощи средств автоматизированного проектирования MFC AppWizard и ClassWizard.
Создайте новый проект под названием MFDialog. В качестве типа приложения выберите из списка Type строку Application (рис. 4.1). Наберите в редакторе исходный текст приложения и сохраните его в файле MFDialog.cpp (листинг 2.13).
Листинг 2.13. Файл MFDialog.cpp
// Включаемый файл для MFC #include <afxwin.h> #include "resource.h" //===================================================== // Класс CMFDialogApp - главный класс приложения //===================================================== class CMFDialogApp : public CWinApp { public: // Мы будем переопределять метод InitInstance, // предназначенный для инициализации приложения virtual BOOL InitInstance(); }; // Создаем объект приложение класса CMFDialogApp CMFDialogApp MFDialogApp; //===================================================== // Класс CMyDialog - класс диалоговой панели //===================================================== class CMyDialog : public CDialog { public: CMyDialog(); CString m_Text; protected: virtual void DoDataExchange(CDataExchange* pDX); // Обработчики сообщений от кнопок диалоговой панели afx_msg void OnDefault(); virtual void OnCancel(); virtual void OnOK(); // Макрокоманда необходима, так как класс // CMyDialog обрабатывает сообщения от органов // управления диалоговой панели DECLARE_MESSAGE_MAP() }; // Конструктор клаасса CMyDialog CMyDialog::CMyDialog() : CDialog(CMyDialog::IDD) { // Инициализируем переменную m_Text m_Text = ""; } //===================================================== // Метод DoDataExchange класса CMyDialog //===================================================== void CMyDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT, m_Text); } //===================================================== // Таблица сообщений класса CMyDialog //===================================================== BEGIN_MESSAGE_MAP(CMyDialog, CDialog) ON_BN_CLICKED(IDC_DEFAULT, OnDefault) END_MESSAGE_MAP() //===================================================== // Метод OnDefault класса CMyDialog //===================================================== void CMyDialog::OnDefault() { // TODO: m_Text = "Start Text"; UpdateData(FALSE); MessageBeep(0); } //===================================================== // Метод OnCancel класса CMyDialog //===================================================== void CMyDialog::OnCancel() { // Подаем звуковой сигнал MessageBeep(0); // Вызываем метод OnCancel базового класса CDialog::OnCancel(); } //===================================================== // Метод OnOK класса CMyDialog //===================================================== void CMyDialog::OnOK() { // Вызываем метод OnOK базового класса CDialog::OnOK(); // Подаем звуковой сигнал MessageBeep(0); } //===================================================== // Метод InitInstance класса CMFDialogApp //===================================================== BOOL CMFDialogApp::InitInstance() { // Создаем объект класса CMyDialog CMyDialog dlgTest; m_pMainWnd = &dlgTest; // Отображаем на экране модельную диалоговую панель dlgTest.DoModal(); // Отображаем на экране значение переменной m_Text, // ввходящей в класс CMyDialog AfxMessageBox(dlgTest.m_Text); return FALSE; }
Создайте файл ресурсов MFDlgRes.rc и добавьте в него новую диалоговую панель. На экране откроется окно редактора диалоговой панели и панель с инструментами Controls (рис. 2.28). По умолчанию новая диалоговая панель называется Dialog и содержит две кнопки OK и Cancel.
Вы можете добавлять в диалоговую панель другие органы управления - кнопки, переключатели, поля редактирования, статические текстовые поля, рисунки. Более того в Visual C++ версии 4.0 вам становятся доступны новые органы управления - многостраничные диалоговые панели, поля для просмотра видеоинформации и т. д.
Рис. 2.28. Создание диалоговой панели
В следующей таблице мы кратко описали органы управления диалоговой панели, которые можно добавлять с помощью панели инструментов Controls.
Кнопка |
Название |
Описание |
|
Select |
Если вы нажмете эту кнопку, то сможете выбрать органы управления, которые уже расположены в диалоговой панели |
|
Picture |
Рисунок |
|
Static Text |
Статическое текстовое поле |
|
Edit Box |
Поле редактирования |
|
Group Box |
Прямоугольник, объединяющий группу органов управления |
|
Button |
Кнопка |
|
Check Box |
Переключатель в виде прямоугольника |
|
Radio Button |
Переключатель круглой формы (радиопереключатель) |
|
Combo Box |
Список с окном редактирования |
|
List Box |
Список |
|
Horizontal Scroll Bar |
Горизонтальная полоса просмотра |
|
Vertical Scroll Bar |
Вертикальная полоса просмотра |
|
Animate |
Окно просмотра видео |
|
Tab Control |
Позволяет размещать в диалоговой панели несколько страниц органов управления, каждая из которых имеет “закладку”. Пользователь одновременно видит закладки всех страниц и может выбрать любую из них. |
|
Tree Control |
Позволяет просматривать иерархические (древовидные) структуры данных. Такой орган управления можно использовать для отображения структуры каталогов на диске, заголовков статей справочной системы и т. д. |
|
List Control |
Может использоваться для отображения списка пиктограмм |
|
Hot Key |
Орган управления Hot Key предназначен для ввода комбинации клавиш акселераторов |
|
Slider |
Движок. Обычно используется при отображении видеоинформации. Позволяет перейти к просмотру произвольного кадра |
|
Progress |
Линейный индикатор. Позволяет отображать на экране ход какого-нибудь процесса, например процесса копирования файлов |
|
Spin |
Обычно используется для увеличения или уменьшения значения какого-нибудь параметра |
|
Custom Control |
Орган управления, внешний вид и назначение которого определяется пользователем |
Для нашего первого приложения с диалоговой панелью вам надо добавить только одну кнопку, одно текстовое поле и одно поле редактирования.
Сначала добавьте кнопку. Для этого щелкните по изображению кнопки в панели Controls. Затем переместите указатель мыши в то место диалоговой панели, где вы желаете разместить кнопку и нажмите левую клавишу мыши. В диалоговой панели появится изображение кнопки, названное по умолчанию Button1. Выполните по ней двойной щелчок левой клавишей мыши. На экране появится панель Push Button Propeties, определяющая различные характеристики кнопки. В первую очередь вас будут интересовать поля Caption и ID. В поле Caption введите название кнопки Default, а в поле ID ее идентификатор IDC_DEFAULT. Остальные характеристики кнопки оставьте без изменения.
Находясь в редакторе ресурсов вы можете сразу попробовать как работает диалоговая панель. Найдите диалоговую панель Dialog (рис. 2.29). Если ее нет на экране, выберите из меню View строку Toolbars и в открывшейся диалоговой панели Toolbars установите переключатель Dialog. Диалоговая панель Dialog содержит ряд кнопок. Первая кнопка, на которой нарисован тумблер, позволяет проверить, как будет работать диалоговая панель.
Рис. 2.29. Панель управления Dialog
Остальные кнопки диалоговой панели Dialog позволяют задавать выравнивание органов управления друг относительно друга и относительно границ панели. Последние две кнопки устанавливают разметку на диалоговой панели. Разметка поможет вам ровнее разместить органы управления.
Вы можете изменить заголовок диалоговой панели, ее идентификатор и многие другие параметры, если сделаете двойной щелчок левой кнопкой мыши по заголовку диалоговой панели. На экране появится панель Dialog Properties, содержащая несколько страниц. Выберите страницу General (рис. 2.30).
Рис. 2.30. Характеристики диалоговой панели
В поле ID вы можете изменить идентификатор диалоговой панели, в поле Caption - заголовок диалоговой панели. В полях Font name и Font size отображается тип и размер шрифта, который используется для всех текстов в диалоговой панели. Изменить параметры шрифта можно, нажав кнопку Font. Поля X Pos и Y Pos позволяют указать начальное положение диалоговой панели на экране. Если X Pos и Y Pos содержат нулевые значения, начальное положение диалоговой панели определяется операционной системой.
Диалоговая панель может иметь собственное меню. Это меню будет отображаться непосредственно под заголовком диалоговой панели. Если вы желаете подключить к диалоговой панели меню, сначала разработайте меню и запишите его в файл ресурсов. Затем выберите в диалоговой панели Dialog Properties идентификатор данного меню из списка Menu.
Для приложения MFDialog вам надо поменять только идентификатор диалоговой панели и ее название. Измените идентификатор диалоговой панели с IDD_DIALOG1 на “DIALOGPANEL”, а ее заголовок - с Dialog на My Dialog. Остальные характеристики диалоговой панели оставьте без изменения.
Итак, диалоговая панель “DIALOGPANEL” приложения MFDialog содержит три кнопки, одно статическое текстовое поле и одно поле редактирования. В листинге 2.14 представлен фрагмент файла ресурсов в котором определяется диалоговая панель приложения.
Листинг 2.14. Фрагмент файла MFDlgRes.rc
////////////////////////////////////////////////////////////// // Диалоговая панель // DIALOGPANEL DIALOG DISCARDABLE 0, 0, 186, 46 STYLE DS_MODALFRAME|DS_CENTER|WS_POPUP|WS_CAPTION|WS_SYSMENU CAPTION "My Dialog" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,129,7,50,14 PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14 PUSHBUTTON "Default",IDC_DEFAULT,70,7,50,14 EDITTEXT IDC_EDIT,7,24,113,14,ES_AUTOHSCROLL LTEXT "Line Editor",IDC_STATIC,9,10,34,8 END
Идентификаторы, задействованные в файле ресурсов приложения по умолчанию, определяются во включаемом файле resource.h. Мы привели этот файл в листинге 2.15. Вы можете изменить название включаемого файла, выбрав из меню View строку Resource Includes.
Листинг 2.15. Файл resource.h
//{{NO_DEPENDENCIES}} // Включаемый файл, созданный Microsoft Developer Studio // Используется в файле ресурсов MFDlgRes.rc // #define IDR_MENU 101 #define IDC_DEFAULT 1000 #define IDC_EDIT 1001 #define ID_TEST_DIALOG 40001 #define ID_TEST_EXIT 40002 // Следующие значения идентификаторов используются по // умолчанию для новых объектов #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 103 #define _APS_NEXT_COMMAND_VALUE 40003 #define _APS_NEXT_CONTROL_VALUE 1003 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Обратите внимание, что включаемый файл resource.h содержит не только определения идентификаторов, но также дополнительную служебную информацию. Она расположена после директивы #ifdef APSTUDIO_INVOKED и представляет собой ряд макроопределений. Данные макроопределения используются редактором ресурсов при создании новых идентификаторов.
Откройте страницу ClassView в окне Project Workspace. Обратите внимание, что если вы переместите окно Project Workspace к границе главного окна Visual C++, его заголовок пропадет и окно станет похоже на обычную панель управления. Если горизонтальный размер окна Project Workspace уменьшится, тогда исчезнут названия в закладках страниц и останутся только маленькие пиктограммы (рис. 2.31).
В ней отображаются два класса CMFDialogApp и CMyDialog. В главный класс приложения CMFDialogApp входит метод InitInstance. В класс CMyDialog входит конструктор CMyDialog, метод DoDataExchange XE "CWnd:DoDataExchange", предназначенный для обмена данными между органами управления диалоговой панели и привязанных к ним переменных, а также методы OnOK XE "CDialog:OnOK", OnCancel XE "CDialog:OnCancel" и OnDefault. Последние три метода вызываются когда пользователь нажимает на кнопки OK, Cancel и Default, расположенные в диалоговой панели. Кроме того, определена глобальная переменная MFDialogApp.
Рис. 2.31. Классы проекта MFDialog
Страница ResourceView окна Project Workspace показывает все ресурсы, входящие в проект (рис. 2.32). В приложении MFDialog определен только один ресурс - диалоговая панель, имеющая идентификатор “DIALOGPANEL”.
Рис. 2.32. Ресурсы проекта MFDialog
Постройте проект и запустите полученный выполнимый файл. На экране появится диалоговая панель My Dialog, определенная в файле ресурсов нашего проекта (рис. 2.33). Вначале поле редактирования Line Editor пустое. В нем вы можете ввести любой текст. Если вы нажмете на кнопку Default, тогда все, что вы ввели в поле редактирования Line Editor, пропадает и в нем отображается строка Start Text.
Кроме кнопки Default в диалоговой панели My Dialog находятся еще две кнопки OK и Cancel. Если вы нажмете кнопку OK, тогда диалоговая панель закрывается, а на экране появляется текстовое сообщение, содержащее текст, введенный вами в поле Line Editor. Если нажать кнопку Cancel, то диалоговая панель My Dialog также закроется, но сообщение с введенным вами текстом не появится.
Рис. 2.33. Приложение MFDialog
Рассмотренное нами приложение MFDialog можно создать значительно быстрее, если воспользоваться средствами автоматизированной разработки приложений MFC AppWizard и ClassWizard. Используя эти средства, вы будете избавлены от необходимости вручную набивать в текстовом редакторе код приложения и сможете сконцентрировать свои усилия на реализации обработчиков сообщений от органов управления диалоговой панели.
Теперь приступим к рассмотрению исходного текста приложения, представленного в листинге 1.1.
Сначала мы определяем главный класс приложения CMFDialogApp, наследованный от базового класса CWinApp XE "CWinApp". Класс CMFDialogApp и его методы используются аналогично приложению MFHello. Отличие заключается в том, что переопределенный метод InitInstance вместо отображения на экране сообщения при помощи функции AfxMessageBox XE "AfxMessageBox", отображает полноценную модальную диалоговую панель.
//===================================================== // Метод InitInstance класса CMFDialogApp //===================================================== BOOL CMFDialogApp::InitInstance() { // Создаем объект класса CMyDialog CMyDialog dlgTest; m_pMainWnd = &dlgTest; // Отображаем на экране модельную диалоговую панель dlgTest.DoModal(); // Отображаем на экране значение переменной m_Text, // входящей в класс CMyDialog AfxMessageBox(dlgTest.m_Text); return FALSE; }
Сначала создается объект dlgTest класса CMyDialog, который будет представлять диалоговую панель. Когда объект dlgTest создан, диалоговая панель еще не появляется на экране, для этого надо воспользоваться методом DoModal, определенным в классе CDialog.
Следующим оператором мы присваиваем адрес объекта диалоговой панели элементу данных m_pMainWnd XE "CWinThread:m_pMainWnd", входящему в класс CWinApp. Элемент данных m_pMainWnd определяет главное окно приложения. В нашем случае главное окно как таковое отсутствует и вместо него выступает диалоговая панель.
Чтобы отобразить диалоговую панель на экране, мы вызываем для объекта dlgTest метод DoModal XE "CDialog:DoModal". На этом выполнение метода InitInstance приостанавливается, пока пользователь не закроет диалоговую панель.
Когда диалоговая панель закрыта, мы отображаем на экране состояние переменной dlgTest.m_Text, которая соответствует полю ввода Edit диалоговой панели. Последний оператор метода return возвращает значение FALSE и приложение завершается.
Второй класс, который мы используем в своем приложении, наследуется от базового класса CDialog и носит название CMyDialog. Этот класс предназначен для управления диалоговой панелью приложения.
//===================================================== // Класс CMyDialog - класс диалоговой панели //===================================================== class CMyDialog : public CDialog { public: CMyDialog(); CString m_Text; protected: virtual void DoDataExchange(CDataExchange* pDX); // Обработчики сообщений от кнопок диалоговой панели afx_msg void OnDefault(); virtual void OnCancel(); virtual void OnOK(); DECLARE_MESSAGE_MAP() };
Обратите внимание на то, как мы определяем конструктор класса CMyDialog. После названия конструктора стоит символ двоеточие и название конструктора класса CDialog. При этом в качестве параметра, конструктору CDialog передается идентификатор диалоговой панели "DIALOGPANEL":
// Конструктор класса CMyDialog CMyDialog::CMyDialog() : CDialog("DIALOGPANEL") { // Инициализируем переменную m_Text m_Text = ""; }
Основное назначение конструктора CMyDialog - вызвать конструктор класса CDialog, указав ему в качестве параметра идентификатор диалоговой панели "DIALOGPANEL". Именно конструктор класса CDialog выполняет создание диалоговой панели.
В конструкторе также инициализируется переменная m_Text, входящая в класс CMyDialog. В нее записывается пустая строка.
Виртуальный метод DoDataExchange XE "CWnd:DoDataExchange", который мы переопределяем в классе диалоговой панели, первоначально определен в классе CWnd. Он служит для реализации механизмов автоматического обмена данными - Dialog Data Exchange XE "Dialog Data Exchange" (DDX XE "DDX" ) и автоматической проверки данных - Dialog Data Validation XE "Dialog Data Validation" (DDV XE "DDV" ).
Механизм автоматического обмена данными позволяет привязать к органам управления диалоговой панели переменные или элементы данных класса диалоговой панели. Ряд специальных функций, определенных в библиотеке MFC, вызываются методом DoDataExchange и выполняют обмен данными между органами управления диалоговой панели и соответствующими элементами данных класса диалоговой панели. Такой обмен работает в обоих направлениях. Информация из органов управления диалоговой панели может записываться в элементы данных класса, или в обратном направлении - информация из элементов данных класса может отображаться в диалоговой панели.
Названия всех функций, обеспечивающих обмен данными, начинаются символами DDX_. Например определены функции DDX_Text, DDX_Radio, DDX_Check, DDX_Scroll и т. д. Практически каждый тип органов управления диалоговой панели имеет собственную функцию для выполнения процедуры обмена данными.
Все функции DDX_ имеют три параметра. Первый параметр содержит указатель на объект класса CDataExchange. Этот объект определяет параметры обмена, в том числе направление, в котором надо выполнить обмен данными. Второй параметр определяет идентификатор органа управления, с которым выполняется обмен данными. Третий параметр содержит ссылку на элемент данные класса диалоговой панели, связанный с данным органом управления.
Ниже представлен прототип функции DDX_, предназначенной для обмена информацией между полем редактирования диалоговой панели и элементом данных, входящим в класс диалоговой панели. Этот элемент имеет класс CString:
void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, CString& value);
Метод DoDataExchange позволяет выполнять проверку данных, которые пользователь вводит в диалоговой панели. Для этого предназначен ряд функций DDV_. Эти функции позволяют гарантировать, что данные, введенные пользователем в диалоговой панели, соответствуют определенным условиям.
Если вы используете функцию DDV_ для проверки ввода в данном органе управления, вы должны вызвать ее сразу после вызова функции DDX_ для этого же органа управления.
Если функция DDV_ обнаруживает ошибку пользователя при вводе информации в органе управления диалоговой панели, она отображает сообщение и передает фокус ввода соответствующему органу управления.
В отличие от функций DDX_, функции DDV_, в зависимости от их предназначения, имеют различное количество параметров. Первый параметр, как и в случае функций DDX_, содержит указатель на объект класса CDataExchange XE "CDataExchange". Остальные параметры имеют различное назначение в зависимости от функции. Так, например, прототип функции, которая проверяет максимальную длину введенной текстовой строки, выглядит следующим образом:
void AFXAPI DDV_MaxChars(CDataExchange* pDX, CString const& value, int nChars);
Второй параметр содержит ссылку на элемент данных класса диалоговой панели, в который записана текстовая строка из поля редактирования панели. Третий параметр определяет максимально допустимую длину этой строки.
Другие функции DDV_ имеют больше параметров. Так, функция DDV_MinMaxInt, проверяющая, что введенное значение находится в определенных границах, имеет 4 параметра:
void AFXAPI DDV_MinMaxInt(CDataExchange* pDX, int value, int minVal, int maxVal);
Первый параметр, как всегда, содержит указатель на объект класса CDataExchange, второй параметр определяет проверяемую величину, а третий и четвертый параметры устанавливают минимальное и максимальное значения для проверяемой величины.
Приложение не должно напрямую вызывать метод DoDataExchange. Он вызывается через метод UpdateData, определенный в классе CWnd (рис. 2.34). Если вам надо выполнить обмен данными, между органами управления и элементами данных класса диалоговой панели, используйте метод UpdateData XE "CWnd:UpdateData". Приведем прототип метода UpdateData:
BOOL UpdateData(BOOL bSaveAndValidate = TRUE);
Рис. 2.34. Вызов метода DoDataExchange
Необязательный параметр bSaveAndValidate, определяет, как будет происходить обмен данными. Если метод UpdateData вызывается с параметром FALSE, выполняется инициализация диалоговой панели. Информация из элементов данных класса отображается в органах управления диалоговой панели.
В случае, если метод UpdateData вызван с параметром TRUE, данные перемещаются в обратном направлении. Из органов управления диалоговой панели они копируются в соответствующие элементы данных класса диалоговой панели.
Метод UpdateData возвращает ненулевое значение, если обмен данными прошел успешно и нуль в противном случае. Ошибка при обмене данными может произойти, если данные копируются из диалоговой панели в элементы класса диалоговой панели и пользователь ввел неправильные данные, отвергнутые процедурой автоматической проверки данных.
При создании модальной диалоговой панели перед тем как панель появится на экране, вызывается виртуальный метод OnInitDialog класса CDialog XE "CDialog:OnInitDialog". По умолчанию OnInitDialog вызывает метод UpdateData и выполняет инициализацию органов управления. Если вы переопределили метод OnInitDialog в своем классе диалоговой панели, в первую очередь вызовите метод OnInitDialog класса CDialog.
Метод UpdateData также вызывается некоторыми другими методами класса CDialog. Так, метод UpdateData вызывается, когда пользователь закрывает модальную диалоговую панель, нажимая кнопку OK. Заметим, что кнопка OK должна иметь идентификатор IDOK XE "IDOK". Если пользователь нажмет на кнопку Cancel, имеющую идентификатор IDCANCEL XE "IDCANCEL", то диалоговая панель также закрывается, но метод UpdateData не вызывается и обмен данными не происходит.
//===================================================== // Метод DoDataExchange класса CMyDialog //===================================================== void CMyDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT, m_Text); }
Методу DoDataExchange XE "CWnd:DoDataExchange" передается указатель pDX на объект класса CDataExchange. Этот объект создается, когда вы инициируете процесс обмена данными, вызывая метод UpdateData XE "CWnd:UpdateData". Элементы данных класса CDataExchange определяют процедуру обмена данными, в том числе определяют, в каком направлении будет происходить этот обмен. Обратите внимание, что указатель pDX передается функциям DDX_ и DDV_.
В библиотеку MFC входит большое количество функций DDX_ и DDV_. Чтобы облегчить задачу написания метода DoDataExchange для класса вашей диалоговой панели, используйте ClassWizard XE "ClassWizard". Он позволяет привязать к органам диалоговой панели элементы данных класса. При этом метод DoDataExchange создается ClassWizard автоматически. ClassWizard сам выбирает, какие функции DDX_ и DDV_ надо использовать для данного органа управления и связанного с ним элемента данных класса диалоговой панели. Подробно об использовании ClassWizard для разработки диалоговых панелей вы можете прочитать в главе “Приложение с главной диалоговой панелью”.
Класс диалоговой панели должен обрабатывать сообщения от своих органов управления, поэтому он должен иметь таблицу сообщений. В заголовке таблицы сообщений указывается имя класса CMyDialog и имя базового класса CDialog XE "CDialog" :
//===================================================== // Таблица сообщений класса CMyDialog //===================================================== BEGIN_MESSAGE_MAP(CMyDialog, CDialog) ON_BN_CLICKED(IDC_DEFAULT, OnDefault) END_MESSAGE_MAP()
Таблица сообщений содержит только одну строку, в которой обрабатывается сообщение с кодом извещения ON_BN_CLICKED XE "ON_BN_CLICKED" от кнопки “Default”. Когда пользователь нажимает кнопку, вырабатывается данное сообщение и вызывается его обработчик - метод OnDefault, определенный в классе CMyDialog:
//===================================================== // Метод OnDefault класса CMyDialog //===================================================== void CMyDialog::OnDefault() { // TODO: m_Text = "Start Text"; UpdateData(FALSE); MessageBeep(0); }
Две другие кнопки диалоговой панели "DIALOGPANEL", OK и Cancel не представлены в таблице сообщений, но тем не менее в приложении определены методы OnOK и OnCancel, которые вызываются при нажатии на них. Оказывается для диалоговых панелей определены две стандартные кнопки OK и Cancel, которым присвоены специальные идентификаторы IDOK XE "IDOK" и IDCANCEL.
Базовый класс CDialog, также как и класс CMyDialog, содержит таблицу сообщений. Если вы установили библиотеку MFC вместе с исходными текстами, вы можете изучить реализацию класса CDialog в файле Dlgcore.cpp. Сам класс CDialog определен во включаемом файле Afxwin.h. Ниже представлена выдержка из таблицы сообщений, определенной в файле Dlgcore.cpp:
BEGIN_MESSAGE_MAP(CDialog, CWnd) //{{AFX_MSG_MAP(CDialog) ON_WM_CTLCOLOR() ON_COMMAND(IDOK, OnOK) ON_COMMAND(IDCANCEL, OnCancel) ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp) ON_MESSAGE(WM_HELPHITTEST, OnHelpHitTest) ON_MESSAGE(WM_QUERY3DCONTROLS, OnQuery3dControls) ON_MESSAGE(WM_INITDIALOG, HandleInitDialog) ON_MESSAGE(WM_SETFONT, HandleSetFont) //}}AFX_MSG_MAP END_MESSAGE_MAP()
Среди прочих сообщений, в этой таблице определены командные сообщения с идентификаторами IDOK и IDCANCEL XE "IDCANCEL". Для обработки этих командных сообщений определены виртуальные методы OnOK и OnCancel. Данные методы также определены в MFC (файл Dlgcore.cpp). Поэтому когда диалоговая панель содержит кнопки с идентификаторами IDOK и IDCANCEL, как правило нет необходимости создавать для них обработчики.
Так как в таблице сообщений класса CMyDialog отсутствуют макрокоманды для обработки сообщений от кнопок OK и Cancel, они передаются для обработки базовому классу CDialog. Здесь они обрабатываются виртуальными методами OnOK и OnCancel.
Метод OnOK, определенный в классе CDialog, копирует данные из полей диалоговой панели в связанные с ними переменные. Для этого вызывается метод UpdateData с параметром TRUE. Затем выполняется вызов метода EndDialog, который закрывает диалоговую панель и возвращает значение IDOK. После того как диалоговая панель закрыта, метод DoModal возвращает значение IDOK и продолжает работать метод InitInstance.
void CDialog::OnOK() { if(!UpdateData(TRUE)) { // В процессе обмена данными произошла ошибка TRACE0("UpdateData failed during dialog termination.\n"); return; } EndDialog(IDOK); }
Метод OnCancel, определенный в классе CDialog, еще проще, чем OnOK. Он только закрывает диалоговую панель и возвращает значение IDCANCEL. Копирование данных из полей диалоговой панели не происходит, так как пользователь отменил изменения, нажав кнопку Cancel.
void CDialog::OnCancel() { EndDialog(IDCANCEL); }
Так как методы OnOK и OnCancel определены в классе CDialog как виртуальные, вы можете переназначить их в своем классе CMyDialog. В этом случае управление получат переопределенные вами методы, а не методы класса CDialog. Методы базового класса CDialog можно вызвать, явно указав класс XE "CDialog:OnCancel".
//===================================================== // Метод OnCancel класса CMyDialog //===================================================== void CMyDialog::OnCancel() { // Подаем звуковой сигнал MessageBeep(0); // Вызываем метод OnCancel базового класса CDialog::OnCancel(); }
Переопределенный нами метод OnCancel вызывает функцию программного интерфейса MessageBeep, которая подает звуковой сигнал, а затем вызываем метод OnCancel базового класса CDialog. Метод OnCancel базового класса CDialog вызывается в конце, так как он закрывает саму диалоговую панель.
Аналогично методу OnCancel мы переопределили метод OnOK XE "CDialog:OnOK".
//===================================================== // Метод OnOK класса CMyDialog //===================================================== void CMyDialog::OnOK() { // Вызываем метод OnOK базового класса CDialog::OnOK(); // Подаем звуковой сигнал MessageBeep(0); }
Конечно, наша первая программа использует далеко не все возможности класса CDialog. Другие методы этого класса будут рассмотрены нами позже в главе посвященной автоматизированным средствам проектирования приложений.
Процедура создания немодальной диалоговой панели несколько отличается от процедуры создания модальной диалоговой панели. Как и в случае с модальной диалоговой панелью, в первую очередь вы должны создать шаблон диалоговой панели и добавить его в файл ресурсов приложения.
Затем надо создать класс управляющий диалоговой панелью - класс диалоговой панели. Этот класс наследуется непосредственно от базового класса CDialog. В классе диалоговой панели следует определить методы для обработки сообщений от органов управления диалоговой панели.
Следующие шаги выполняются только для немодальных диалоговых панелей. Для класса диалоговой панели надо определить конструктор, объявив его как public. Чтобы открыть немодальную диалоговую панель, создайте объект класса диалоговой панели, обратившись к определенному вами конструктору. В этот момент диалоговая панель еще не появляется на экране. Для этого надо вызвать метод Create XE "CDialog:Create" класса CDialog.
Вы можете вызывать метод Create либо непосредственно из конструктора класса диалоговой панели, либо уже после создания объекта. Если диалоговая панель имеет стиль WS_VISIBLE, она сразу появится на экране. В противном случае для этого надо вызвать метод ShowWindow. Как видите, для отображения немодальной диалоговой панели мы не использовали метод DoModal.
В классе CDialog определены два прототипа метода Create. Один позволяет указать диалоговую панель через ее текстовое имя, а другой через числовой идентификатор:
BOOL Create(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL); BOOL Create(UINT nIDTemplate, CWnd* pParentWnd = NULL);
Параметр lpszTemplateName должен содержать указатель на строку, закрытую нулем, содержащую имя ресурса диалоговой панели. Если вы используете метод Create, имеющий второй прототип, вместо имени шаблона, надо указать в параметре nIDTemplate его идентификатор.
Параметр pParentWnd задает родительское окно диалоговой панели. Если в качестве pParentWnd указать NULL, то в качестве родительского окна выступает главное окно приложения.
Метод Create возвращает управление сразу после того, как диалоговая панель отобразилась на экране. Он возвращает ненулевое значение, если создание диалоговой панели завершилось успешно и нуль в противном случае.
Чтобы закрыть немодальную диалоговую панель, можно воспользоваться методом DestroyWindow (метод DestroyWindow определен в классе CWnd и вы можете вызвать его для объектов класса диалоговой панели).
Теперь вам остается удалить объект, управляющий диалоговой панелью. Для этого в классе диалоговой панели можно переопределить виртуальный метод PostNcDestroy XE "CWnd:PostNcDestroy" (этот метод первоначально определен в базовом классе CWnd). В нем вы можете вызвать оператор delete, передав ему в качестве параметра указатель на данный объект this XE "this".