Microsoft Visual C++ и MFC. Программирование для Win95 и WinNT

         

Немного о C++


Несмотря на все многообразие средств, предоставляемых Си++, совершенно необязательно использовать их все сразу. Первым шагом при переходе от Си к Си++ может стать изменение расширений имен исходных файлов ваших программ. Вместо традиционного расширения C в языке Си++ принято использовать расширение CPP. Теперь ваша программа будет транслироваться, как программа, написанная на языке Си++.

Затем вы можете использовать все новые и новые особенности Си++, постепенно отходя от стандартного Си к Си++. На момент написания книги окончательный стандарт Си++ еще не был разработан. Компиляторы различных фирм, например Microsoft и Borland имеют различия в реализации Си++. Наша книга ориентирована в первую очередь на компиляторы Microsoft Visual C++ версий 1.5, 2.0, 4.0 и 4.1.

Конечно, в одной главе мы не можем рассказать обо всех возможностях языка Си++. Поэтому мы изучим только основные особенности языка Си++. Более подробную информацию вы можете получить из справочников или учебников по языку Си++.



Абстрактные классы


Виртуальные методы могут быть объявлены как чисто виртуальные. Для этого после описания метода указывается специальный спецификатор (= 0). Он означает, что описанные методы не определены.

Класс в котором определен хотя бы один чисто виртуальный метод называется абстрактным. Нельзя создавать объекты абстрактного класса. Абстрактный класс может использоваться только в качестве базового класса для построения других классов.

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

В качестве примера абстрактного класса мы приведем класс Abstract, в котором описан чисто виртуальный метод PureFunc. Обратите внимание, что этот метод не определен в классе Abstract. Определение метода содержится только в порожденном классе Fact.

// Абстрактный класс Abstract

class      Abstract

{

public:

      // Чисто виртуальный метод, не имеет определения

      virtual int    PureFunc(void) = 0;

      void      SetValue(int i) {iValue = i;}

      int  iValue;

};

// Класс Fact



class      Fact : public Abstract

{

      int  PureFunc(void) {return iValue * iValue;}

};



Аннотация


Книга посвящена программированию в среде Microsoft Visual C++ с использованием библиотеки классов MFC. Для тех, кто уже освоил С, но еще не изучил С++, мы расскажем об основных особенностях этого языка. Представленные нами сведения помогут вам разобраться с библиотекой классов MFC.

На конкретных примерах мы научим вас в кратчайшие сроки создавать приложения для операционных систем Windows 3.хх, Windows 95 и WindowsNT. Рассмотрим применение средств автоматизированного программирования MFC AppWizard и ClassWizard, позволяющих разрабатывать приложения в кратчайшие сроки.

Книга предназначена для всех, кто интересуется программированием в среде операционных систем Windows 3.хх, Windows 95 и Windows NT.



Архивный класс (класс CArchive)


Класс CArchive используется для сохранения и восстановления состояния объектов в файлах на диске. Перед использованием объекта класса CArchive он должен быть привязан к файлу - объекту класса CFile.

Более подробно о процессе сохранения и восстановления объектов вы можете прочитать в разделе “Сохранение и восстановление объектов”. Пример использования класса CArchive для записи и восстановления документов в файлах представлен в разделе “Простейший графический редактор” главы “Однооконный интерфейс”.



Базы данных (классы для работы с базами данных)


В MFC включены несколько классов, обеспечивающую поддержку приложений, работающих с базами данных. В первую очередь это классы ориентированные на работу с ODBC драйверами - CDatabase и CRecordSet. Поддерживаются также новые средства для работы с базами данных DAO (Data Access Object). Для этого предназначены классы CDaoDatabase, CDaoRecordSet, . CDaoQueryDef, CDaoTableDef, CDaoWorkspace и CLongBinary.

Для работы с базами данных также предназначены классы CFieldExchange и CDaoFieldExchange. Это самостоятельные классы, они не наследуются от базового класса CObject.

Классы CFieldExchange и CDaoFieldExchange работают с процедурами обмена данными RFX (Record Field Exchange) для классов управляющих базами данных.



Благодарности


Авторы выражают благодарность Фроловой Ольге Викторовне, Кустову Виктору. Мы также благодарим всех сотрудников издательского отдела АО "ДИАЛОГ-МИФИ": Голубева Олега Александровича, Дмитриеву Наталью, Виноградову Елену, Кузьминову Оксану.



Блокировка


В состав класса CFile включены методы LockRange и UnlockRange, позволяющие заблокировать один или несколько фрагментов данных файла для доступа других процессов. Если приложение пытается повторно блокировать данные, уже заблокированные раньше этим или другим приложением, вызывается исключение. Блокировка представляет собой один из механизмов, позволяющих нескольким приложениям или процессам одновременно работать с одним файлом, не мешая друг другу.

Установить блокировку можно с помощью метода LockRange:

virtual void LockRange(DWORD dwPos, DWORD dwCount);

     

throw(CFileException);

Параметр dwPos указывает на начало фрагмента данных внутри файла, который надо заблокировать. Параметр dwCount определяет количество байт для блокировки. В одном файле можно установить несколько блокировок на различные, не перекрывающиеся фрагменты данных.

Чтобы снять установленные блокировки, надо воспользоваться методом UnlockRange. Если в одном файле установлено несколько блокировок, то каждая из них должна сниматься отдельным вызовом метода UnlockRange:

virtual void UnlockRange(DWORD dwPos, DWORD dwCount);

     

throw(CFileException);

Как и для метода LockRange, параметры dwPos и dwCount должны указывать начало фрагмента и его размер. Размер фрагмента должен соответствовать размеру фрагмента, указанному при вызове метода LockRange.



Чтение и запись файлов


Для доступа к файлам предназначены пять различных методов класса CFile: Read, ReadHuge, Write, WriteHuge, Flush. Методы Read и ReadHuge предназначены для чтения данных из предварительно открытого файла. В 16-разрядной операционной системе Windows функция Read может считать не больше, чем 65535 байт. На метод ReadHuge такие ограничения не накладываются. В 32-разрядных операционных системах оба метода могут одновременно считать из файла больше, чем 65535 байт.

virtual UINT Read(void* lpBuf, UINT nCount);

     

throw(CFileException);

Данные, прочитанные из файла, записываются в буфер lpBuf. Параметр nCount определяет количество байт, которое надо считать из файла. Фактически из файла может быть считано меньше байт, чем запрошено параметром nCount. Это происходит, если во время чтения достигнут конец файла. Метод Read возвращает количество байт, прочитанных из файла:

DWORD ReadHuge(void* lpBuffer, DWORD dwCount);

     

throw(CFileException);

Назначение параметров метода ReadHuge аналогично назначению параметров метода Read. Спецификация ReadHuge считается устаревшей и оставлена только для обратной совместимости с 16-разряднымми операционными системами.

Для записи в файл предназначены методы Write и WriteHuge. Метод WriteHuge не накладывает ограничения на количество одновременно записываемых байт. В 16-разрядных версиях операционной системы Windows метод Write позволяет записать не больше 65535 байт. Это ограничение снято в операционных системах Windows NT и Windows 95.

virtual void Write(const void* lpBuf, UINT nCount);

     

throw(CFileException);

Метод Write записывает в открытый файл nCount байт из буфера lpBuf. В случае возникновения ошибки записи, например, переполнения диска, метод Write вызывает исключение.

Не смотря на то, что спецификация WriteHuge считается устаревшей, вы можете использовать ее для создания 16-разрядных приложений в среде Visual C++ версии 1.5х.



Чтение из архивного файла


В предыдущем разделе мы рассказали как сохранить объект вашего класса в архивном файле. Теперь опишем, как восстановить записанное ранее состояние объекта класса из архивного файла. Когда приложение желает восстановить состояние объекта данного класса, оно вызывает для него метод Serialize. В качестве параметра этому методу передается указатель на объект класса CArchive, связанного с файлом, открытым для чтения.

Реализация метода Serialize должна восстановить из файла все элементы данных, которые были в него записаны. Для этого можно воспользоваться оператором >> или методами ReadString и Read, определенными в классе CArchive.

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

Оператор >> можно использовать для чтения из архивного файла переменных простых типов, например long, int, char и объектов других классов, которые наследованы от класса CObject. В одной строке программы можно использовать оператор >> несколько раз.

Для чтения из архивного файла массивов, записанных в него методом Write, надо использовать метод Read класса CArchive. Он позволяет прочитать из файла определенное количество байт и записать его в указанный буфер:

UINT Read(void* lpBuf, UINT nMax);

     

throw(CFileException);

Параметр lpBuf определяет буфер памяти, в который будут записаны считанные из файла данные. Параметр nMax указывает максимальное количество байт, которое можно считать из архивного файла и записать в буфер lpBuf. Метод Read возвращает количество прочитанных байт.

Если требуется прочитать из архивного файла строку, записанную в него методом WriteString, воспользуйтесь методом ReadString. В состав класса CArchive входят два метода ReadString, которые предназначены для записи прочитанной из файла строки в объект класса CString или в строку Си.

Первый метод имеет более простой прототип. Единственный параметр rString определяет объект класса CString, в который будет записана полученная информация. Если метод ReadString успешно завершит чтение, он возвращает значение TRUE, в противном случае - значение FALSE:


BOOL ReadString(CString& rString);

Если вам надо записать прочитанную из архивного файла строку в массив символов, воспользуйтесь другим прототипом метода ReadString:

LPTSTR ReadString(LPTSTR lpsz, UINT nMax);

      throw(CArchiveException);

Параметр lpsz должен указывать на буфер памяти, в который будет записана информация. Параметр nMax определяет максимальное количество символов, которое метод может прочитать из файла. Размер буфера lpsz должен быть как минимум на единицу больше, чем значение nMax (в конец строки lpsz будет записан символ \0).

