MFC под колпаком или создание MFC приложения без App Wizard

Чаще всего создание нового MFC приложения поручается визарду (MFC App Wizard). Визард генерирует основной скелет приложения, который, мы в последствии заполняем нужным кодом, в конечном счёте получая готовое приложение.Получая такую заготовку для нашей будущей программы, сразу бросается в глаза большое количество кода, который отчасти кажется нам немного хитрым, а отчасти и непонятным, да плюс ко всему постоянно присутствует ощущение, что приличное количество кода скрыто от наших глаз. Предлагаю пролить немного света на таинственные участки кода, который для нас генерирует заботливый визард. А заодно и посмотрим как самостоятельно, без помощи визарда, создавать MFC приложения.

Итак, немного о скрытом коде

Каждое 32 битное Windows приложение имеет два основных элемента: функции WinMain и WndProc. Ваша программа обязательно должна как минимум одну WinMain, плюс WndProc для каждого окна. Хотя MFC и создаёт за их за Вас, но всё-таки иметь представление о них желательно.

Основная задача WinMain - это запуск приложения. Как только приложене запущено, Windows сразу же помещает сообщения Вашего приложения в очередь сообщений. Чтобы получить эти сообщения от операционной системы и обработать их, WinMain делает три API вызова. Первый - это GetMessage, задача которого получить сообщение. Затем TranslateMessage для подготовки необходимых преобразований сообщения. И в звключение WinMain вызывает DispatchMessage, который говорит операционной системе какому WndProc отправить сообщение для обработки.

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

Простейшее MFC приложение: Менее 20-ти строк кода

Далее мы рассмотрим простейшую структуру MFC приложения. Так как наша задача обеспечить присутствие в приложении WinMain и WndProc, то необходимо унаследовать для этих функций MFC классы.

Класс приложения мы будем наследовать от MFC класса CWinApp. CWinApp все необходимые функции и переменные для инициализации, запуска и закрытия приложения. CWinApp так же содержит указатель m_pMainWnd, который указывает на объект класса основного окна. Каждое MFC проложение имеет один и только один объект, наследованный непосредственно от CWinApp. В примере, приведённом ниже, он имеет название "CMyApp."

Класс, обслуживающий окно, будем наследовать от CFrameWnd. CFrameWnd так же имеет все необходимые функции и переменные, необходимые для создания и управления окнами. Обратите внимание, что фактически создание объекта окна не влечёт за собой непосредственное создание самого окна, а только содержит в себе функцию Create(), необходимую для создания окна.

Давайте посмотрим, что происходит при запуске нашей программы. Эти этапы можно отследить в приведённом ниже примере:

  1. WinMain начинает работать с точки: CMyApp app; Создаётся объект типа CMyApp с именем "app." App содержит все переменные-члены и функции CWinApp, которые необходимы для запуска и завершения нашего приложения.
  2. Затем WinMain вызывает функцию InitInstance( ) нашего app. InitInstance() создаёт новый объект CMyWnd с m_pMainWnd = new CMyWnd;
  3. Конструктор CMyWnd вызывает функцию Create( ), которая создаёт экземпляр окна, но не отображает его.
  4. Далее функция InitInstance() отображает окно командой m_pMainWnd-> ShowWindow(m_nCmdShow);
  5. WinMain вызывает функцию Run( ), которая посылает сообщения остальной части приложения.

А вот собственно сам пример:

 #include <afxwin.h>

//наследуем наш класс окна от CFrameWnd
class CMyWin: public CFrameWnd
{
public:
CMyWin( );
DECLARE_MESSAGE_MAP( )
};

//объявляем конструктор для нашего класса окна:
CMyWin::CMyWin( )
{
Create(0, "This Text Will Appear in the Title Bar");
}

//наследуем класс приложения от CWinApp
class CMyApp: public CWinApp
{
public:
virtual BOOL InitInstance( );
};

//определяем функцию InitInstance( ) длдя класса приложения
BOOL CMyApp::InitInstance( )
{
m_pMainWnd = new CMyWin( );
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}

//далее следует карта сообщений нашего приложения
BEGIN_MESSAGE_MAP(CMyWin, CFrameWnd)
// здесь располагается список сообщений, которые
// будут обработаны классом CMyWin.

END_MESSAGE_MAP( )

//создаём объект нашего приложения
CMyApp app;
Фактически окно будет больше.

Интерфейс однодокументного (Single Document) приложения

Код, который генерируется визардом при создании однодокументного приложения (SDI), более сложен на первый взгляд, но работает всё по тем же принципам. Мы так же наследуем класс от CWinApp. И наследуем всё тот же класс управления окном от CFrameWnd. Однако, в данном случае, класс управления окном будет называться CMainFrame.

Наш класс приложения так же имеет функцию InitInstance(), но выглядит она уже немного сложнее. Кроме всего прочего она содержит следующие строки:

 CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMainFrame), // основной SDI фрейм окна
RUNTIME_CLASS(CMyView));
AddDocTemplate(pDocTemplate);

Здесь объект приложения создаёт указатель на однодокументный шаблон и связывает его с новым объектом однодокументного шаблона. Мы видим, что в конструктор CSingleDocTemplate передаётся четыре параметра. Первый типа integer - это ID ресурса IDR_MAINFRAME. Следующие три параметра указывают на классы для документа, фрейма, и вида. Затем указатель на новый CSingleDocTemplate добавляется в список шаблонов документов, создаваемый объектом приложения. (В SDI приложении только один шаблон.)

Ресурс IDR_MAINFRAME содержит:

  1. Иконку приложения.
  2. Меню приложения.
  3. Таблицу акселераторов для меню.
  4. Строку документа.

Строка документа разбита на семь частей, отделённых друг от друга символом '\n':

  1. Заголовок окна основного окна.
  2. Заголовок окна присваиваемый новому документу. Если пропущено, то применяется значение по умолчанию "Untitled."
  3. Описание типа документа в MDI приложении. Данная строка не используется в SDI приложении.
  4. Описание типа документа, с расширением файла для данного документа по умолчанию, например, "My Big Program(*.mbp)."
  5. Трёхбуквенное расширение для типа документа, например ".mbp"
  6. Имя без пробелов, которое идентифицирует тип документа в реестре.
  7. Подробное имя типа документа, например, "My Big Program Document."

Примерно так эта строка может выглядеть:
"My Big Program\n\n\nMy Big Program(*.mbp)\n.mbp\nBigProgram\nMBP Document."

Заключение

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

 RECT x;
x.top = 30;
x.left = 30;
x.bottom = 300;
x.right = 300;
Create(NULL, "My New Window", WS_OVERLAPPEDWINDOW, x);

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

 
« Предыдущая статья   Следующая статья »