Глава 3.
Основные составляющие приложения на базе библиотеки классов MFC
До первого поворота всякая дорога прямая.
Владислав Гжещик
Цель этой главы — проанализировать общую структуру программы для Windows, написанной с использованием тех возможностей, которые предоставляет библиотека MFC, и показать тесную взаимосвязь между классами библиотеки и интерфейсом прикладного программирования Win32 API.
Начнем с рассмотрения минимального приложения First, создающего простое окно с заголовком, бордюром и системным меню.
/////////////////////////////////////////////////////////
// Файл First.h
// Copyright (c) 2000 Тихомиров Ю.
#ifndef _AFXWIN_H_
#error сначала включаем 'stdafx.h' для поддержки РСН
#endif
class CFirstApp : public CWinApp
{
public:
CFirstApp(};
// Переопределяемые функции
// {{ AFX_VIRTUAL (CFirstApp)
public-virtual BOOL Initlnstancei);
//}}AFX_VIRTUAL
};
///////////////////////////////////////////////////////
// Файл First.срр
// Copyright (с) 2000 Тихомиров Ю.
#include "stdafx.h"
#include "First.h"
#include "MainFrm.h"
// Может быть создан один и только один объект CFirstApp
CFirstApp theApp;
CFirstApp::CFirstApp()
{
}
// Инициализация объекта CFirstApp
BOOL CFirstApp::Iriitlnstance()
{
const char *szWndClass = AfxRegisterWndClass(
CS_HREDRAW | CS_HREDRAW,
LoadStandardCursor(IDC_ARROW),
(HBRUSH)COLOR_BACKGROUND + 1,
LoadIcon(IDR_MAINFRAKE));
CMainFrame *pFrame = new CMainFrame;
m_pMainWnd = pFrame; //
pFrame->Create( szWndClass, //
"Первая программа", //
WS_OVERLAPPEDWINDOW, //
CRect(0, 0, 560, 400), //
NULL, //
NULL, //
0, //
NULL //
);
m_pMainWnd->CenterWindow();
m_pMainWnd->ShowVJindow (SW_SHOW) ;
m_pMainWnd->UpdateWindow();
return TRUE;
}
/////////////////////////////////////////////////////////
// Файл MainFrm.h
// Copyright (c) 2000 Тихомиров Ю.
class CMainFrame : public CWnd f public:
CMainFrame();
public:
virtual ~ CMainFrar-ie ( ) ;
////////////////////////////////////////////////////////
// Файл MainFrm.cpp
.// Copyright (ci 2000 Тихомиров 1C.
#include "stdafx.h"
#include "First.h"
#include "MainFrm.h"
// Конструктор/деструктор .класса CMainFrame
CMainFrame::CMainFrame()
{
}
CMainFrame : : ~CMai nFrame ()
{
{
//////////////////////////////////////////////////////
// Файл Stdafx.h
// Copyright (c) 2000 Тихомиров Ю.
// stdafx.h : заголовочный файл, подключающий стандартные,
// наиболее часто используемые заголовочные файлы
#define VC_EXTRALEAN // Исключает редко используемые части
// заголовочных файлов Windows
#include <afxwin.h> // Ядро MFC и стандартные компоненты
/////////////////////////////////////////////////////////////////
// Файл Stdafx.cpp
// Copyright (с) 2000 Тихомиров Ю.
// Stdafx.cpp : исходный файл, который подключает только
стандартные включаемые файлы
// First.pch : файл прекомпилированных заголовков
// stdaix.obj : файл, содержащий перекомпилированные типы информации
# include "stdafx.h"
//////////////////////////////////////////////////////////
// Файл First.re
// Copyright (с? 2000 Тихомиров Ю.
....
////////////////////////////////////////////////// //////
// Пиктограмма
IDP_MAINFRAME ICON DISCARDABLE "res\\First.ico"
Рис. 3.1. Главное окно программы First
Программа First строит обычное, традиционное окно, хорошо знакомое всем пользователям системы Windows (рис. 3.1). Окно можно перемещать по экрану, закрывать, свертывать (уменьшать до размера пиктограммы) или раскрывать (увеличивать до размеров экрана), а также изменять его размер. Основное, можно даже сказать единственное, назначение этого примера состоит в том, что он позволяет нам познакомиться с принципами построения программ на базе библиотеки классов MFC.
Давайте пройдемся по приведенному тексту, чтобы полностью разобраться в том, какой минимум действий необходимо выполнить для создания некоего каркаса, который в будущем можно будет наполнять конкретным содержанием.
Первое, на что следует обратить внимание — это наличие объекта CFirstApp, являющегося объектом-приложением Windows. Основной задачей класса, из которого он образован — CWinApp, является создание процесса в системе. Если вспомнить, что процесс — каждый выполняемый экземпляр приложения (в более ранних версиях Windows применялся термин "задача"), то становится понятным, что в приложении может быть определен только один объект класса CWinApp. Основными задачами, возложенными на этот класс, являются создание и инициализация главного окна (напоминаю, что Windows — "оконная" система) и опрос системных сообщений. Итак, еще раз: каждое приложение, которое использует библиотеку MFC, может содержать только один объект "приложение", который конструируется вместе с другими глобальными объектами C++ (определение — CFirstApp theApp) и уже доступен, когда Windows вызывает функцию WinMain. На данном этапе знакомства это самый существенный момент для успешного программирования под Windows на базе MFC, и если вы его усвоили, то можно переходить непосредственно к функции WinMain. которая является "точкой входа" в любую написанную под Windows программу (точно так же, как функция main является "точкой входа" в программу, написанную на языке С).
Так где же эта обязательная функция? Пусть вас не удивляет, что в представленном листинге вы не найдете даже упоминания о ней. Дело в том, что создатели библиотеки MFC проделали большую работу и избавили вас от написания многих строк вспомогательного кода, предоставив возможность заниматься только решением конкретной задачи. Однако это имеет и оборотную сторону — библиотека скрывает от программиста и часть того, что заставляет Windows работать. По этой причине мы будем углубляться в Windows немного больше, чем может показаться необходимым. Мы уверены, что приобретенное понимание позволит вам с большей легкостью использовать возможности библиотек MFC и SDK. Сказанное в полной мере относится к функции WinMain. Что же предлагают нам разработчики MFC в плане ее реализации?
Примечание
На самом деле функция WinMain операционной системой не вызывается. Вместо этого происходит обращение к стартовой функции из стандартной (runtime) библиотеки C/C++. Компоновщик Visual C++ знает, что она имеет имя _WinMainCRTStartup и отвечает за выполнение таких операций, как:
Давайте вместе подробно рассмотрим фрагмент исходного текста из библиотеки.
int AFXAPI WinMain(
HINSTANCE hlnstance,
HINSTANCE hPrevInstance,
LPTSTR IpCmdLine,
int nCindShow) {
int nReturnCode = -1;
CWinApp* pApp = AfxGetApp();
// Внутренняя инициализация
if (!AfxWinInit(hlnstance, nFrevInstance,
IpCmdLine, nCmdShow)) goto InitFailure;
// Глобальная инициализация приложения
if (!pApp-XInitApplication())
goto InitFailure;
// Специфическая инициализация экземпляра приложения
if (!pApp->Init!nstance())
{
// В случае неудачи необходимо корректно завершить приложение ...
if (pApp->m_pMainW:id != NULL)
{
// удалив главное окно, если оно все же было создано
TRACED ("Warning: Destroying non-NULL m_MainWnd\n") ; pApp->m_pMainWnd->DestroyWindow();
}
nReturnCode = pApp->Exit!nstancei};
goto InitFailure;
}
// Если все в порядке - запускаем цикл обработки сообщений
nReturnCode = pApp->Run();
InitFailure:
AfxWinTerm() ;
return nReturnCode;
}
Для лучшего понимания задач, решаемых WinMain, рассмотрим структуру этой функции, упрошенное условное изображение которой представлено на рис. 3.2.
Рис. 3.2. Упрощенная структура функции WinMain
Все представленные здесь функции определены в классе CWlnApp — базовом для объекта "приложение", и должны быть переопределены в нашем производном от него классе. Место класса CWinApp в иерархии объектов библиотеки MFC показано на рис. 3.3.
Поскольку без использования этого класса нельзя создать ни одно приложение на базе библиотеки MFC, то рассмотрим те основные его функции, которые необходимо переопределить при создании конкретного приложения. Прежде всего это, конечно же. конструктор:
CWinApp::CWinApp (LPCTSTR IpszAppKame = NULL)
Параметр IpszAppName— указатель на текстовую строку, которая содержит имя приложения, используемое системой Windows. Если этот аргумент отсутствует или равен NULL (по умолчанию), то для формирования имени приложения конструктор использует строку из файла ресурсов с идентификатором AFX_IDS_ APP_TITLE (в ней вы можете указать имя главного окна вашего приложения), а при ее отсутствии — имя исполняемого файла. Этот параметр конструктор запоминает в компоненте класса m_pszAppName.
Рис. 3.3. Место класса CWinApp в иерархии
Следующую важную группу составляют переопределяемые (виртуальные) функции. В документации на MFC обращается внимание на следующие функции этой группы: Initlnstance, Run, Exit Instance и Onldle.
virtual BOOL CWinApp::Initlnstance()
Отвечает за инициализацию каждого нового экземпляра приложения.
Если посмотреть поставляемые исходные тексты, то можно увидеть, что в своем первозданном виде эта функция не делает ничего:
BOOL CWi лАрр: :Initlnstance()
{
return TRUE;
}
и наполнение ее содержанием целиком является задачей программиста. Обычно это то место, где конструируется объект "главное окно" приложения, загружаются стандартные настройки из INI-файла или из реестра Windows, обрабатывается командная строка текущего экземпляра приложения, создается новый или открывается существующий документ, регистрируются шаблоны документов, создаются сами документы, их представления (view) и ассоциированные с ними окна (этот процесс будет подробно рассмотрен в одной из следующих глав). После создания главного окна переменная C\VinThread::m_pMainWnd устанавливается равной значению указателя на это окно. Это единственная функция CWinApp, которую вы должны обязательно переопределить для того, чтобы ваше приложение что-нибудь делало. Если вы вернетесь к тексту нашего приложения, то увидите, что именно это мы и сделали.
Следующие две функции не требуют обязательного переопределения, но представление о них необходимо иметь.
virtual BOOL CWinApp::ExitInstance ()
Вызывается только из функции Run для завершения работы текущего экземпляра приложения. Реализованная версия этой функции записывает сохраняемые настройки в INI-файл приложения. Если есть необходимость в проведении специальной обработки при завершении приложения — такой, как возврат памяти, распределенной во время работы, очистка контекста устройства или других ресурсов, то следует переопределить эту функцию. Освобождение стандартных элементов, например, документов и их представлений (view), производится стандартными средствами библиотеки классов MFC и не должно беспокоить разработчиков программного обеспечения.
virtual int CWinApp::Run ()
Запускает цикл обработки сообщений, в котором получаются и распределяются . сообщения Windows до тех пор, пока не поступит сообщение WM_QUIT, инициирующее завершение работы приложения. Все поступающие сообщения направляются в функцию PreTranslateMessage и затем в стандартную функцию Windows Translate/Message для поддержки работы с клавиатурой. В заключение вызывается функция Windows DispatchMessage. Если очередь сообщений пуста, то Run вызывает виртуальную функцию Onldle для выполнения фоновой обработки. Эта функция не требует переопределения, хотя и допускает его.
Теперь, после некоторого знакомства с классом CWinApp, можно вернуться к нашей первой программе.
Минимальная программа для Windows
Напомним еще раз основные этапы создания и работы нашей первой программы.
1. Создается единственный глобальный объект класса приложения CFirstApp — theApp. Программа First использует класс CWinApp как базовый для собственного класса CFirstApp, который имеет две функции — конструктор и функцию, необходимую для создания главного окна приложения и переопределяющую аналогичную функцию родительского класса:
class CFirstApp : public CWinApp
{
public:
CFirstApp () ;
// Переопределяемые
// ClassWizard описывает здесь виртуальные функции
//{{AFX_VIRTUAL(CFirstApp)
public:
virtual BOOL Initlnstance();
//}}AFX_VIRTUAL
} ;
2. После создания объекта theApp операционная система вызывает функцию WinMain — точку входа в программу, написанную под Windows. Реализация этой функции в библиотеке MFC выполняет следующие действия:
• определяет указатель на объект "приложение", вызывая для этого глобальную функцию AfxGetApp
CWinApp* pApp = AfxGetApp();
• вызывает функцию, отвечающую за инициализацию первого экземпляра приложения
pApp->InitApplication();
• вызывает функцию, отвечающую за мниииатизацию текущего экземпляра приложения
pApp->Init!nstance ();
• запускает цикл обработки сообщений
pApp->Run();
• завершает функцию Win Main, а значит, и работу приложения.
При всей своей лаконичности функция Win Main имеет все составляющие, необходимые для выполнения программы под Windows. Из этих составляющих наибольший интерес представляют инициализация текущего экземпляра и цикл обработки сообщений, поэтому рассмотрим их подробнее. Любая программа, написанная на базе библиотеки классов MFC, переопределяет функцию Initlnstance. В примере First это выглядит так:
BOOL CFirstApp::Initlnstance()
{
const char *szWndClass -- AfxRegisterWndClass(
CS_HREDRAW I CS_HREDRAW,
LoadStandardCursor(IDC_ARROW),
(HBRUSH)COLOR_BACKGROUND + 1,
Loadlcon(IDR_MAINFRAME);;
CMainWnd *pWnd = new CMainWnd;
pWnd->CreateEx(
0, // дополнительные стили
szWndClass, // класс окна
"Первая программа", // заголовок
WS_OVERLAPPEDWINDOW, // стиль
CW_USEDEFAULT, CW_USEDEFAULT, // левый верхний угол (х, у)
CW_USEDEFAULT, CW_USEDEFAULT, // размерь; {сх, су)
NULL, // родительское окно
NULL) ; // идентификатор меню
pWnd->ShowWindow(m_nCmdShow);
m_pMainWnd = pWnd;
return TRUE;
}
Основное назначение этой функции, как вы помните, заключается в проведении некоторых инициализирующих действий (в нашей функции отсутствуют) и создание главного окна приложения, для чего необходимо прежде всего зарегистрировать класс нашего окна (подробно к вопросу регистрации мы вернемся в следующем разделе):
const char *szWndClass = AfxRegisterWndClass(
C3_HREDRAW i CS_ HREDRAW,
LoadStandardCursor( IDC_ARROW),
(HBRUSH)COLOR BACKGROUND +1 ,
LoadIcon (IDR_ MAINFPAME)) ;
а затем создать экземпляр одного из оконных классов MFC:
CMainWnd *pWnd = new CMatnWnd;
и создать собственно окно:
pWnd->НекотораяФункцкяСозданияОкна(...);
После того как объект окна MFC и само окно созданы, указатель на этот объект присваивается переменной CWinThread::m pMainWnd (такое присвоение необходимо для того, чтобы иметь возможность корректно завершить приложение, когда будет закрыто его главное окно):
m_pMainWnd = pWnd;
Можно создавать окно Windows и выводить его на экран
m_pMainWnd->ShowWindow(m_nCmdShow);
По завершении инициализации основная работа объекта "приложение" состоит в том, чтобы обеспечить передачу сообщений между пользователем и оконным объектом.
К этим вопросам мы еще вернемся, а пока нам надо рассмотреть процесс создания собственно окна.
Каждая программа на базе MFC, которую вы создаете, будет содержать по крайней мере один класс, определяющий оконный объект. Приложение First определяет свой класс CMainFrame, базирующийся на одном ш предопределенных классов, а именно на CFrameWnd, и полностью использует тот прочный фундамент, который заложила библиотека MFC. не добавляя ничего своего:
class CMainFrame : publiс CFrameWnd
{
public:
CMainFrame 0 ;
virtual ~CMainFrame();
};
В действительности все еще проще: этот класс можно было вообще не определять, поскольку обе его функции пустые. Я включил его только для наглядности. Всю работу выполняет базовый класс нашего окна CFnimeWnd. Еще раз повторим — окно создается в два-три шага: сначала определяется оконный класс (этот шаг в большинстве случаев необязателен), затем создается объект "окно", и, наконец, окно делается видимым.
Теперь о создании собственно окна Windows. Мы использовали для этой цели функцию
CWnd::CreateEx(
0,
szWndClass, // класс окна
"Первая программа", // Заголовок
WS_OVERLAPPEDWINDOW, // стиль
CW_USEDEFAULT, CW_USEDEFAULT, // левый верхний угол (x,у)
CW_USEDEFAULT, CW_USEDEFAULT, // paзмеры (сх, су)
NULL, // родительское окно.
NULL); // идентификатор меню
В дополнение к CreateEx класс CWnd имеет вторую функцию для создания окон — Create. Эти функции практически идентичны и имеют два отличия: во-первых, функция Create позволяет создавать лишь дочерние окна, в то время как CreateEx— также перекрывающиеся и вспомогательные, и, во-вторых, CreateEx содержит один дополнительный параметр (Ex. означает Extended, т. е. расширенный). Поскольку нам надо было создать главное окно программы, которое не может быть дочерним, мы воспользовались функцией CreateEx.
После возврата из CreateEx система Windows создает у себя всю внутреннюю информацию, необходимую для сопровождения окна. т. е. вносит запись в базу, содержащую информацию об окнах Но в большинстве случаев окно на экране еще не появляется — требуется вызвать функцию Show Window, которая также принадлежит классу CWnd:
m_pMainWnd->ShowWindow (m_nCnidShow)
В зависимости от значения mjiCmdShow программа может при вызове иметь свернутое, обычное или. развернутое окно. Вызовом Show-Window завершается инициализация оконного объекта.
Еще раз напомню, что такая последовательность действий при инициализации типична для создания всех приложений Windows, и будем двигаться дальше.