Когда метод Serialize завершит работу по восстановлению или записи объекта из архивного файла, вы должны закрыть используемый для этого объект класса CArchive. Для этого необходимо вызвать метод Close:

void Close();

      throw(CArchiveException, CFileException);

После вызова этого метода закройте файл, связанный с объектом CArchive, вызвав метод CFile::Close, и удалите сам объект класса CFile.


Диагностика


Класс CObject содержит методы AssertValid и Dump, которые могут помочь на этапе отладки приложения. Оба эти методы определены как виртуальные. Вы можете переопределить их в своем классе.



Диалоговая панель


Очень удобным средством для организации взаимодействия пользователя и приложения являются диалоговые панели. Более того, многие приложения могут успешно работать и без главного окна, взаимодействуя с пользователем только через диалоговые панели. Примером такого приложения может служить приложение Scandisk, входящее в состав операционной системы Windows 95.

Библиотека классов MFC содержит класс CDialog, специально предназначенный для управления диалоговыми панелями. Как мы рассказывали в предыдущих томах серии “Библиотека системного программиста”, посвященных программированию для операционных систем Windows и Windows 95, диалоговые панели бывают двух типов - модальные и немодальные.

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

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

Как ни странно, и модальные и немодальные диалоговые панели обслуживаются одним (общим) классом CDialog, наследованным от базового класса CWnd (рис. 2.27).

Рис. 2.27. Класс CDialog

В библиотеке MFC версии 1.0 для немодальных диалоговых панелей был предназначен отдельный класс CModalDialog. Однако, начиная с MFC версии 2.0 он включен в класс CDialog. Для совместимости класс CModalDialog также оставлен, но он определен макрокомандой #define как CDialog (файл Afxwin.h):

#define CModalDialog CDialog

Как создать и отобразить на экране диалоговую панель? В первую очередь необходимо добавить в файл ресурсов приложения шаблон новой диалоговой панели и при помощи редактора ресурсов изменить его по своему усмотрению.

Следующим этапом создается класс для управления диалоговой панелью. Этот класс наследуется непосредственно от базового класса CDialog.

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

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

Чтобы создать модальную диалоговую панель, сначала необходимо создать объект определенного вами класса диалоговой панели, а затем вызвать метод DoModal, определенный в классе CDialog.

Процедура создания немодальной диалоговой панели несколько другая. Для этого используется метод Create класса CDialog. Мы рассмотрим создание немодальных диалоговых панелей позже.


В ресурсах приложения определена только одна диалоговая панель с идентификатором IDD_ABOUTBOX. Она содержит краткую информацию о приложении и отображается на экране, когда пользователь выбирает из меню Help строку About Single.

//////////////////////////////////////////////////////////////

// Диалоговая панель

IDD_ABOUTBOX DIALOG DISCARDABLE  0, 0, 217, 55

CAPTION "About Single"

STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU

FONT 8, "MS Sans Serif"

BEGIN

      ICON          IDR_MAINFRAME,IDC_STATIC,11,17,20,20

      LTEXT             "Single Version 1.0",IDC_STATIC,40,10,119,8,

                         SS_NOPREFIX

      LTEXT             "Copyright \251 1996",IDC_STATIC,40,25,119,8

      DEFPUSHBUTTON          "OK",IDOK,178,7,32,14,WS_GROUP

END




Диалоговые панели представляются объектами классов, наследованных от базового класса СDialog. Если командное сообщение, поступившее объекту диалоговой панели, не может быть обработано, оно передается его родительскому окну.

Если родительское окно диалоговой панели также не может обработать командное сообщение, оно передается главному объекту приложения.



Диалоговые панели приложения Dialog


Диалоговые панели имеют идентификаторы IDD_DIALOG_DIALOG и IDD_ABOUTBOX. Диалоговая панель IDD_DIALOG_DIALOG - это и есть главная диалоговая панель приложения. Она будет отображаться на экране монитора сразу после запуска приложения.

Просмотрите внешний вид диалоговой панели IDD_DIALOG_DIALOG в редакторе ресурсов. Для этого сделайте двойной щелчок по ее названию в окне Project Workspace. Изначально эта панель содержит только две кнопки OK и Cancel, а также короткую текстовую строку. Впоследствии вы можете изменять эту панель, добавляя к ней новые органы управления.

Вторая диалоговая панель IDD_ABOUTBOX содержит информацию о приложении - его название, авторские права, год разработки и пиктограмму. Эта панель будет отображаться на экране, когда пользователь выберет строку About из системного меню главной диалоговой панели приложения.

Вы можете изменить диалоговую панель IDD_ABOUTBOX по своему усмотрению. Так например, вы можете добавить к ней вашу фамилию и адрес электронной почты.

Ниже мы привели фрагменты из файла ресурсов приложения Dialog, в которых определяются шаблоны диалоговых панелей IDD_ABOUTBOX и IDD_DIALOG_DIALOG.

//////////////////////////////////////////////////////////////

// Шаблоны диалоговых панелей приложения

IDD_ABOUTBOX DIALOG DISCARDABLE  0, 0, 217, 55

STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU

CAPTION "About Dialog"

FONT 8, "MS Sans Serif"

BEGIN

      ICON          IDR_MAINFRAME,IDC_STATIC,11,17,20,20

      LTEXT      "Dialog Version 1.0",

                         IDC_STATIC,40,10,119,8,SS_NOPREFIX

      LTEXT             "Copyright © 1996",IDC_STATIC,40,25,119,8

      DEFPUSHBUTTON          "OK",IDOK,178,7,32,14,WS_GROUP

END

IDD_DIALOG_DIALOG DIALOGEX 0, 0, 185, 92

STYLE DS_MODALFRAME|WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU

EXSTYLE WS_EX_APPWINDOW

CAPTION "Dialog"

FONT 8, "MS Sans Serif"

BEGIN

      PUSHBUTTON                                 "Cancel",IDCANCEL,128,23,50,14

      DEFPUSHBUTTON          "OK",IDOK,128,7,50,14

      LTEXT                                                             "TODO: Place dialog controls here.",

                                                                IDC_STATIC,5,34,113,8

END



Для забывчивых пользователей


В ряде случаев пользователь может забыть сохранить внесенные им изменения документа в файле. Попробуйте отредактировать ранее сохраненный документ приложения Single, а затем создайте новый файл. Изменения документа сохранены не будут.

Класс CDocument и все классы, для которых он является базовым, позволяют установить специальный флаг модификации, означающий что документ изменен. В этом случае, перед закрытием документа пользователю будет предложено его сохранить. Для установки этого флага предназначен метод SetModifiedFlag. Вот прототип метода SetModifiedFlag:

void SetModifiedFlag(BOOL bModified = TRUE);

Если документ изменен, установите флаг модификации, вызвав метод SetModifiedFlag с параметром bModified, равным TRUE или без параметра. В случае необходимости вы можете убрать установленный флаг. Для этого надо вызвать метод SetModifiedFlag с параметром bModified, равным FALSE.

Мы должны добавить вызов метода SetModifiedFlag в методах OnLButtonDown и OnRButtonDown, выполняющих модификацию документа. Вызов метода можно разместить в любом месте, например, сразу после добавления к массиву arrayFig, представляющему документ, нового элемента.

//////////////////////////////////////////////////////////////

// Метод OnLButtonDown класса CSingleView

void CSingleView::OnLButtonDown(UINT nFlags, CPoint point)

{

      // ...

      // Добавляем к массиву, определяющему документ, новый

      // элемент

      pDoc->arrayFig.Add(OneFigure);

      // Устанавливаем флаг изменения документа

      pDoc->SetModifiedFlag();

      CView::OnLButtonDown(nFlags, point);

}

//////////////////////////////////////////////////////////////

// Метод OnRButtonDown класса CSingleView

void CSingleView::OnRButtonDown(UINT nFlags, CPoint point)

{

      // ...

      // Добавляем к массиву, определяющему документ, новый

      // элемент

      pDoc->arrayFig.Add(OneFigure);

      // Устанавливаем флаг изменения документа

      pDoc->SetModifiedFlag();

     

      CView::OnRButtonDown(nFlags, point);

}



Добавление к классу нового элемента данных


Процедура добавления в класс новых данных сходна с только что описанной процедурой добавления метода. Для этого выберите из меню строку Add Variable. На экране появится диалоговая панель Add Member Variable, представленная на рисунке 2.16.

Рис. 2.16. Диалоговая панель Add Member Variable

В поле Variable Type надо ввести тип данных, а в поле Variable Declaration - название соответствующей переменной. Область видимости переменной определяется переключателем Access.

С помощью ClassView можно просмотреть список названий файлов в которых используется данный класс. Для этого надо выбрать из временного меню строку References. На экране появится диалоговая панель Definitions and References.



Добавление к классу нового метода


Чтобы добавить в класс новый метод, выберите из временного меню строку Add Function. На экране появится диалоговая панель Add Member Function, представленная на рисунке 2.15.

Рис. 2.15. Диалоговая панель Add Member Function

В поле Function Type следует ввести тип значения, возвращаемого методом. Объявление метода запишите в поле Function Declaration. В этой же диалоговой панели можно определить область видимости метода. Для этого предназначен переключатель с зависимой фиксацией Access. Он может находиться в трех положениях: Public, Protected и Private. Переключатели Static и Virtual позволяют определить, что добавляемый метод должен быть объявлен, соответственно, как статический или виртуальный.

Когда все поля диалоговой панели заполнены, нажмите кнопку OK. В объявлении класса будет добавлен новый метод. Название метода также появится в списке элементов класса в окне ClassView.



Документ


