Глава 18. Основы библиотеки MFC

Из предыдущих глав вы узнали, что создание даже самых простых приложений Windowsс использованием стандартных API-функций является довольно сложной задачей, требующей много времени и внимания. Например, код простейшего при­ложения SWP.C(мы рассматривали его в предыдущей главе) на языке С занял почти две страницы текста. Большая часть кода нужна лишь для того, чтобы вывести на экран окно приложения и поддерживать его работу.

Компилятор MicrosoftVisualC++ предлагает мощную библиотеку классов (Mi­crosoftFoundationClasses— MFC), предназначенную для разработки 32-разрядных приложений, отвечающих всем стандартам и требованиям сегодняшнего дня. В MFCинкапсулированы все API-функции Windowsи дополнительно обеспечивается под­держка панелей инструментов, страниц свойств, технологии OLE, элементов управ­ления ActiveXи многое другое. Кроме того, имеются функции управления базами данных, основанными на разных источниках данных, включая ОАО и ODBC. Поддерживается и разработка приложений для Internet.

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

Зачем нужны библиотеки классов

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

Ниже перечислены основные преимущества использования библиотек классов:

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

Принципы, лежащие в основе MFC

Создатели MFC при разработке данного продукта придерживались строгих правил и стандартов. Основные принципы, на которых базируется MFC, перечислены ниже:

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

Одна из поставленных перед разработчиками MFC задач состояла в том, чтобы программисты, уже знакомые с API-функциями, не заучивали новый, далеко не малый набор имен функций и констант. Это требование строго соблюдалось при именовании членов классов. Данная особенность выгодно отличает MFC от других библиотек классов.

При создании MFCтакже учитывалась возможность работы в смешанном режиме. Другими словами, в одной программе могут применяться как библиотека MFC, так и API-функции. Некоторые функции, например SetCursor() и GetSystemMetrics(), должны вызываться напрямую, даже если в приложении используется MFC. Еще одно важное свойство, на которое обращали внимание разработчики Microsoft, — это возможность непосредственного использования базовых классов в программах. Существовавшие до MFCбиблиотеки классов были чересчур абстрактными. В Microsoft их называли "тяжелыми классами", поскольку основанные на них приложения отличались большим размером программного кода и недостаточно быстро выполнялись. Разработчикам MFCудалось найти золотую середину между разумным уровнем абстрактности и размером кода.

Ключевые особенности MFC

Ниже перечислены основные достоинства библиотеки MFC.

 

Опытный программист сразу же оценит две наиболее важные особенности MFC: знакомые имена методов и привязка сообщений к обработчикам. Если вы вернетесь к программе PIE.С, рассмотренной в предыдущей главе, то убедитесь в обилии блоков switch/case. Увлечение такими конструкциями чревато возникновением трудно обнаруживаемых ошибок при выполнении программы. Эту проблему легко обойти с помощью MFC.

Профессиональный разработчик будет удовлетворен быстротой выполнения про-фаммного кода библиотечных классов. Интенсивное использование MFCтеперь не щшведет к тяжеловесности полученного приложения.

Все начинается с CObject

Большинство библиотек классов, в том числе и MFC, имеют какой-нибудь один общий родительский класс, от которого порождаются все остальные. В MFCтаковым является CObject. Ниже показано описание этого класса, взятое из файла AFX.H. (Файлы заголовков библиотеки MFCхранятся в папке MFC/INCLUDE.)

/////////// Класс CObject является базовым для классов MFC

class CObject

{  

public:

// Модель объекта (проверка типа, выделение памяти, уничтожение)
virtual CRuntimeClass* GetRuntimeClass0
const/­virtual -CObject ();  // необходим виртуальный деструктор

// Резервирование и удаление памяти
void* PASCAL operator new(size_t nSize);
void* PASCAL operator new(size_t, void* p) ;
void PASCAL operator delete(void* p) ;

fif defined(_DEBUG) && ! defined (_AFX_NO_DEBUG_CRT)  '?','_

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

// (используется при отладке)

void* PASCAL operator new(size_t nSize, LPCSTR IpszFileName,

intnLine); #endif

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

protected: CObject ();

private:

CObject(const CObjectS objectSrc);       //реализация отсутствует
void operator=(const CObjectS objectSrc);// реализация отсутствует

// Атрибуты
public:

BOOL IsSerializableОconst;

BOOL IsKindOf(const CRuntimeClass* pClass) const;

// Виртуальные методы

virtual void Serialize (CArchiveS ar) ;

// Поддержка диагностики

virtual void AssertValidO const;

virtual void Dump (CDumpContextS. dc) const;

// Реализация
public:

static const AFX_DATA CRuntimeClass classCObject;

#ifdef _AFXDLL

static CRuntimeClass* PASCAL _GetBaseClass();

#endif

};

}

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

