Из предыдущих глав вы узнали, что создание даже самых простых приложений Windowsс использованием стандартных API-функций является довольно сложной задачей, требующей много времени и внимания. Например, код простейшего приложения SWP.C(мы рассматривали его в предыдущей главе) на языке С занял почти две страницы текста. Большая часть кода нужна лишь для того, чтобы вывести на экран окно приложения и поддерживать его работу.
Компилятор MicrosoftVisualC++ предлагает мощную библиотеку классов (MicrosoftFoundationClasses— 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.
Поддержка COM (ComponentObjectModel— модель компонентных объектов).
Использование тех же соглашений об именовании методов классов, которые применялись при подборе имен для API-функций Windows. Это существенно облегчает идентификацию действий, выполняемых классами.
Опытный программист сразу же оценит две наиболее важные особенности MFC: знакомые имена методов и привязка сообщений к обработчикам. Если вы вернетесь к программе PIE.С, рассмотренной в предыдущей главе, то убедитесь в обилии блоков switch/case. Увлечение такими конструкциями чревато возникновением трудно обнаруживаемых ошибок при выполнении программы. Эту проблему легко обойти с помощью MFC.
Профессиональный разработчик будет удовлетворен быстротой выполнения про-фаммного кода библиотечных классов. Интенсивное использование MFCтеперь не щшведет к тяжеловесности полученного приложения.
Большинство библиотек классов, в том числе и 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
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
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
Эти два списка могут служить вам справочником при дальнейшем изучении возможностей 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 ()
Первый параметр метода 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-приложений, разрабатываемых в этой книге. В следующей главе мы создадим шаблон приложения, в котором осуществляется вывод информации в рабочую область окна,