Также как и окно просмотра, объект, представляющий документ, сначала проверяет свою таблицу сообщений. Только в том, случае если в классе документа отсутствует обработчик командного сообщения, оно передается для обработки шаблону данного документа.

Объект, представляющий шаблон документа, проверяет только собственную таблицу сообщений и не передает командные сообщения другим объектам приложения.



Документ приложения (класс CDocument)


Большинство приложений работают с данными или документами, хранимыми на диске в отдельных файлах. Класс CDocument, наследованный от базового класса CCmdTarget, служит для представления документов приложения.



Долгий путь сообщения


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

Попробуйте последовательно выбирать все строки из меню Test. При выборе первых трех строк на экране появляется сообщение, содержащее название класса в котором обработано сообщение. Если выбрать из меню Test строку Exit приложение завершится не смотря на то, что команда ID_APP_EXIT, соответствующая этой строке меню вообще не имеет обработчика в исходном тексте нашего приложения.

Поэкспериментируйте с приложением MFMessage. Очень скоро вы обнаружите, что команды обрабатываются не только классом окна, как это было в приложении MFMenu, но также и главным классом приложения. Те команды которые не имеют обработчика в таблице сообщений класса окна, передаются для обработке в класс приложения.

Если же команда может быть обработана и в классе окна и в классе приложения, она обрабатывается только один раз в классе окна. Обработчик класса приложения в этом случае не вызывается.

Широкие возможности для управления обработкой сообщений предоставляет ClassWizard. Мы расскажем более подробно об обработке сообщений и средствах ClassWizard в следующей главе.


Дружественные функции


В Си++ вы можете определить для класса так называемую дружественную функцию, воспользовавшись ключевым словом friend. В классе содержится только объявление дружественной функции. Ее определение расположено вне класса. Вы можете объявить дружественную функцию в любой секции класса - public, private или protect.

Дружественная функция не является элементом класса, но может обращаться ко всем его элементам, включая private и protect. Одна и та же функция может быть дружественной для двух или более классов.

В следующем примере определена функция Clear, дружественная для класса point. Дружественная функция Clear используется для изменения значения элементов данных m_x и m_y, объявленных как private:

//==========================================================

// Класс point

class point

{

public:

      // Функция Clear объявляется дружественной классу point

      friend void point::Clear(point*);

      // Интерфейс класса...

private:

      int  m_x;

      int  m_y;

};

//==========================================================

// Функция Clear

void Clear(point* ptrPoint)

{

      // Обращаемся к элементам класса, объявленным как private

      ptrPoint->m_x = 0;

      ptrPoint->m_y = 0;

      return;

}

//==========================================================

// Главная функция

void main()

{

      point pointTestPoint;

      // Вызываем дружественную функцию

      Clear(&pointTestPoint);

}

С помощью ключевого слова friend вы можете объявить некоторые методы одного класса дружественными для другого класса. Такие методы могут обращаться ко всем элементам класса, даже объявленным как private и protect, несмотря на то, что сами они входят в другой класс.

В следующем примере мы определяем два класса - line и point. В классе point определяем метод Set и объявляем его в классе line как дружественный. Дружественный метод Set может обращаться ко всем элементам класса line:

// Предварительное объявление класса line


class line;

//==========================================================

// Класс point

class point

{

public:

      // Метод Set класса point

      void Set(line*);

      // ...

};

//==========================================================

// Класс line

class line

{

public:

      // Метод Set класса point объявляется дружественной

      // классу point

      friend void point::Set(line*);

private:

      int  begin_x, begin_y;

      int  end_x, end_y;

};

//==========================================================

// Функция Clear

void point::Set(line* ptrLine)

{

      // Обращаемся к элементам класса line, объявленным как

      // private

      ptrLine->begin_x = 0;

      ptrLine->begin_y = 0;

      // ...

     

      return;

}

//==========================================================

// Главная функция

void main()

{

      point           pointTestPoint;

      line       lineTestPoint;

      // Вызываем дружественный метод

      pointTestPoint.Set(&lineTestPoint);

}


Дружественные функции и дружественные классы


Доступ к элементам класса из программы и других классов ограничен. Вы можете непосредственно обращаться только к элементам класса, определенным или описанным после ключевого слова public. Однако, в некоторых случаях, требуется определить функцию вне класса или другой класс, методы которого могут обращаться непосредственно ко всем элементам класса, включая элементы объявленные как private и protect.



Дружественные классы


По аналогии с дружественными функциями и методами, можно объявить дружественный класс. Все методы дружественного класса, могут обращаться ко всем элементам класса, включая элементы, объявленные как private и protect.

Так, например, в предыдущем примере вы могли бы определить, что класс point является дружественным классу line. Все методы класса point могут обращаться к любым элемента класса line.

//==========================================================

// Класс point

class point

{

      // ...

};

//==========================================================

// Класс line

class line

{

public:

      // Класс point объявляется дружественным классу line

      friend class point;

};



Единичное наследование


В случае единичного наследования порожденный класс наследуется только от одного базового класса. Рисунок 1.1 отражает единичное наследование классов. Единичное наследование является наиболее распространенным методом наследования. Библиотека классов MFC использует только единичное наследование.

Чтобы указать, что класс наследуется от другого базового класса, имя базового класса <base> необходимо указать после имени класса перед открывающей фигурной скобкой определения класса. Непосредственно перед именем базового класса необходимо поставить знак двоеточия:

class [<tag>[:<base>]]

{

     

<member-list>

} [<declarators>];

Перед названием базового класса может быть указан спецификатор доступа public, private или protect. Назначение этих спецификаторов мы рассмотрим в разделе “Разграничение доступа к элементам базового класса”. Сейчас же мы скажем только, что если вы не укажите спецификатор доступа, то по умолчанию будет подразумеваться спецификатор private.

Ниже мы определили базовый класс Base, содержащий несколько элементов, а затем наследовали от него два новых класса DerivedFirst и DerivedSecond. В каждом из порожденных классов мы определили различные дополнительные методы и элементы данных.

// Класс Base

class      Base

{

      // Элементы класса Base

};

// Класс DerivedFirst, наследованный от базового класса Base

class      DerivedFirst : Base

{

      // Элементы класса DerivedFirst

};

// Класс DerivedSecond, наследованный от базового класса Base

class      DerivedSecond : Base

{

      // Элементы класса DerivedSecond

};

Классы DerivedFirst и DerivedSecond сами могут выступать в качестве базовых классов.

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

В качестве примера приведем базовый класс Base и производный от него класс Derived. В обоих классах определен элемент данных iNumber. Чтобы получить доступ из порожденного класса к элементу iNumber базового класса указывается его полное имя Base::iNumber.

// Класс Base

class      Base

{

public:

      int  iNumber;

      // Другие элементы класса

};

// Класс Derived, наследованный от базового класса Base

class      Derived : Base

{

public:

      // Это объявление скрывает элемент iNumber базового класса

      int  iNumber;

      int  GetNumber(void) {return iNumber + Base::iNumber; }

};

Указатель на объект базового класса можно присвоить указатель на объект класса порожденного от него. Эта возможность будет широко использоваться в библиотеке классов MFC.



Файловая система (класс CFile)


Библиотека MFC включает класс для работы с файловой системой компьютера. Он называется CFile и также наследуется от базового класса CObject. Непосредственно от класса CFile наследуются еще несколько классов - CMemFile, CStdioFile, CSocketFile.

При работе с файловой системой вам может потребоваться получить различную информацию о некотором файле - например, дату создания, размер и т. д. Для хранения этих данных предназначен специальный класс CFileStatus. Класс CFileStatus один из немногих классов, которые не наследуются от базового класса CObject.


Библиотека MFC включает класс CFile, предназначенный для обеспечения работы с файлами. Он позволяет упростить использование файлов, представляя файл как объект, который можно создать, читать, записывать и т. д. Класс CFile наследуется непосредственно от класса CObject:

CFile <- CObject     

Чтобы получить доступ к файлу, сначала надо создать объект класса CFile. Конструктор класса CFile позволяет сразу после создания такого объекта открыть файл. Но мы воспользуется более общим способом и откроем файл позже, вызвав метод Open.



Файловая система - класс CStdioFile


Если вы привыкли пользоваться функциями потокового ввода/вывода из стандартной библиотеки трансляторов Си и Си++, обратите внимание на класс CStdioFile, наследованный от базового класса CFile. Этот класс позволяет выполнять буферизованный ввод/вывод в текстовом и двоичном режиме.

CStdioFile <- CFile <- CObject

В текстовом режиме выполняется специальная обработка символов возврата каретки и перевода строки. Когда в файл, открытый в текстовом режиме, записывается символ перевода строки \n (код 0x0A), он преобразуется в два символа - символ перевода строки (код 0x0A) и символ возврата каретки (код 0x0D). И наоборот, когда из файла считывается пара символов перевода строки и возврата каретки, они преобразуются в один символ перевода строки.

Для объектов класса CStdioFile можно вызывать все методы его базового класса CFile, кроме методов Duplicate, LockRange и UnlockRange. Напомним, что класс CMemFile, также наследованный от базового класса CFile, тоже работает с этими методами.

В класс CStdioFile входит элемент данных m_pStream, который содержит указатель на открытый файл. Если объект CStdioFile создан, но файл либо еще не открыт, либо закрыт, тогда m_pStream содержит константу NULL.

Класс CStdioFile имеет три различных конструктора. Первый конструктор класса CStdioFile не имеет параметров:

CStdioFile();

Этот конструктор только создает объект класса, но не открывает никаких файлов. Чтобы открыть файл, надо вызвать метод Open базового класса CFile.

Второй конструктор класса CStdioFile можно вызывать, если файл уже открыт и вам надо создать новый объект класса и связать с ним открытый файл:

CStdioFile(FILE* pOpenStream);

Этот конструктор можно использовать, если файл был открыт стандартной функцией fopen.

Параметр pOpenStream должен содержать указатель на файл, полученный вызовом стандартной функции fopen.

Третий, последний конструктор можно использовать, если надо создать объект класса CStdioFile, открыть новый файл и связать его с только что созданным объектом:


CStdioFile(LPCTSTR lpszFileName, UINT nOpenFlags);

      throw(CFileException);

Если указанный файл не может быть открыт, вызывается исключение CFileException.

Параметр lpszFileName должен содержать указатель на строку с именем файла. Можно указать полный путь файла, а не только его имя. Параметр nOpenFlags определяет режим, в котором будет открыт файл. Возможные значения этого параметра были описаны ранее (см. Метод Open класса CFile).

Для чтения и записи в текстовый файл класс CStdioFile включает два новых метода ReadString и WriteString. Метод ReadString позволяет прочитать из файла строку символов, а метод WriteString - записать.

Метод ReadString имеет две формы. Первая используется для чтения строк из файла в буфер памяти, а вторая для чтения строк и записи их в объект класса CString.

Вот описание первой формы метода ReadString:

virtual LPTSTR ReadString(LPTSTR lpsz, UINT nMax);

      throw(CFileException);

Из открытого файла считывается текстовая строка и записывается в буфер lpsz. Считается, что строка файла оканчивается символами перевода строки и возврата каретки. В конец строки, записанной в буфер lpsz, заносится символ двоичного нуля (\0).

Максимальное количество считываемых символов определяется параметром nMax. Если в строке файла больше, чем nMax - 1 байт, то остальные символы не будут прочитаны. Метод ReadString возвращает указатель на прочитанную строку или NULL, если достигнут конец файла.

Вторая форма метода ReadString не намного отличается от первой. Вместо двух параметров lpsz и nMax указывается один параметр rString, указывающий на объект класса CString, в который будет записана строка, прочитанная из файла:

BOOL ReadString(CString& rString);

      throw(CFileException);

Еще одно отличие второй формы метода ReadString заключается в типе возвращаемого им значения. Если достигнут конец файла, то вторая форма метода ReadString возвращает константу FALSE.

Для записи в файл текстовой строки предназначен метод WriteString:

virtual void WriteString(LPCTSTR lpsz);

      throw(CFileException);

В качестве параметра lpsz этого метода надо указать адрес буфера с текстовой строкой, закрытой символом \0. Символ \0 не записывается в файл. Если в текстовой строке lpsz есть символы перевода строки, они записываются как пара символов возврата каретки и перевода строки.

Метод WriteString может вызвать исключение, если во время записи в файл произойдет ошибка, например переполнение диска.


Файловая система - классы CMemFile и CStdioFile


В библиотеку MFC входит класс CMemFile, наследуемый от базового класса CFile. Класс CMemFile представляет файл, размещенный в оперативной памяти. Вы можете работать с объектами класса CMemFile также, как с объектами класса CFile. Отличие заключается в том, что файл, связанный с объектом CMemFile, на самом деле расположен не на магнитном диске, а в оперативной памяти компьютера. За счет этого операции с таким файлом происходят значительно быстрее, чем с обычными файлами.

CMemFile <- CFile <- CObject

Работая с объектами класса CMemFile, можно использовать все методы класса CFile, которые мы уже описали в предыдущей главе. Вы можете записывать данные в такой файл и считывать их. К сожалению, для класса CMemFile не реализованы методы блокировки LockRange и UnlockRange и метод для копирования Duplicate. Кроме этих методов, в состав класса CMemFile включены дополнительные методы.

Для создания объектов класса CMemFile предназначены два различных конструктора. Первый конструктор CMemFile имеет всего один необязательный параметр nGrowBytes:

CMemFile(UINT nGrowBytes = 1024);

Этот конструктор создает в оперативной памяти пустой файл. После создания файл автоматически открывается. Вы не должны специально вызывать метод Open.

Когда вы начинаете запись в такой файл, автоматически выделяется блок памяти. Для получения памяти методы класса CMemFile вызывают стандартные функции malloc, realloc и free. Если выделенного блока памяти недостаточно, его размер увеличивается. Увеличение блока памяти файла происходит частями по nGrowBytes байт. После удаления объекта класса CMemFile используемая им память автоматически возвращается системе.

Второй конструктор класса CMemFile имеет более сложный прототип. Этот конструктор используется в тех случаях, когда вы сами выделяете память для файла:

CMemFile(BYTE* lpBuffer, UINT nBufferSize,

      UINT nGrowBytes = 0);

Параметр lpBuffer указывает на буфер, который будет использоваться для файла. Размер буфера определяется параметром nBufferSize.


Необязательный параметр nGrowBytes используется более комплексно, чем в первом конструкторе класса. Если nGrowBytes содержит нуль, то созданный файл будет содержать данные из буфера lpBuffer. Длина такого файла будет равна nBufferSize.

Класс CMemFile позволяет получить указатель на область памяти, используемую файлом. Через этот указатель можно непосредственно работать с содержимым файла, не ограничивая себя методами класса CFile.

Для получения указателя на буфер файла вы можете воспользоваться методом Detach:

BYTE * Detach();

Перед эти полезно определить длину файла и соответственно, размер буфера памяти, вызвав метод GetLength.

Метод Detach закрывает данный файл и возвращает указатель на используемый им блок памяти. Если вам требуется опять открыть файл и связать с ним блок оперативной памяти, вызовите метод Attach:

void

Attach(BYTE* lpBuffer, UINT nBufferSize, UINT nGrowBytes = 0);

Параметры метода Attach соответствуют параметрам второго конструктора класса CMemFile, рассмотренному выше. Параметр lpBuffer указывает на буфер размера nBufferSize, который будет связан с файлом.

Если необязательный параметр nGrowBytes равен нулю, то созданный файл будет содержать данные из буфера lpBuffer. Если nGrowBytes больше нуля, то содержимое буфера lpBuffer игнорируется. Если вы запишете в такой файл больше данных, чем помещается в отведенном вами буфере, его размер автоматически увеличивается на nGrowBytes байт. Для управления буфером файла класс CMemFile вызывает стандартные функции malloc, calloc и free. Поэтому, чтобы не нарушать механизм управления памяти, буфер lpBuffer должен быть создан функциями malloc или calloc.


Файлы StdAfx.cpp и StdAfx.h


Самый маленький файл проекта StdAfx.cpp. Исходный текст файла StdAfx.cpp представлен в листинге 4.5.

Листинг 4.5. Файл StdAfx.cpp

#include "stdafx.h"

Фактически файл StdAfx.cpp содержит только директиву #include, предназначенную для подключения файла StdAfx.h. Включаемый файл StdAfx.h, представлен нами в листинге 4.6.

Включаемый файл StdAfx.h предназначен для включения стандартных системных включаемых файлов afxwin.h, afxext.h и afxcmn.h. Если в вашем проекте определены редко изменяемые включаемые файлы, которые используются во многих модулях приложения, вы можете также подключить их в этом файле.

Листинг 4.6. Файл StdAfx.h

// Исключить редко используемые директивы из файлов windows.h

// и afxv_w32.h

#define VC_EXTRALEAN     

// Файл afxwin.h необходим при использовании MFC

#include <afxwin.h>        

// Файл afxwin.h определяет некоторые расширения MFC

#include <afxext.h>        

#ifndef _AFX_NO_AFXCMN_SUPPORT

      // Файл afxcmn.h используется для органов управления

      // операционной системы Windows 95

      #include <afxcmn.h>                                        

#endif // _AFX_NO_AFXCMN_SUPPORT



Главное окно однооконного приложения


Большинство командных сообщений передаются главному окну приложения. Для приложений, имеющих однооконный интерфейс, роль главного окна приложения выполняет объект класса CFrameWnd или объект класса, наследованного от базового класса CFrameWnd.

Получив сообщение, главное окно приложения сначала предоставляет возможность обработать сообщение окну просмотра. И только если окно просмотра не может обработать сообщение, проверяется таблица сообщений класса главного окна приложения.

Если главное окно приложения также не может обработать командное сообщение, оно передается объекту главного класса приложения. Напомним, что главный класс приложения наследуется от базового класса CWinApp и приложение имеет только один объект этого класса.



Главный класс приложения


Теперь приступим к рассмотрению исходного текста приложения, представленного в листинге 1.1.

Сначала мы определяем главный класс приложения CMFDialogApp, наследованный от базового класса CWinApp. Класс CMFDialogApp и его методы используются аналогично приложению MFHello. Отличие заключается в том, что переопределенный метод InitInstance вместо отображения на экране сообщения при помощи функции 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, входящему в класс CWinApp. Элемент данных m_pMainWnd определяет главное окно приложения. В нашем случае главное окно как таковое отсутствует и вместо него выступает диалоговая панель.

Чтобы отобразить диалоговую панель на экране, мы вызываем для объекта dlgTest метод DoModal. На этом выполнение метода InitInstance приостанавливается, пока пользователь не закроет диалоговую панель.

Когда диалоговая панель закрыта, мы отображаем на экране состояние переменной dlgTest.m_Text, которая соответствует полю ввода Edit диалоговой панели. Последний оператор метода return возвращает значение FALSE и приложение завершается.



Главный класс приложения Dialog


Главный класс приложения CDialogApp, наследованный от базового класса CWinApp, определен во включаемом файле Dialog.h. Исходный текст этого файла содержится в листинге 4.1.