Обратите внимание, из каких компонентов состоит описание класса CObject. Как видите, здесь четко прослеживается выделение блоков открытых (public), закрытых (private) и защищенных (protected) членов класса. CObjectтакже обеспечивает возможность динамического определения типа и сериализации. Вспомните, что возможность динамической проверки типа позволяет определять тип объекта во время выполнения программы. Сведения о состоянии объекта можно сохранить в файле на диске, благодаря чему реализуется концепция постоянства.

Все остальные классы MFCпорождаются от CObject. Примером такого класса может служить CGdiObject(объявлен в файле AFXWIN.H), описание которого приведено ниже.

///////////

// Абстрактныйкласс CGdiObject дляметода CDC: :SelectObject

class CGdiObject : public CObject {

DECLARE_DYNCREATE (CGdiObject) 
public:

// Атрибуты

HGDIOBJ m_hObject;  // должна быть первой переменной-членом
operator HGDIOBJ () const;
HGDIOBJ GetSafeHandleO const;

static CGdiObject* PASCAL FromHandle (HGDIOBJ hObject) ;   
static void PASCAL DeleteTempMap () ;  
BOOL Attach (HGDIOBJ hObject) ;
HGDIOBJ Detach () ;            

// Конструкторы

CGdiObject() ; // должен создавать объект производного класса BOOLDeleteObject() ;

// Методы

int GetObject (int nCount, LPVOID IpObject) const;

UINT GetObjectTypeO const;

BOOL CreateStockObject (int nlndex) ;

BOOL UnrealizeObjectO ;

BOOL operator— (const CGdiObjectS ob j ) const;

root. nnf=rator! = (const CGdiObiectS obi)const;

// Реализация
public:

virtual -CGdiObject() ; tifdef _DEBUG

virtual void Dump (CDumpContextS. dc) const;

virtual void AssertValid() const; #endif };

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

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

Например, в традиционных процедурных программах вызов функции DeleteObject()имеет следующий синтаксис:

DeleteObject(hBRUSH); /* где hBRUSH- дескриптор кисти */  

В MFC-приложениях того же результата можно достичь путем вызова функции-члена:

newbrush.DeleteObject (); // где newbrush- текущая кисть

Иерархия классов MFC

Ниже показан список классов библиотеки MFC, порожденных от CObject.

CObject

CException

CArchiveException CDaoException CDBException

CFileException           
CInternetException
  CMemoryException

CNotSupportedException
COleDispatchException
COleException
CResourceException
CUserException

CFile          
CMemFile

CSharedFile

COleStreamFile          
CMonikerFile

CAsyncMonikerFile

CDataPathProperty

CCachedDataPathProperty
CSocketFile    

CStdioFile

CInternetFile

CGopherFile
CHttpFile
CRecentFileList CDC

CClientDC

CMetaFileDC

CPaintDC

CWindowDC
CDocState
CImageList
CGdiObject

CBitmap

CBrush

CFont

CPalette

CPen

CRgn CMenu

CCommandLinelnfo
CDatabase
CRecordSet
CLongBinary
CDaoDatabase
CDaoQueryDef
CDaoRecordSet
CDaoTableDef
CDaoWorkspace
CSyncObject

CCriticalSection

CEvent

CMutex

CSemaphore CAsyncSocket

CSocket
CArray
CByteArray
CDWordArray
CObArray
CPtrArray
CStringArray
CUIntArray
CWordArray
CList
CObList
CPtrList
CStringList
CMap

CMapWordToOb
CMapWordToPtr
CMapPtrToPtr
CMapPtrToWord
CMapStringToOb

CMapStringToPtr
CMapStringToString
CInternetSession
CInternetconnection
CFtpConnection
CGopherConnection
CHttpConnection
CFileFind

CFtpFileFind
CGopherFileFind
CGopherLocator
CCmdTarget

CWinThread

CWinApp

COleControlModule
CDocTemplate

CMultiDocTemplate
CSingleDocTemplate
COleObjectFactory

COleTemplateServer
COleDataSource
COleDropSource

COleDropTarget
COleMessageFilter
CConnectionPoint
CDocument

COleDocument

COleLinkingDoc

COleServerDoc

CRichEditDoc CDocItem

COleClientltem

COleDocObjectltem
CRichEditcntrltem
COleServefltem

CDocObjectServerItem
CDocObjectServer
CWnd

CFrameWnd

CMDIChildWnd
CMDIFrameWnd
CMiniFrameWnd
COlelPFrameWnd
CSplitterWnd
CControlBar

CDialogBar
COleResizeBar
CReBar
CStatusBar
CToolBar
CPropertySheet

CPropertySheetEx


CDialog

CCommonDialog

CColorDialog

CFileDialog

CFindReplaceDialog

CFontDialog

COleDialog

COleBusyDialog

COleChangelcoriDialog

COleChangeSourceDialog

COleConvertDialog

COlelnsertDialog

COleLinksDialog

COleUpdateDialog
COlePasteSpecialDialog
COlePropertiesDialog
COlePageSetupDialog
CPrintDialog
COlePropertyPage
CPropertyPage

CPropertyPageEx CView

CCtrlView

CEditView
CListView
CRichEditView
CTreeView
CScrollView

CFormView

CDaoRecordView
CHtmlView
COleDBRecordView
CRecordView
CAnimateCtrl
CButton

CBitmapButton
CComboBox

CComboBoxEx
CDateTimeCtrl
CEdit

CHeaderCtrl
CHotKeyCtrl
CIPRddressCtrl
CListBox

CCheckListBox
CDragListBox

CListCtrl      
CMonthCalCtrl
COleControl
CProgressCtrl
CReBarCtrl

CRichEditCtrl

CScrollBar   

CSliderCtrl

CSpinButtonCtrl

CStatic

CStatusBarCtrl

CTabCtrl

CToolbarCtrl

CToolTipCtrl 

CTreeCtrl

В следующем списке перечислены классы, которые не порождены от CObject.

CHtmlStream

CHttpFilter      

CHttpFilterContext   

CHttpServer   

CHttpServerContexf

CArchive

CDumpContext

CRuntimeClass 

CPoint

CRect

CSize

CString

CTime

CTimeSpan  

CCreateContext

CMemoryState

COleSafeArray

CPrintlnfo

CCmdUI

COleCmdOI
CDaoFieldExchange
CDataExchange
CDBVariant
CFieldExchange
COleDataObject
COleDispatchDriver
CPropExchange
CRectTracker
CWaitCursor
CTypedPtrArray

CTypedPtrList   
CTypedPtrMap
CFontHolder
CPictureHolder

COleCurrency
COleDateTime
COleDateTimeSpan
COleVariant

CMultiLock
CSingleLock

Эти два списка могут служить вам справочником при дальнейшем изучении возможностей MFC.

Простейшее MFC-приложение

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

Рассматриваемая ниже программа SIMPLE.CPP просто открывает на экране свое окно, помещая а строку заголовка определенный текст.

Файл SIMPLE.CPP

Чтобы создать простейшее Windows-приложение на базе MFC, введите в окне компилятора VisualC++ следующий код: .

//
//   simple.cpp
//  Пример приложения, написанного с использованием MFC.
//

#include <afxwin.h>

Class CTheApp : public CWinApp

(

public:

virtual BOOL Initlnstance (); );

class CMainWnd : public CFrameWnd

(

public:

CMainWnd() (

Create(NULL, "Hello MFC World",

WSJDVERLAPPEDWINDOW, rectDefault, NULL, NULL), )

};

BOOL CTheApp: : Initlnstance 0

{

m_pMainWnd = new CMainWnd (); m_pMainWnd->ShowWindow (m_nCmdShow) ; m_pMainWnd->UpdateWindow() ;

return TRUE; }

CTheApp TheApp;

В следующих параграфах мы детально рассмотрим назначение каждого блока программы.

Файл AFXWIN.H

Файл AFXWIN.Hвыполняет роль шлюза в библиотеку MFC. Через него подключаются все остальные файлы заголовков, включая WINDOWS. H. Использование файла AFXWIN.H упрощает создание предварительно скомпилированных файлов заголовков. Предварительная компиляция частей программы позволяет сократить время, затрачиваемое на повторное построение приложения.

Создание класса, производного от CWinApp

Приложение начинается с определения класса CTheApp, являющегося производным от CWinApp:

class CTheApp : public CWinApp

{

public:

virtual BOOL Initlnstance () ; };

Виртуальный метод Initlnstance() наследуется от CWinApp. Переопределяя этот метод, программист получает возможность управлять инициализацией приложения. В классе CWinAppимеются также открытые виртуальные функции Exitlnstance(),Run()и другие, но в большинстве приложений нет необходимости переопределять их.

Ниже приведено описание класса CWinApp, взятое из файла AFXWIN.H.

///////////////
// CWinApp- базовый класс для всех приложений Windows

class CWinApp : public CWinThread {

DECLARE_DYNAMIC (CWinApp) public:

// Конструктор

CWinApp(LPCTSTR IpszAppName =• NULL);// имя приложения

// задается по умолчанию

// Атрибуты

// параметры запуска (не изменять)
HINSTANCE m_hlnstance;
HINSTANCE m_hPrev!nstance;
LPTSTR m_lpCmdLine;

int m_nCmdShow;

// параметры выполнения (могут меняться в Initlnstance)
LPCTSTR m_pszAppName;     // читаемое имя приложения
LPCTSTR m_pszRegistryKey; // используется для регистрации
CDocManager* m_pDocManager;

public:   // устанавливаются в конструкторе

LPCTSTRm_pszExeName;     // имя исполняемого файла (без пробелов)
LPCTSTRm_pszHelpFilePath; // определяется на основании пути к модулю
LPCTSTRm_pszProfileName;  // определяется на основании имени приложения

// Операции инициализации - должны выполняться в Initlnstanceprotected:

void LoadStdProfileSettings(UINT nMaxMRU = _AFX_MRU_COUNT);

void EnableShellOpen ();

void SetDialogBkColor(COLORREF clrCtlBk = RGB(192,192,192),

COLORREFclrCtlText= RGB(0, 0, 0)); // установка фонового цвета диалогового окна и окна сообщений

void SetRegistryKey(LPCTSTR IpszRegistryKey);

void SetRegistryKey(UINT nIDRegistryKey);

// позволяет хранить данные о приложении в реестре, а не в INI-файле
// (в качестве ключа обычно используется название компании)

BOOLEnable3dControls(); //для 3-мерных элементов управления

// используется файл CTL3D32.DLLtifndef _AFXDLL

BOOLEnableSdControlsStatic();  // для тех же целей статически компонуется

// файлCTL3D.LIB fendif

void RegisterShellFileTypes(BOOL bCompat=FALSE);

// вызывается после того, как будут зарегистрированы все шаблоны

// документа               .
void RegisterShellFileTypesCompat();

// требуется для совместимости с предыдущими версиями
void UnregisterShellFileTypes();

// Вспомогательные операции - обычно выполняются в Initlnstance
public:

// Указатели мыши

HCURSOR LoadCursor(LPCTSTR IpszResourceName) const;

HCURSOR LoadCursor(UINT nIDResource) const;

HCDRSOR LoadStandardCursor(LPCTSTR IpszCursorName) const;

HCURSOR LoadOEMCursor(UINT nIDCursor) const;

// Значки

HICON Loadlcon(LPCTSTR IpszResourceName) const;

HICON Loadlcon(UINT nIDResource) const;

HICON LoadStandardlcon(LPCTSTR IpszIconName) const;


HICON LoadOEMIcon(UINT nIDIcon) const;

// могут переопределяться

virtual BOOL Initlnstance();

virtualintExitlnstanceO; // возвращает код завершения приложения

virtual int Run();

virtual BOOL OnIdle(LONG ICount);

virtual LRESULT ProcessWndProcException(CException* e,const MSG* pMsg);
public:

virtual CWinApp();

protected:

// { {AFX_MSG (CWinApp)

afx_msg void OnAppExitO;

afx_msg void OnUpdateRecentFileMenu (CCmdUI* pCmdUI) ;

af x_msg BOOL OnOpenRecentFile (UINT nID) ;

//}}afx_msg declare_message_map ( ) };

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

Класс CFrameWnd

Окно приложения, выводимое на экран и управляемое классом CMainWnd, создается на базе класса CFrameWnd:

class CMainWnd : public CFrameWnd

{

public:

CMainWnd () (

Create (NULL, "Hello MFC World",

WS_OVERLAPPEDWINDOW, rectDefault, NULL, NULL);

}

};

Конструктор класса CMainWnd вызывает метод Create(), предназначенный для установления начальных параметров окна. В данном примере задается стиль окна и строка заголовка. В главе 7 будет показано, что с помощью этой же функции можно задать строку меню и таблицу горячих клавиш.

Ниже представлено описание класса CFrameWnd, взятое из файла AFXWIN.H.

////////////
// CFrameWnd- базовый класс для масштабируемых окон

class CFrameWnd : public CWnd     
{

DECLARE_DYNCREATE(CFrameWnd)

// Конструкторы

public:  

static AFX_DATA const CRect rectDefault;

CFrameWnd();

BOOL LoadAccelTable(LPCTSTR IpszResourceName); BOOL Create(LPCTSTR IpszClassName,

LPCTSTR IpszWindowName,

DWORD dwStyle = WS_OVERLAPPEDWINDOW,

const RECT& rect = rectDefault,

CWnd* pParentWnd = NULL, // != NULL для всплывающих окон

LPCTSTR IpszMenuName = NULL,

DWORD dwExStyle =0,

CCreateContext* pContext = NULL);

// динамическое создание - загружает рамку и связанные с окном ресурсы
virtualBOOLLoadFrame(HINTnIDResource,

DWORD dwDefaultStyle = WS_OVERLAPPEDWINDOW I

FWS_ADDTOTITLE,

CWnd* pParentWnd = NULL,

CCreateContext* pContext = NULL);

// специальная функция для создания области просмотра
CWnd*. CreateView(CCreateContext* pContext,

UINT nID = AFX IDW PANE FIRST);

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

void EnableDocking(DWORD dwDockStyle);

void DockControlBar(CControlBar* pBar, UINT nDockBarlD = 0,

LPCRECT IpRect = NULL);

void FloatcontrolBar(CControlBar* pBar, CPoint point, DWORD dwStyle = CBRS_ALIGN_TOP); CControlBar* GetControlBar(UINT nID);

// Реализация
public:

virtual -CFrameWnd (); 

intm_nWindow;      // номер окна — отображается как ":n"

HMENUm_hMenuDefault;      // ресурс меню

HACCELm_hAccelTable;      // таблица горячих клавиш

DWORD m_dwPromptContext;

BOOLm_bHelpMode;// если TRUE, активен режим вызова справки

// по[Shift+Fl]

CFrameWnd* m_pNextFrameWnd; // следующее окно в списке приложения
CRect m_rectBorder;
COleFrameHook* m_pNotifyHook;

CPtrList m_listControlBars; // массив панелей инструментов,

// связанных с данным окном intm_nShowDelay;

// обработчики оконных сообщений

afx_msg int OnCreate(LPCREATESTRUCT IpCreateStruct);

afx_msg void.OnDestroy();

afx_msg void OnClose();

afx_msg void OnlnitMenu(CMenu*);

afx_msg void OnlnitMenuPopup(CMenu*, UINT, BOOL);

afx_msg void OnMenuSelect(OINT nltemlD, UINT nFlags,

HMENU hSysMenu);

afx_msg LRESULT OnPopMessageString(WPARAM wParam, LPARAM IParam) ;
afx_msg LRESULT OnSetMessageString(WPARAM wParam, LPARAM IParam),

protected:

afx_msg LRESULT OnDDEInitiate(WPARAM wParam, LPARAM IParam);
afx_msg LRESULT OnDDEExecute(WPARAM wParam, LPARAM IParam);
afx_msg LRESULT OnDDETerminate(WPARAM wParam, LPARAM IParam);
afx_msg LRESULT OnRegisteredMouseWheel(WPARAM wParam, LPARAM IParam);
DECLARE_MESSAGE_MAP ()

friend class CWinApp; );

Первый параметр метода Create() позволяет задать имя класса окна в соответствии с синтаксисом стандартной API-функции RegisterClass(). Обычно этот параметр не используется и равен null.

Реализация метода InitInstance( )

В классе CTheAppпереопределяется метод initlnstance()   базового класса

CWinApp:

BOOL CTheApp::Initlnstance()

{

m_pMainWnd=new CMainWndO;

m_j>MainWnd->ShowWindow (m_nCmdShow) ;

m_pMainWnd->UpdateWindow();

returnTRUE; }

Оператор new вызывает конструктор CMainWnd (), рассмотренный в предыдущем параграфе. Переменная-член m_pMainWnd(префикс га_ указывает на переменную-члена класса) определяет положение окна на экране. Функция ShowWindow() выводит окно на экран. Параметр m_nCmdShow инициализируется конструктором класса CWinApp и определяет параметры отображения окна. Функция Updatewindow() перерисовывает содержимое окна.

Конструктор

В последнем блоке программы вызывается конструктор класса CWinApp:

CTheAppTheApp;

Запуск программы

Рассмотренная нами программа очень проста. Приложение просто отображает свое окно, ничего не выводя в нем (рис. 18.1).


Рис. 18.1. Окно простейшей MFC-программы

Многократное использование одних и тех же базовых классов в различных приложениях — это основа проектирования программ на C++. Библиотеку MFC можно рассматривать как естественное и органичное расширение данного языка. Созданный нами программный код является основой для всех MFC-приложений, разрабатываемых в этой книге. В следующей главе мы создадим шаблон приложения, в котором осуществляется вывод информации в рабочую область окна,