Глава 6.
Каждый должен заниматься своим делом...
Никогда не удается делать что-то одно.
Закон Харбина
Теперь, когда мы познакомились с обшей структурой приложений, написанных с применением библиотеки классов MFC, и рассмотрели типы окон, используемых в Windows, попытаемся разделить задачи, за которые должен отвечать некоторый отдельный объект. Что я имею в виду? На данном этапе знакомства с программированием под Windows мы можем выделить три задачи, присутствующие в любом приложении:
1. Взаимодействие с операционной системой.
2. Организация взаимодействия с пользователем.
3. Представление информации пользователю.
Для решения первой задачи в библиотеке реализован класс CWinApp, который настолько успешно справляется с возложенной на него задачей, что для его использования не требуется никаких дополнительных усилий. Взаимодействие с пользователем — это основная задача подавляющего большинства приложений. Более того, сама система Windows задумывалась и реализовывалась именно для упрощения этого процесса. В библиотеке за эту задачу отвечают классы фреймов, базирующиеся на CFrameWnd. В принципе, даже если ограничиться только этими двумя составляющими, можно писать достаточно сложные и разнообразные приложения. Однако по мере развития такого приложения разбираться в нем будет все труднее и труднее. Представьте себе, например, летчика пассажирского самолета, который помимо выполнения своих основных функций будет еще отвечать за обслуживание пассажиров. Думаю, далеко не каждый захочет лететь на таком самолете.
Именно по этой причине я и разделил задачи взаимодействия с пользователем и представления ему информации. Необходимо создать еще один "оконный" класс, подчинив его основному фрейму. Этот класс будет отвечать за некоторое дочернее окно, в которое мы в дальнейшем будем направлять весь вывод необходимой пользователю информации. Итак, чтобы попутно научиться ценить свое время, откроем проект MyWizarcD, имеющийся на прилагаемой дискете, и скомпилируем его. Обратите внимание на сообщение компилятора, которое он выводит в окне Output после построения приложения
Copying custom AppWizard to Template directory...
1 file(s) copied
// Копирование настраиваемого мастера в каталог шаблонов ..,
// скопирован 1 файл
Как вы уже догадались — только что мы создали свой новый Wizard, на основе которого и будем создавать наше следующее приложение. Теперь вам не придется вручную набивать минимально необходимые тексты или "выкидывать" лишнее из предлагаемых Visual C++ проектов.
Примечание
Если вы пользуетесь версией Visual C++ 6.0, то там предусмотрено создание проекта, аналогичного нашему. Однако оно, с одной стороны, более развернутое (в частности, есть меню и блок диалога), а с другой — несколько ограниченнее: чтобы заменить курсор и/или пиктограмму необходимо добавить свой код. Так что выбирайте сами.
Теперь вам осталось только выбрать MyWizard3 в качестве мастера для создания каркас и указать имя приложения. Вот как это выглядит у меня (рис. 6.1).
Два раза нажмите кнопку ОК, и каркас проекта готов (рис. 6.2). По сравнению с нашей первой программой здесь появился класс CChildWnd. Именно он и будет отвечать за поддержку решения третьей задачи, о которой мы говорили в начале главы. Изменения в классах CClockApp (отвечает за взаимодействие с системой) и CMainFrame (как обычно, должен отвечать за взаимодействие с пользователем) минимальные, поэтому сразу переходим к новому для нас классу — CChildWnd.
Уже по названию можно догадаться, что объект этого класса представляет в приложении дочернее окно. Вот его определение, сгенерированное AppWizard:
class CChildWnd : public CWnd
{
// Construction
public:
CChildWnd();
Attributes
public:
// Operations
public:
Overrides
/./ CiassWizard generated virtual function overrides
//{{AFX_VIRTUAL{CChiIdWnd)
protected:
virtual BOOL PreCreateWindow(CREATESTRrJCT& cs);
//})AFX_VIRTUAL
/ Implementation
public:
virtual ~CChildWnd() ;
// Generated message map functions
protected:
//{{AFX_MSG(CChildWnd)
// NOTE - the CiassWizard will aac and remove member functions here.
//}}AFX_MSG DECLARE_MESSAGE_MAP()
};
Как видите, мастер упростил использование этого класса, вставив в него необходимые макросы. Но больше ничего необычного здесь нет — конст-
Рис. 6.1. Создаем проект Clock, воспользовавшись вновь созданным мастером MyWizardS
Рис. 6.2. Теперь можно наполнять уже работающее приложение "своим" содержимым
руктор, деструктор и переопределенная виртуальная функция PreCreateWindow. Обратите также внимание, что в качестве базового мы используем класс CWnd, практически идеально подготовленный для создания дочерних окон.
Пока ничего нового, по сравнению с предыдущими примерами, мы не видим. Теперь вопрос: "Когда будет вызвана эта переопределенная версия функции?" Правильно, после того, как мы создадим объект класса и вызовем одну из имеющихся функций создания окна Windows. В главе 4 мы уже обсуждали эти вопросы, и поэтому приведу только соответствующие фрагменты кода.
class CMainFrame : public CFrameWnd
{
...
protected; // этот фрейм владеет дочерним окном
CChildWnd m_wndChild;
};
int CMainFrame::OnCreate(LPCREATESTRUCT IpCreateStruct)
{
if (CFrameWnd::OnCreate(IpCreateStruct) = = -1)
return -1;
// Создаем дочернее окно Windows
// в качестве клиентской области фрейма
if (!m_wndChiId.Create(NULL,
NULL,
AFX_WS_DEFAULT_VIEW,
CRect(0, 0, 0, 0),
this,
AFX_I DW_PANE_FIRST,
NULL))
{
TRACED("Ошибка при создании дочернего окна \n");
return -1;
}
return 0;
}
Что здесь для нас интересного? Конечно же, это параметры функции CWnd::Create, которые мы использовали:
virtual BOOL CWnd::Create(
LPCTSTR IpszClassName,
LPCTSTR IpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID,
CCreateContext* pContext = NULL)
Параметры функции нам уже практически знакомы, поэтому только перечислю их: IpszClassName— имя класса окна Windows; IpszClassName— заголовок окна; dwStyle— стиль окна; rect— координаты и положение окна на экране; pParentWnd — указатель на родительское окно; nID— идентификатор окна; pContext— некоторый контекст, связанный с окном.
А вот как это выглядит у нас:
m_wndChiId.Create(
NULL,
NULL,
// Нам пока не нужен специальный класс
// Согласитесь, что окно, представляющее клиентскую область,
// будет странно выглядеть с заголовком
AFX_WS_DEFAULT_VIEW,
// Используем специальную константу для создания клиентской области окна
CRect(0, 0, 0, 0),
// Размерами займемся позже this,
// Родителем является фрейм, в котором окно создается
AFX_IDW_PANE_FIRST,
// Еще одна предопределенная константа для идентификации того, что это окно первое
NULL)
// He связываем окно ни с каким контекстом
Другими словами, при создании клиентской области окна нам не надо практически ни о чем задумываться — главное не ошибиться в числе параметров. Основную работу сделает сам класс CChildWnd, Посмотрите, как выглядит реализация этого класса:
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CChildWnd
// Пустые конструктор и ...
CChildWnd::CChildWnd ()
{
}
// деструктор
CChildWnd::~CChildWnd()
{
}
// Операторные скобки, куда ClassWizard будет вставлять макросы BEGIN_MESSAGEJMAP(CChiidWnd, CWnd)
//{{AFX_MSG_MAP(CChildWnd)
// NOTE — the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CChildWnd message handlers - обработчики сообщений класса
// Единственная виртуальная функция, которую мы переопределили
BOOL CChildWnd::PreCreateWindow(CREATESTRUCT& cs}
{
// Что творится в недрах библиотеки, зачастую неважно, однако
// надо предоставить ей возможность выполнить свою часть работы
if ( ! CWnd: : PreCreateWindow (cs,) )
return FALSE;
// Теперь можно изменить стиль, ...
cs.dwExStyle [= WS_EX_CLIENTEDGE;
cs.style &= ~WS_B0RDER;
//а также класс окна,
// что позволит определить свои цвет фона, курсор и пиктограмму
cs.lpszClass = AfxRegisterWndClass(
CS HREDRAW [ CS_HREDRAW [ CS_DBLCLKS, AfxGetApp()->LoadCursor(IDR_MAINFRAME), (HBRUSH)(COLOR_WINDOW +1), AfxGetApp()->LoadIcon(IDR_MAINFRAMK));
return TRCJE;
}
Примечание
Пиктограмма и курсор для вас уже созданы (посмотрите в ресурсах), но вы с легкостью можете изменить их внешний вид.
Может возникнуть вопрос: "Почему мы стали проделывать все эти изменения здесь, а не установили все необходимые стили при создании дочернего окна?" Однозначный ответ дать невозможно, но мне представляется, что каждый должен заниматься своим делом": фрейм создает себе клиентскую область, а она уже "сама решает, как ей выглядеть".
Итак, мы получили некоторый каркас, в котором три объекта отвечают каждый за свою часть работы. Единственное, что я бы изменил в этом каркасе — это заголовок основного окна приложения, чтобы он более соответствовал назначению программы. Посмотрите на рис. 6.3 и внесите необходимые изменения в код, чтобы заголовок выглядел аналогичным образом название выбрано именно таким, поскольку на базе этого каркаса мы будем с вами создавать аналоговые часы).
Рис. 6.3. На основе этого каркаса мы будем создавать аналоговые часы