Первые строки файла содержат директиву #ifndef, которая проверяет, определен ли символ __AFXWIN_H__. Символ __AFXWIN_H__ определен в файле afxwin.h. Если на этапе обработки файла Dialog.h символ не определен, то при построении проекта выдается сообщение об ошибке. Это гарантирует, что включаемый файл afxwin.h будет обработан до Dialog.h.

Следующая директива #include включает файл resource.h. Этот файл создается MFC AppWizard и содержит определение идентификаторов, задействованных для ресурсов приложения.

Листинг 4.1. Файл Dialog.h

// Dialog.h : Главный включаемый файл для приложения Dialog

//

#ifndef __AFXWIN_H__

      #error include 'stdafx.h' before including this file for PCH

#endif

#include "resource.h"   // включаемый файл содержащий

                        // идентификаторы ресурсов приложения

//////////////////////////////////////////////////////////////

// Класс CDialogApp:

// Методы класса CDialogApp определены в файле Dialog.cpp

//

class CDialogApp : public CWinApp

{

public:

      CDialogApp();

// Overrides

      // В следующем блоке ClassWizard помещает описания

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

      //{{AFX_VIRTUAL(CDialogApp)

public:

      virtual BOOL InitInstance();

      //}}AFX_VIRTUAL

// Implementation

      //{{AFX_MSG(CDialogApp)

             // В этом блоке ClassWizard размещает описания методов

             // класса. Не редактируйте содержимое этого блока вручную

      //}}AFX_MSG

      DECLARE_MESSAGE_MAP()

};

Для класса CDialogApp описан конструктор CDialogApp, не имеющий параметров. Этот конструктор будет использоваться в момент запуска приложения для создания объекта класса CDialogApp.

Кроме конструктора, в классе CDialogApp, переопределяется виртуальный метод InitInstance базового класса CWinApp. Как видите, метод InitInstance находится, после комментария // Overrides, который обозначает секцию переопределения виртуальных методов.


В принципе вы можете удалить комментарий // Overrides, это ни как не повлияет на работу приложения. Все комментарии типа // Overrides и // Implementation вставляются MFC AppWizard для программиста, чтобы ему было легче определить назначение метода или элемента данных класса.

MFC AppWizard поместил объявление метода InitInstance внутри блока комментариев AFX_VIRTUAL. Первая и последняя строка, обозначающая этот блок, не влияют на работу программы, так как они являются комментариями (расположены после символов комментария //). Блок AFX_VIRTUAL нужен ClassWizard, чтобы выделить методы класса, которые им управляются. Вы не должны вручную вносить изменения в этот блок и другие блоки AFX_.

Более подробно о блоках AFX_ и других комментариях, вставляемых MFC AppWizard и ClassWizard, мы рассказывали в разделе “Имена, используемые MFC” данной главы книги.

Основной файл приложения имеет имя, совпадающее с именем проекта - Dialog.cpp. Текст этого файла приведен нами в листинге 4.2. Файл содержит реализацию методов главного класса приложения CDialogApp.

Листинг 4.2. Файл Dialog.cpp

// Dialog.cpp : Определяет главный класс приложения

//

// Включаемые файлы

#include "stdafx.h"

#include "Dialog.h"

#include "DialogDlg.h"

// Для отладочной версии приложения включается дополнительные

// определения

#ifdef _DEBUG

      #define new DEBUG_NEW

      #undef THIS_FILE

      static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////

// CDialogApp

BEGIN_MESSAGE_MAP(CDialogApp, CWinApp)

      //{{AFX_MSG_MAP(CDialogApp)

      // ClassWizard размещает в данном блоке макрокоманды для

      // обработки сообщений. Не изменяйте содержимое этого блока

      // вручную

      //}}AFX_MSG

      ON_COMMAND(ID_HELP, CWinApp::OnHelp)

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////

// Конструктор класса CDialogApp



CDialogApp::CDialogApp()

{

      // TODO: здесь вы можете добавить собственный код

}

//////////////////////////////////////////////////////////////

// Создаем один объект класса CDialogApp. Это будет главный

// объект приложения

CDialogApp theApp;

//////////////////////////////////////////////////////////////

// Инициализация приложения

BOOL CDialogApp::InitInstance()

{

// Стандартная инициализация приложения. Вы можете сократить

// размер выполняемого модуля приложения, если удалите

// инициализацию, которая вам не нужна

#ifdef _AFXDLL

      Enable3dControls();

#else

      Enable3dControlsStatic();

#endif

      CDialogDlg dlg;

      m_pMainWnd = &dlg;

      int nResponse = dlg.DoModal();

      if (nResponse == IDOK)

      {

             // TODO: Здесь вы можете разместить код приложения,

             // который вызывается, если пользователь нажмет кнопку OK

             // в диалоговой панели приложения

      }

      else if (nResponse == IDCANCEL)

      {

             // TODO: Здесь вы можете разместить код приложения,

             // который вызывается, если пользователь нажмет кнопку

             // Cancel в диалоговой панели приложения

      }

      // Так как диалоговая панель закрыта, возвращаем значение

      // FALSE чтобы завершить приложение

      return FALSE;

}

В начале файла Dialog.cpp подключены три файла stdafx.h, Dialog.h и DialogDlg.h. Файл stdafx.h будет описан нами ниже. Сейчас отметим, что он содержит определения, необходимые для библиотеки классов MFC.

Файл Dialog.h содержит описание главного класса приложения CDialogApp. Файл DialogDlg.h включает описание класса диалоговой панели приложения. Именно эта панель будет представлять пользовательский интерфейс нашего приложения.

Далее директива #ifdef проверяет, был ли определен символ _DEBUG. Вы не найдете определение _DEBUG ни в одном исходном файле проекта. Этот символ определяется самой средой VIsual C++, если вы создаете отладочную версию приложения.



#ifdef _DEBUG

      #define new DEBUG_NEW

      #undef THIS_FILE

      static char THIS_FILE[] = __FILE__;

#endif

Для отладочной версии приложения определяется символ DEBUG_NEW и переопределяется статическая переменная THIS_FILE. Если такой символ уже был определен, он предварительно отменяется  директивой #undef, а затем определяется снова.

THIS_FILE определяется как символьная строка, в которую записывается имя исходного файла данного модуля, определенное специальным символом __FILE__ (то, есть Dialog.cpp).

Исходные тексты приложений, подготовленных с использованием средств автоматизированного проектирования MFC AppWizard и ClassWizard, активно используют символы, определенные в среде VIsual C++. Чтобы просмотреть их список, выберите из меню Build строку Settings. На экране появится диалоговая панель Project Settings, содержащая несколько страниц. Выберите страницу C/C ++ (рис. 4.9).

В поле Settings For приложение Dialog представлено двумя строками. Одна выбирает параметры проекта для отладочной версии приложения, а вторая для законченной, не отладочной, версии.

В поле Preprocessor definitions отображается список символов, определенных для данного проекта. Вы можете добавить в этот список новые символы, или убрать символы, которые уже определены.



Рис. 4.9. Настройка проекта

Далее в исходном файле располагается таблица сообщений главного класса приложения. На первый взгляд внешний вид таблицы сообщений соответствует таблице сообщений приложения MFMessage.


Главный объект приложения


В файле Dialog.cpp объявляется глобальный объект главного класса приложения. Именно с создания этого объекта и начинается работа приложения.

CDialogApp theApp;

Объект класса CWinApp обязательно входит во все приложения, созданные с использованием MFC AppWizard, вне зависимости от пользовательского интерфейса этого приложения. Приложения с однооконным, многооконным интерфейсом и с интерфейсом, основанном на диалоговой панели, имеют единственный объект класса CWinApp.


В приложении создается всего один объект класса CSingleApp. Этот объект определяется как статический, поэтому его конструктор получает управление сразу после запуска приложения.

CSingleApp theApp;



Характеристики открытого файла


Чтобы определить расположение открытого файла на диске, надо вызвать метод GetFilePath. Этот метод возвращает объект класса CString, в котором содержится полный путь файла, включая имя диска, каталоги, имя диска и его расширение:

virtual CString GetFilePath() const;

Если требуется определить только имя и расширение открытого файла, можно воспользоваться методом GetFileName. Он возвращает объект класса CString, в котором находится имя файла:

virtual CString GetFileName() const;

В случае, когда надо узнать только имя открытого файла без расширения, воспользуйтесь методом GetFileTitle:

virtual CString GetFileTitle() const;

Последний метод класса CFile позволяет установить путь файла. Этот метод не создает, не копирует и не изменяет имени файла, он только заполняет соответствующий элемент данных в объекте класса CFile:

virtual void SetFilePath(LPCTSTR lpszNewName);

Единственный параметр метода lpszNewName должен содержать указатель на строку символов, содержащую путь файла. Эта строка должна оканчиваться двоичным нулем.



Идентификатор открытого файла


В состав класса CFile входит элемент данных m_hFile типа UINT. В нем хранится идентификатор открытого файла. Если вы создали объект класса CFile, но еще не открыли никакого файла, то в m_hFile записана константа hFileNull.

Обычно нет необходимости непосредственно использовать идентификатор открытого файла. Методы класса CFile позволяют выполнять практически любые операции с файлами и не требуют указывать идентификатора файла. Так как m_hFile является элементом класса, то реализация его методов всегда имеет свободный доступ к нему.



Имена, используемые MFC


Библиотека классов содержит огромное количество классов, структур, констант и т. д. Чтобы сделать исходный текст приложений MFC более легким для понимания, принято использовать ряд соглашений для используемых имен и комментариев.

Названия всех классов и шаблонов классов библиотеки MFC начинаются с заглавной буквы C. Например, CWnd, CMenu, CArray - это классы. Когда вы наследуете собственные классы от классов MFC, вы можете давать им любые имена. Рекомендуется называть их как и классы MFC с заглавной буквы C. Это сделает исходный текст приложения более ясным для понимания.

Чтобы отличать элементы данных, входящие в класс, от простых переменных, их имена принято начинать с префикса  m_. Названия методов классов, как правило специально не выделяются, но их обычно  пишут с заглавной буквы.

Библиотека MFC включает помимо классов, набор служебных функций. Названия этих функций начинаются с символов Afx, например AfxGetApp, AfxGetAppName, AfxGetMainWnd. Символы AFX являются сокращением от словосочетания Application FrameworkX, означающего основу приложения, его внутреннее устройство.

Символы AFX встречаются не только в названии функций MFC. Многие константы, макрокоманды и другие символы начинаются с этих символов. В общем случае Afx является признаком, по которому вы можете определить принадлежность того или иного объекта (функции, переменной, ключевого слова или символа) к библиотеке MFC.

В процессе создания приложения MFC AppWizard и ClassWizard могут определять идентификаторы ресурсов, идентификаторы для справочной системы и т. д. При этом, для идентификаторов различного типа используются разные префиксы:

Префикс

Представляет идентификаторы

HID_

Контекстной подсказки для команд

HIDD_

Контекстной подсказки для диалоговых панелей

ID_

Строк меню и кнопок панелей управления

IDB_

Растровых изображений bitmap

IDC_

Курсоров

IDC_

Органов управления диалоговых панелей

IDD_

Шаблонов диалоговых панелей

IDI_

Пиктограмм

IDP_

Строковых ресурсов, используемые в диалоговых панелях message box для отображения приглашения

IDR_

Приложение может иметь несколько ресурсов различного типа с одинаковыми идентификаторами. Для таких идентификаторов используют префикс IDR_

IDS_

Строковых ресурсов

IDOK, IDCANCEL

Стандартные идентификаторы для кнопок OK и Cancel диалоговых панелей

<
Когда приложение разрабатывается средствами MFC AppWizard и ClassWizard, они размещают в исходном тексте приложения комментарии следующего вида:

//{{AFX_

  ...

//}}AFX_

Такие комментарии образуют блок кода программы, который управляется только средствами MFC AppWizard и ClassWizard. Пользователь не должен вручную вносить изменения в этом блоке. Для этого необходимо использовать средства ClassWizard.

Чтобы подчеркнуть особое положение программного кода, заключенного в комментарии //{{AFX_, он отображается серым цветом. Это еще раз напоминает пользователю, о том, что он не должен вручную вносить изменения в этот код. В следующей таблице представлено краткое описание некоторых блоков //{{AFX_.

Блок

Включает

//{{AFX_DATA 

//}}AFX_DATA

Объявление элементов данных класса. Используется в описании классов диалоговых панелей

//{{AFX_DATA_INIT 

//}}AFX_DATA_INIT

Инициализация элементов данных класса. Используется в файле реализации классов диалоговых панелей

//{{AFX_DATA_MAP 

//}}AFX_DATA_MAP

Макрокоманды DDX, предназначенные для связывания элементов данных класса и органов управления диалоговых панелей. Используется в файле реализации классов диалоговых панелей

//{{AFX_MSG

//}}AFX_MSG

Описание методов, которые предназначены для обработки сообщений. Этот блок используется при описании класса

//{{AFX_MSG_MAP

//}}AFX_MSG_MAP

Макрокоманды таблицы сообщений класса. Используются совместно с AFX_MSG

//{{AFX_VIRTUAL

//}}AFX_VIRTUAL

Описание переопределенных виртуальных методов класса. Блок AFX_VIRTUAL используется при описании класса

Мы перечислили далеко не все блоки //{{AFX_. Существует еще целый ряд блоков, относящихся к реализации технологии OLE и использованию баз данных.

Когда вы будете изучать описание классов приложения, созданных средствами MFC AppWizard и ClassWizard, вы заметите ряд комментариев, разделяющих элементы класса на несколько категорий. Описание этих комментариев мы привели в следующей таблице.

Комментарий

После комментария размещаются

// Constructors

Конструкторы класса и методы, используемые для инициализации объектов класса. Как правило, элементы класса размещенные в этой секции определены с ключевым словом public

// Attributes

Элементы данных класса, и методы для доступа к ним (свойства класса). Как правило, элементы класса размещенные в этой секции определены с ключевым словом public

// Operations

Виртуальные и не виртуальные методы, используемые для выполнения операций над объектами класса. Как правило элементы класса размещенные в этой секции определены с ключевым словом public

// Overridables

Здесь расположены виртуальные методы, которые вы можете переопределить в порожденных классах. Как правило, элементы класса размещенные в этой секции определены с ключевым словом protected. В большинстве случаев, названия виртуальных методов класса начинается с символов On

// Implementation

Методы и элементы данных, относящиеся к внутреннему устройству класса - реализации класса. Как правило, элементы класса размещенные в этой секции определены с ключевым словом protected или private

// Dialog Data

Элементы данных, класса диалоговой панели, связанные с органами управления

Для некоторых классов, используются и другие комментарии, например, // Advanced Overridables и т. д.

MFC AppWizard и ClassWizard помогают вам разрабатывать приложение. Они создают все классы и методы, необходимые для его работы. Вам остается дописать к ним свой код. В тех местах, где вы можете вставить этот код, MFC AppWizard и ClassWizard, как правило, помещают комментарий // TODO:.


Информация о классе


Класс CObject содержит два метода: GetRuntimeClass и IsKindOf, позволяющих получить информацию о классе объекта.



Информация о классе объекта (структура CRuntimeClass)


Во многих случаях бывает необходимо уже во время работы приложения получить информацию о классе объекта и его базовом классе. Для этого любой класс, наследованный от базового класса CObject связан с структурой CRuntimeClass. Она позволяет определить имя класса объекта, размер объекта в байтах, указатель на конструктор класса, не имеющий аргументов и деструктор класса. Можно также узнать подобную информацию о базовом классе и некоторые дополнительные сведения.



Исходные тексты приложения


Рассмотрим исходные тексты приложения более подробно. Из главы “Введение в MFC” вы уже знаете, что приложения, созданные на основе библиотеки MFC, как правило, не имеют главной функции приложения WinMain.

Функция WinMain скрыта от программиста внутри методов класса CWinApp. Каждое приложение должно иметь один объект класса, наследованного от базового класса CWinApp. Поэтому свой рассказ мы начнем с главного класса приложения.



Исключения (класс CException)


Для реализации механизма исключений в MFC определен специальный класс CException, наследованный от базового класса CObject. Все исключения, определенные в MFC, наследуются от этого класса. Вот список классов, наследованных от CException и их краткое описание. Более полное описание классов, связанных с исключениями, вы можете найти в разделе “Исключения - класс CException” главы “Вспомогательные классы MFC”.

Класс

Описание

CArchiveException

Исключение, вызванное ошибкой при использовании объекта класса CArchive. Класс CArchive применяется для сохранения и загрузки документа из файла на диске

CDaoException

Ошибка при работе с базами данных (при использовании классов DAO)

CDBException

Ошибка при работе с базами данных (при использовании ODBC)

CFileException

Ошибка, связанная с файловой системой

CMemoryException

Недостаточно оперативной памяти

CNotSupportedException

Попытка выполнить неопределенную операцию

COleDispatchException, COleException

Ошибка OLE

CResourceException

Не найден ресурс

CUserException

Ошибка приложения, вызванная действиями пользователя


Обработчик исключения может выполнять различные действия в зависимости от того какое исключение и в каком контексте было вызвано. Для этого вы можете использовать методы и данные из объекта, переданного в обработчик исключения.

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

Класс

Исключение вызывается

CMemoryException

При распределении оперативной памяти

CFileException

При работе с файлами

CArchiveException

Во время записи или восстановления объектов (Archive/Serialization)

CNotSupportedException

При обращении к неизвестный метод, который не поддерживается данным классом

CResourceException

Ошибка при работе с ресурсами Windows

CDaoException

Ошибка при работе с базами данных, через средства DAO

CDBException

Ошибка при работе с базами данных, через средства ODBC

COleException

Ошибка при работе OLE

COleDispatchException

Ошибка при работе OLE

CUserException

При обработке этого исключения на экране отображается сообщение, а затем вызывается исключение CException

Сейчас мы не будем рассматривать исключения, связанные с технологией OLE и базами данных.


Изменение порядка обработки сообщений


Порядок обработки командных сообщений определяется виртуальным методом OnCmdMsg. Этот метод первоначально определен в классе CCmdTarget и переопределен в классах CFrameWnd, CView, CDocument и некоторых других классах MFC.

В ряде случаев может понадобиться изменить порядок, в котором командные сообщения передаются объектам приложения или ввести новый объект в цепочке стандартных объектов. В этом случае вы должны переопределить виртуальный метод OnCmdMsg соответствующим образом. В качестве примера использования метода OnCmdMsg вы можете взять класс CView. Если вы установили Visual C++ с исходными текстами библиотеки MFC, вы можете найти определение этого метода в файле Viewcore.cpp.



Как связаться с авторами


Авторы имеют почтовый адрес в сети GlasNet. Все свои замечания и предложения по содержанию книг серий "Библиотека системного программиста", а также "Персональный компьютер - шаг за шагом" вы можете присылать нам по следующему адресу:

frolov@glas.apc.org

Наш почтовый адрес доступен не только пользователям сети GlasNet. Абоненты других компьютерных сетей также могут передавать нам сообщения. Ниже мы приводим наш адрес в различных сетях:

Глобальная сеть

Наш адрес

CompuServe

>internet:frolov@glas.apc.org

GlasNet

frolov@glas.apc.org

Internet

frolov@glas.apc.org

Relcom

frolov@glas.apc.org

UUCP

uunet!cdp!glas!frolov

Вы также можете присылать свои пожелания почтой по адресу:

Издательский отдел АО "ДИАЛОГ-МИФИ".

Индекс 115409, город Москва, улица Москворечье, дом 31, корпус 2.

Приносим свои извинения за то, что не можем ответить на каждое письмо. Мы также не занимаемся рассылкой дискет и исходных текстов к нашим книгам. По этому вопросу обращайтесь непосредственно в издательство “Диалог-МИФИ”.



Класс CArchiveException


Исключительные ситуации, возникающие во время записи и восстановления объектов из файла, вызывают исключение CArchiveException.

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

Константа

Причина ошибки

CArchiveException:: none

Без ошибки

CArchiveException:: generic

Неопределенная ошибка

CArchiveException:: readOnly

Попытка записи в архивный объект, открытый для чтения

CArchiveException:: endOfFile

Обнаружен конец файла при чтении объекта

CArchiveException:: writeOnly

Попытка читать из архивного объекта, открытого для записи

CArchiveException:: badIndex

Неправильный формат файла

CArchiveException:: badClass

Попытка прочитать объект в объект неправильного типа

CArchiveException:: badSchema

Попытка чтения объекта с несоответствующей версией класса

Чтобы создать объект CArchiveException и вызвать исключение воспользуйтесь функцией AfxThrowArchiveException:

void AfxThrowArchiveException(int cause);

Параметр cause должен определять причину вызова исключения. Возможный список значений этого параметра представлен в таблице выше (см. элемент данных m_cause класса CArchiveException).



Класс CException


Класс CException включает два виртуальных метода GetErrorMessage и ReportError. Эти методы позволяют получить словесное описание причины, которая привела к вызову исключения. Заметим, что методы GetErrorMessage и ReportError чисто виртуальные, поэтому они должны быть переопределены в наследуемом классе:

virtual BOOL

GetErrorMessage(LPTSTR lpszError, UINT nMaxError,

      PUINT pnHelpContext = NULL);

Когда вы вызываете в обработчике исключения метод GetErrorMessage, он записывает в буфер lpszError сообщение об ошибке, вызвовшей исключение. Размер буфера надо указать в параметре nMaxError. В конце сообщения всегда записывается символ двоичного нуля. Если сообщение не помещается в буфер lpszError (оно больше чем nMaxError - 1 байт), тогда в буфер записываются только nMaxError - 1 символов сообщения. В последний байт записывается двоичный нуль.

Необязательный параметр pnHelpContext может содержать указатель на переменную типа UINT, в которую будет записан идентификатор контекстной подсказки (help context ID).

Метод GetErrorMessage возвращает ненулевое значение, если сообщение об ошибке доступно, и нуль в противном случае.

Вы можете вызывать метод ReportError из обработчика исключений:

virtual int

ReportError(UINT nType = MB_OK, UINT nMessageID = 0);

Метод ReportError отображает в диалоговой панели на экране сообщение об ошибке, вызвавшей данное исключение. Параметр nType определяет внешний вид диалоговой панели сообщения. В качестве параметра nType можно указать любую комбинацию стилей панелей сообщения, таких как MB_OK,  MB_OKCANCEL,  MB_RETRYCANCEL, MB_ICONEXCLAMATION, MB_ICONINFORMATION, MB_ICONQUESTION, MB_ICONSTOP. Если вы не укажите этот параметр, тогда подразумевается стиль MB_OK, то есть панель сообщения с одной кнопкой OK.

Иногда исключение может не иметь текстового описания. Вы можете указать методу ReportError, чтобы он отображал в этом случае определенное сообщение. Текст этого сообщения надо сохранить в строковом ресурсе, а соответствующий идентификатор передать методу ReportError в качестве второго параметра nMessageID. Если вы не укажите этот параметр, тогда отображается сообщение “No error message is available”.

Метод ReportError возвращает значение типа AfxMessageBox. Оно определяет, какая кнопка была нажата в диалоговой панели с сообщением.

Методы GetErrorMessage и ReportError используют данные из ресурсов, созданных AppWizard. Поэтому они могут работать неправильно, если приложение создано без использования AppWizard.



Класс CFileException


Класс CFileException предназначен для обработки исключительных ситуаций, возникающих во время создания или вызова методов класса CFile и порожденных от него классов. Этот класс описан нами в разделе “Класс CFile” и предназначается для работы с файловой системой. Естественно, при работе с файловой системой могут возникнуть самые разнообразные ошибки (исключительные ситуации): попытка открыть несуществующий файл, переполнение диска во время операции записи, ошибка чтения с диска и т. д.

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

Константа

Причина ошибки

CFileException:: none

Без ошибки

CFileException:: generic

Неопределенная ошибка

CFileException:: fileNotFound

Файл не найден

CFileException:: badPath

Задан несуществующий путь

CFileException:: tooManyOpenFiles

Открыто слишком много файлов

CFileException:: accessDenied

Доступ к файлу закрыт

CFileException:: invalidFile

Использование неправильного идентификатора (дескриптора) файла

CFileException:: removeCurrentDir

Попытка удалить текущий каталог

CFileException:: directoryFull

Переполнение структуры каталогов. Невозможно создать новый каталог

CFileException:: badSeek

Ошибка во время перемещения указателя файлов

CFileException:: hardIO

Ошибка аппаратного обеспечения компьютера

CFileException:: sharingViolation

Программа SHARE.EXE не загружена или общая область заблокирована (locked)

CFileException:: lockViolation

Попытка заблокировать область файла, которая уже была заблокирована ранее

CFileException:: diskFull

Нет свободного пространства на диске

CFileException:: endOfFile

Достигнут конец файла

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



Класс CMainFrame


Класс CMainFrame представляет главное окно нашего приложения. Внутри этого окна отображаются окно просмотра документа, а также панели управления и состояния. Определение класса CMainFrame расположено в файле MainFrm.h.

Класс CMainFrame наследуется от базового класса CFrameWnd, определенного в библиотеке MFC. Как правило у программиста не возникает необходимости как-либо дорабатывать класс главного окна приложения, созданный MFC AppWizard.

class CMainFrame : public CFrameWnd

{

protected:

      CMainFrame();

      DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

// Operations

public:

// Overrides

      //{{AFX_VIRTUAL(CMainFrame)

      virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

      //}}AFX_VIRTUAL

// Implementation

public:

      virtual ~CMainFrame();

#ifdef _DEBUG

      virtual void AssertValid() const;

      virtual void Dump(CDumpContext& dc) const;

#endif

protected: 

      // Панель управления и панель состояния

      CStatusBar  m_wndStatusBar;

      CToolBar    m_wndToolBar;

// Методы, предназначенные для обработки сообщений

protected:

      //{{AFX_MSG(CMainFrame)

      afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

      //}}AFX_MSG

      DECLARE_MESSAGE_MAP()

};

Шаблон документов приложения создает объекты класса CMainFrame динамически. Для этого в определении класса указана макрокоманда DECLARE_DYNCREATE, объявлен конструктор, не имеющий параметров, а в файле реализации добавлена макрокоманда IMPLEMENT_DYNCREATE.

// Объекты класса CMainFrame могут создаваться динамически

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)



Класс CMemoryException


Когда приложение заказывает у операционной системы новый блок оперативной памяти, может случиться, что вся память уже используется и больше памяти отдано приложению быть не может.

Когда приложение пытается создать новую переменную или объект, вызывая оператор new, то в том случае, если память под него не может быть выделена, создается объект класса CMemoryException и вызывается соответствующее исключение.

Функции malloc не вызывают исключение CMemoryException, но вы можете проверять значение, возвращаемое функцией malloc, и если оно равно нулю, вызывать его сами.

Чтобы самому вызвать исключение, воспользуйтесь функцией AfxThrowMemoryException:

void AfxThrowMemoryException();

Эта функция не пытается получить у операционной системы дополнительную память, а использует память полученную ранее. В противном случае, возможно, вы не смогли бы создать даже объекта CMemoryException, так как свободная память уже кончилась.

Приведем небольшой пример использования этой функции. Допустим, вы пытаетесь получить область оперативной памяти для хранения данных с помощью функции GlobalAlloc. Если операционная система не может выделить область памяти такого размера, она возвращает NULL и вы можете вызвать функцию AfxThrowMemoryException:

if(GlobalAlloc(GMEM_FIXED, 1000000) == NULL)

      AfxThrowMemoryException();



Класс CNotSupportedException


Если приложение пытается вызвать несуществующий метод класса, то вызывается исключение CNotSupportedException. Конструктор класса CNotSupportedException имеет следующий вид:

CNotSupportedException();

Однако если вы сами желаете вызвать из своего кода исключение этого типа, то вместо того, чтобы создавать объект класса CNotSupportedException вручную и передавать его оператору throw, воспользуйтесь функцией AfxThrowNotSupportedException:

void AfxThrowNotSupportedException();



Класс CObject - основной класс MFC


Подавляющее большинство классов из библиотеки MFC наследуются от основного класса CObject. Практически все классы, которые используются в ваших приложениях, например CView или CWinApp, унаследованы от класса CObject.

Класс CObject обеспечивает наиболее общие свойства всех других классов. Так, класс CObject позволяет сохранять и восстанавливать состояние объектов, порожденных из данного класса. Например, текущее состояние объекта можно записать в файл на диске, а затем восстановить объект из этого файла. Этот процесс иногда называют преобразованием в последовательную форму (serialization).

Для объектов, порожденных от CObject, можно получить информацию во время работы программы. Такая возможность используется при динамическом создании объектов. MFC версии 4.0 позволяет также определить имя типа объекта во время работы программы. Эта особенность называется Run-Time Type Information (RTTI). Кроме того возможности класса CObject используются для вывода диагностических сообщений объектами, порожденными от этого класса.

Класс CObject не позволяет осуществлять множественное наследование. Ваш класс должен иметь только один базовый класс CObject.

Чтобы приложение смогло воспользоваться всеми возможностями класса CObject, необходимо включить в описание и определение класса специальные макрокоманды.

Например, чтобы получить возможность во время выполнения приложения определять название класса и его базовый класс, следует включить в объявление и определение вашего класса макрокоманды DECLARE_DYNAMIC  и IMPLEMENT_DYNAMIC. А если вам надо, чтобы состояние объектов класса можно было сохранять и восстанавливать из архивного класса, следует включить в объявление и определение класса макрокоманды DECLARE_SERIAL и IMPLEMENT_SERIAL.



Класс CPoint - точка на плоскости


В предыдущих томах серии “Библиотека системного программиста” мы рассматривали структуру POINT, используемую средствами разработки приложений Windows. Структура POINT позволяет сохранить координаты точки в двумерном пространстве.

Библиотека классов MFC включает в себя класс CPoint, который можно использовать вместо структуры POINT. Класс CPoint имеет несколько конструкторов, которые вы можете использовать.

Первый конструктор класса не имеет параметров:

CPoint();

Вы можете создать объект класса CPoint и сразу присвоить ему значения. Если известны отдельные координаты точки, воспользуйтесь конструктором с двумя параметрами:

CPoint(int initX, int initY);

Первый параметр initX определяет х-координату точки, а параметр initY - y-координату точки. Если надо создать объект CPoint и записать в него координаты из переменной типа POINT или другого объекта класса CPoint, используйте другой конструктор:

CPoint(POINT initPt);

Можно создать объект CPoint и записать в него данные из объекта класса CSize или структуры SIZE:

CPoint(SIZE initSize);

Если у вас есть переменная типа DWORD, в младшем слове которой записана x-координата точки, а в старшем слове y-координата, то можно создать объект класса CPoint и записать в него данные из этой переменной:

CPoint(DWORD dwPoint);

Объекты класса CPoint можно сравнивать друг с другом, пользуясь обычными операторами равенства == (равно) и != (не равно). Результатом действия этих операторов является значение типа BOOL. Если условие определенное операторами равенства выполняется, возвращается ненулевое значение. В противном случае результат равен нулю.



Класс CResourceException


Если в процессе работы возникают проблемы с ресурсами, например приложение пытается загрузить несуществующий ресурс, тогда вызывается исключение CResourceException. Вы можете вызвать это исключение сами. Для этого воспользуйтесь функцией AfxThrowResourceException:

void AfxThrowResourceException();



Класс CSingleApp


Главный класс приложения CSingleApp наследуется от базового класса CWinApp. Вы можете просмотреть определение класса, если выполните двойной щелчок левой клавишей мыши по его названию в окне Project Workspace. Откроется окно редактора и в него загрузится файл Single.h. Курсор будет автоматически установлен на описание класса CSingleApp.

//////////////////////////////////////////////////////////////

// Класс CSingleApp:

class CSingleApp : public CWinApp

{

public:

      CSingleApp();

// Overrides

      //{{AFX_VIRTUAL(CSingleApp)

public:

      virtual BOOL InitInstance();

      //}}AFX_VIRTUAL

// Implementation

      //{{AFX_MSG(CSingleApp)

      afx_msg void OnAppAbout();

      //}}AFX_MSG

      DECLARE_MESSAGE_MAP()

};



Класс CSingleDoc


Следующий класс который мы рассмотрим - это класс документа нашего приложения CSingleDoc. В качестве базового класса для него используется класс CDocument библиотеки MFC.

Класс CSingleDoc, несколько сложнее главного класса приложения, рассмотренного выше.

class CSingleDoc : public CDocument

{

protected:

      CSingleDoc();

      DECLARE_DYNCREATE(CSingleDoc)

// Attributes

public:

// Operations

public:

// Overrides

      //{{AFX_VIRTUAL(CSingleDoc)

public:

      virtual BOOL OnNewDocument();

      virtual void Serialize(CArchive& ar);

      //}}AFX_VIRTUAL

// Implementation

public:

      virtual ~CSingleDoc();

#ifdef _DEBUG

      virtual void AssertValid() const;

      virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Методы, предназначенные для обработки сообщений

protected:

      //{{AFX_MSG(CSingleDoc)

      //}}AFX_MSG

      DECLARE_MESSAGE_MAP()

};

Просмотрите исходные тексты приложения. Вы не обнаружите кода, который бы явно создавал объекты этого класса. Объект класса CSingleDoc создается динамически шаблоном документа, во время работы приложения. Шаблон документа также динамически создает еще два объекта - класса окна и класса окна просмотра.

Для того чтобы объекты любого класса, наследованного от базового класса CObject, в том числе и CSingleDoc, можно было создавать динамически, необходимо выполнить следующее:

в описании класса надо поместить макрокоманду DECLARE_DYNCREATE. В качестве параметра этой макрокоманды необходимо указать имя данного класса;

определить конструктор класса, который не имеет параметров;

разместить макрокоманду IMPLEMENT_DYNCREATE в файле реализации. Макрокоманда IMPLEMENT_DYNCREATE имеет два параметра. В первом указывается имя класса, а во втором имя его базового класса

MFC AppWizard автоматически выполняет все эти требования для класса документа приложения CSingleDoc, класса окна приложения CMainFrame и класса окна просмотра CSingleView.



Класс CSingleView


Следующий класс, который мы рассмотрим, является классом окна просмотра документа CSingleView. Этот класс наследуется от базового класса CView библиотеки MFC. Определения класса CSingleView вы можете найти в файле SingleView.h.

Окно просмотра и связанный с ним класс окна просмотра документа создается шаблоном документа в процессе работы приложения. Поэтому необходимо, чтобы объекты класса CSingleView можно было создавать динамически.

Для этого определяется конструктор класса, не имеющий параметров, в определении класса указывается макрокоманда DECLARE_DYNCREATE, а в файле реализации макрокоманда IMPLEMENT_DYNCREATE.

class CSingleView : public CView

{

protected:

      CSingleView();

      DECLARE_DYNCREATE(CSingleView)

// Attributes

public:

      CSingleDoc* GetDocument();

// Operations

public:

// Overrides

      //{{AFX_VIRTUAL(CSingleView)

public:

      virtual void OnDraw(CDC* pDC);

      virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

      virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

      virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

      virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

      //}}AFX_VIRTUAL

// Implementation

public:

      virtual ~CSingleView();

#ifdef _DEBUG

      virtual void AssertValid() const;

      virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Методы, предназначенные для обработки сообщений

protected:

      //{{AFX_MSG(CSingleView)

      //}}AFX_MSG

      DECLARE_MESSAGE_MAP()

};

Секция Overrides в описании класса CSingleView содержит описания переопределяемых виртуальных методов базового класса CView. Два метода описаны в ней как public - OnDraw и PreCreateWindow и три как protected - OnPreparePrinting, OnBeginPrinting, OnEndPrinting. Поэтому методы OnDraw и PreCreateWindow можно вызывать и из других классов приложения, а методы OnPreparePrinting, OnBeginPrinting, OnEndPrinting только из класса CSingleView.



Класс CSize - относительные координаты


Класс CSize создан на основе структуры SIZE, предназначенной для определения относительных координат точек в двумерном пространстве. Так как CSize наследуется от структуры SIZE, он включает все ее поля. Вы можете непосредственно обращаться к элементам SIZE. Элементы данных cx и cy объявлены как доступные. Объекты класса CSize можно использовать вместо переменных типа SIZE - их можно указывать в качестве параметров функций. Вы можете передавать объекты CSize функциям, которые принимают параметры типа SIZE. Объявление класса CSize расположено во включаемом файле #include <afxwin.h>.



Класс CString - текстовые строки


Практически ни одно приложение не обходится без использования текстовых строк. Язык Си и Си++ не имеет специального строкового типа. Обычно для хранения строк используют массивы символов. MFC предоставляет программисту более удобное средство для организации и работы со строками. Для этого предназначен специальный класс CString.

Строки CString состоят из символов, типа TCHAR. Тип TCHAR определяется по разному в зависимости от того, определен или нет символ _UNICODE. Если символ _UNICODE не определен, TCHAR соответствует обычному типу char. В противном случае он представляет символы, определяемые двумя байтами.

При использовании класса CString отпадает необходимость постоянно следить за тем, чтобы длина строки не превысила объем выделенной для нее памяти. Строки CString автоматически расширяются и сокращаются в зависимости от фактической длины строки.

В класс CString входят методы, выполняющие все основные операции над строками - копирование, сравнение, присваивание и т. д. Над объектами класса CString определены такие операции как +, -, облегчающие работу со строками. Строки, представленные объектами CString, можно сравнивать, используя обычные операторы сравнения <, >, <=, >=, ==. Объекты класса CString можно записывать на диск - для них работает механизм сохранения и восстановления объектов класса. Фактически вы можете работать со стоками как с другими простыми типами данных.

Строки CString можно использовать совместно с строками языка Си (массивами символов, заканчивающихся знаком двоичного нуля). Вы можете обращаться к объектам класса CString как к обычным массивам символов, то есть использовать их как строки символов языка Си. Вы можете указывать строки в качестве параметра функций и методов, которые принимают параметры типа const char* или LPCTSTR.

Класс CString содержит большое количество различных методов, предназначенных для работы со строками. Среди них есть методы для доступа к отдельным элементам строки, сравнения строк между собой, поиска подстрок, сохранения строк в файле и их последующего восстановления. Мы оставляем класс CString для вашего самостоятельного изучения и ограничимся описанием только самых основным методов.