    Обработка сообщений 

    Windows взаимодействует с приложением, посылая ему сообщения. Поэтому 
обработка сообщений является ядром всех при-no^eHHjn^Wiridows. В предыдущей 
главе был рассмотрен пример небольшой программы, написанной с использованием 
библиотеки MFC. Теперь давайте расширим ее таким образом, чтобы в программе 
принимались и обрабатывались некоторые наиболее распространенные сообщения. 
принимались и обрабатывались некоторые наиболее распространенные сообщения. 

    Что такое сообщения? 

    В Windows имеется огромное число сообщений. Каждое из них представляется 
уникальным целым значением. В библиотечном файле WINDOWS.H для всех сообщений 
определены стандартные имена. Вот наиболее распространенные сообщения: 
    	WM_CHAR	
	WM_PAINT                       
	WM_MOVE              
	WM_CLOSE 
	WM_LBUTTONUP    
	WM_LBUTTONDOWN    
	WM_COMMAND     
	WM_SIZE 
    Часто сообщения .сопровождаются различными числовыми параметрами, несущими 
дополнительную информацию. К ним относятся, например, координаты текстового 
курсора или курсора мыши, код нажатой клавиши, а также некоторые системные 
значения, такие как размер символа. 
    Windows взаимодействует с программой через механизм сообщений. В 
частности, каждый раз, когда происходит определенное событие, на которое 
должна отреагировать программа, ей посылается сообщение, определяющее данное 
событие. Важно понимать, что нет необходимости явно отвечать на все сообщения. 
Фактически, из-за огромного их числа программа не будет реагировать на 
большинство из них. Обрабатывать нужно только те сообщения, которые относятся 
непосредственно к программе. Для остальных в библиотеке MFC содержатся 
стандартные обработчики. 
    В традиционных Windows-программах каждое сообщение (а также связанная с 
ним информация) передается в качестве аргументов функции окна программы. В ней 
с помощью большой инструкции switch определяется тип полученного сообщения и 
производится соответствующая обработка. Откровенно говоря, это достаточно 
громоздкая процедура, чреватая ошибками. Однако при использовании библиотеки 
MFC обработка сообщений осуществляется гораздо проще. 

    Обработка сообщений в MFC 

    MFC также поддерживает механизм обработки сообщений. Это существенно 
упрощает написание программы. В MFC включен набор предопределенных 
функций-обработчиков сообщений, которые можно использовать в программе. Если 
программа содержит какую-либо из них, то эта функция будет вызываться каждый 
раз, когда поступает связанное с ней сообщение. При наличии дополнительной 
информации, относящейся к сообщению, она передается в виде аргументов функции. 
    Для организации обработки сообщения необходимо выполнить следующие действия: 
    1.    В очередь сообщений программы должна быть включена макрокоманда 
соответствующего сообщения.	-•—--. 
    2.    Прототип функции-обработчика должен быть включен в описание класса, 
ответственного за обработку данного сообщения. 
    3.    В программу должна быть включена реализация функции-обработчика. 
Теперь давайте рассмотрим этот механизм подробнее. 

    40 MFC: основы программирования 


    Включение макрокоманд в очередь сообщений 

    Чтобы программа могла ответить на сообщение, в очередь сообщений должна 
быть помещена связанная с ним макрокоманда. Названия макрокоманд в MFC 
соответствуют именам стандартных сообщений Windows, но дополнительно имеют 
префикс ON_ и заканчиваются парой круглых скобок. Однако изГэтого правила есть 
одно исключение: сообщению WM_COMMAND соответствует макрокоманда ON_COMMAND(). 
Причина заключается в том, что WM_COMMAND — это особое сообщение, которое 
специальным образом обрабатывается в MFC. (Об этом будет говориться в главе 
4.) Ниже приведены названия макрокоманд MFC, соответствующих перечисленным 
выше сообщениям: 
    	ON_WM_CHAR()	
	ON_WM_PAINT()                 
	ON_WM_MOVE() 
    	ON_WM_CLOSE()	
	ON_WM_LBUTTONUP()    
	ON_WM_LBUTTONDOWN() 
    	ON_COMMAND()	
	ON_WM_SIZE() 
    Чтобы включить макрокоманду в очередь сообщений, нужно просто поместить ее 
между командами BEGIN_MESSAGE_MAP() и END_MESSAGE_MAP(). Например, если 
необходимо, чтобы в программе обрабатывалось сообщение WM_CHAR, то очередь 
сообщений должна выглядеть следующим образом: 

    BEGIN_MESSAGE_MAP(CMainWin,   CFrameWnd) 
    ON_W'M_CHAR () END_MESSAGE_MAP() 

    Конечно, в очереди может находиться более одной макрокоманды. Например, в 
следующем примере производится обработка сообщений WM_CHAR и WM_LBUT-TONDOWN. 

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_CHAR() 
    ON_WM_LBUTTONDOWN () END_MESSAGE_MAP() 

    Как будет показано дальше, сообщение WM_CHAR генерируется при нажатии 
клавиши на клавиатуре, а сообщение WM_LBUTTONDOWN — при нажатии левой кнопки мыши. 

    Включение обработчиков сообщений в описание класса 

    Каждое сообщение, обрабатываемое в программе, должно быть связано с одним 
из обработчиков. Всеобработчики сообщений являются членами класса CWnd и могут 
быть переопределены в программе. Как правило, имя функции-обработчика состоит 
из названия сообщения (без WM_), которому предшествует префикс On. Например, 
обработчик для WM_CHAR~Ha3biBaeTCH OnChar(), для WM_LBUTTONDO\VN — OnLButtonDown(), 
для WM_PAINT - OnPaint(). 
    Обработчик сообщения должен быть помещен в описание класса, ответственного 
за создание окна. Это выполняется включением прототипа функции в объявление 
класса. В следующем примере обработчик OnPaint() включается в класс CMainWin: 

    class CMainWin : public CFrameWnd 
    { 
    public: 


    3. Обработка сообщений	41 


    CMainWin(); 
    afx_msg  void  OnPaintO; DECLARE_MESSAGE_MAP() }; 

    Как видите, для OnPaint() использован спецификатор типа afx_msg. С помощью 
него должны быть объявлены все прототипы функций-обработчиков. Теперь, когда 
OnPaint() является частью класса CMainWin, этот класс сможет принимать — и 
должен отвечать на — сообщение WM_PAINT. 
    С технической точки зрения обработчики сообщений не являются виртуальными 
функциями, хотя и похожи на них. Вместо этого, из соображений эффективности, 
они определены в специальной таблице сообщений в MFC. Как упоминалось только 
что, все функции-обработчики имеют тип afx_msg. В настоящее время это лишь 
метка спецификатора типа, хотя в будущем возможны изменения. 

    Реализация обработчиков сообщений 

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

    Подробнее о макросе BEGIN_MESSAGE_MAP() 

    Хотя до сих пор мы говорили об очереди сообщений в единственном числе, в 
программе их может быть несколько. Ведь, фактически, в каждом окне могут 
обрабатываться сообщения. К какому из окон относится данная очередь, 
определяется в параметрах макроса BEGIN_MESSAGE_MAP(). Его общий вид приведен ниже: 

    BEGIN_MESSAGE_MAP(Владелец, Базовый) 
    I/ здесь помещаются макрокоманды сообщений END_MESSAGE_MAP() 

    Название класса, к которому относится очередь, определяется параметром 
Владелец. Параметр Базовый определяет название базового класса, от которого 
был порожден класс-ападелец. Например, в очереди 

    BEGINJMESSAGE_MAP(CMainWin, CFrameWnd) 
    ON__WM_CHAR () 
    ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() 

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

    42	MFC: основы программирования 


    Реакция на нажатие клавиши 

    Одно из наиболее распространенных в Windows сообщений генерируется при 
нажатии клавиши. Оно называется WM_CHAR, и для него используется макрокоманда 
ON_WM_CHAR(). Важно понимать, что приложение никогда не получает данные 
непосредственно от клавиатуры. Вместо этого каждый раз при нажатии клавиши 
активному окну посылается сообщение WM_CHAR. Чтобы продемонстрировать это, 
давайте расширим пример из главы 2 таким образом, чтобы в нем обрабатывались 
сообщения от клавиатуры. 
    Обработчик сообщения WM_CHAR называется OnCharQ. Его прототип таков: 

    afxjnsg void OnChar(UINT Ch, UINT Count, UINT Flags); 

    Этот обработчик вызывается всякий раз, когда поступает сообщение WM_CHAR. 
ASCII-код нажатой клавиши передается в параметре С/г. Количество повторяющихся 
символов, сгенерированных в результате удерживания клавиши нажатой, передается 
в параметре Count. Параметр Flags кодируется следующим образом (табл. 3.1): 

    Таблица 3.1. Содержимое параметра Flags 

    Бит	Значение 

    15	Установлен, если клавиша отпущена; сброшен, если клавиша удерживается нажатой 
    14	Установлен, если клавиша была нажата до того, как послано сообщение; 
сброшен, если клавиша не была нажата 
    13	Установлен, если также нажата клавиша ALT; сброшен, если клавиша ALT не нажата 
    12	Используется Windows 
    11	Используется Windows 
    10	В настоящий момент не используется 
    9	В настоящий момент не используется 
    8	Установлен, если нажатая клавиша является функциональной или находится 
на расширенной клавиатуре; сброшен в противном случае 
    7 — 0	Код клавиши, зависящий от производителя (т.е. скэн-код) 

    Пока для нас важным является лишь параметр СУ/, так как он определяет код 
нажатой клавиши. Заметьте, однако, какую подробную информацию предоставляет 
Windows. И мы можем использовать ту ее часть, которая нам необходима. 
    Для обработки сообщения WM_CHAR необходимо поместить его макрокоманду в 
очередь сообщений, добавить прототип соответствующего обработчика в объявление 
класса, предназначенного для создания главного окна, и включить в программу 
реализацию функции OnCharQ. Вот, например, версия OnCharQ, в которой 
поступающие символы отображаются на экране: 

    char   str[80];   //   строка   для   вывода      'faMrtQ.J[fa\^J!)	•> = • • 
    //  Обработка   сообщения  WM_CHAR 
    afx_msg  void  CMainWin::OnChar(UINT   ch,   UINT   count,   UINT   flags) 


    3. Обработка сообщений	43 


    { 
    CClientDC dc(this); 
    dc.TextOut (1, 1, "   ", 3); // стирает предыдущий символ 
    wsprintf (str, "%c", ch); 
    dc.TextOut (1, 1, str, strlen (str)); 
    I 

    Смысл кода внутри OnCharQ весьма прост: введенный символ дублируется на 
экране (так называемый режим "эха"). Должно быть, вы удивлены, что для решения 
столь тривиальной задачи потребовалось так много строк. Причина заключается в 
том, что Windows должна организовать связь между программой и экраном. Это 
называется контекстом устройства, или просто DC (Device Context)*, и 
достигается путем объявления объекта типа CClientDC, порождаемого от CDC. 
Класс CClientDC позволяет предоставить контекст устройства для рабочей области 
окна. Пусть вас пока не волнует точное определение контекста устройства. Мы 
рассмотрим его в следующем параграфе. Для начала достаточно знать, что, 
получив контекст устройства, программа может начать выводить текст на экран. 
    Функция, предназначенная для отображения символа, называется TextOutO и 
является членом класса CDC. Есть два варианта этой функции. Их прототипы таковы: 

    	virtual BOOL CDC::TextOut(int X, int Y, LPCSTR JpszStr, int Length); 
	BOOL  CDC::TextOut(int X, int Y, const CString SStrOb); 

    В первом случае текстовая строка, адресуемая указателем IpszStr, выводится 
в окно в точке с координатами, которые содержатся в параметрах X и У. (По 
умолчанию координаты приводятся в пикселях.) Длина строки задается параметром 
Length. Во втором случае выводится строка, содержащаяся в переменной StrOb. 
CString — это один из специальных выделенных классов в MFC. Он предназначен 
для работы со строками. В книге мы будем работать с первым вариантом 
TextOut(). (Вы можете сами поэкспериментировать с CString.) В обоих случаях 
функция TextOut() возвращает либо ненулевое значение при успешном завершении, 
либо ноль. 
    Каждый раз при вызове OnChar() символ, введенный пользователем, 
записывается с помощью функции wsprintfQ в строку единичной длины, а затем с 
помощью TextOut() отображается в окне в позиции с координатами (1,1). 
(wsprintf() — это специальная Windows-версия стандартной функции sprintf().) 
Переменная sir объявлена глобальной, потому что должна сохранять свое значение 
между вызовами функции в последующих примерах. 
    Верхний левый угол рабочей области окна имеет координаты (0,0). Координаты 
точки окна всегда относятся к самому окну, а не к экрану, поэтому вводимый 
символ будет отображаться в верхнем левом углу независимо от действительного 
расположения окна на экране. 
    Цель первого вызова функции TextOutQ — стереть любой ранее отображенный 
символ. Поскольку Windows — это графическая система, символы в ней могут иметь 

    *     Читатели, привыкшие искать скрытый смысл в словах, в данном случае 
могут не стараться: термин "контекст устройства" условен даже для английского 
языка. — Примеч. ред. 


    44	MFC: основы программирования 

    разный размер, и замена одного символа другим не обязательно приведет к 
удалению первого. Например, если символ "w" заменяется символом "i", то часть 
"w" все равно останется на экране, если только не будет удалена специально. 
(Постарайтесь проследить, что происходит при первом вызове TextOut().) 
    Сначала вам может показаться, что использование TextOutQ для вывода одного 
символа не совсем рационально. Дело в том, что в MFC (и вообше в Windows) нет 
функции, предназначенной просто для вывода символа. Как вы увидите, большую 
часть диалога с пользователем Windows осуществляет через диалоговые окна, 
меню, панели инструментов и т.д. Поэтому функций для вывода текста в рабочую 
область окна совсем немного. 
    Ниже приведен пример программы обработки строк, начинающийся с 
библиотечного файла MESSAGE!.H, содержащего объявления порождаемых классов. 

    //  MESSAGE1 .Н 
    //  Это  класс  для   создания   главного  окна class  CMainWin   :   
public  CFrameWnd ( public: 
    CMainWin () ; 
    afxjnsg  void OnChar(UINT  ch,   UINT  count,   UINT   flags); 
    DECLARE_MESSAGE_MAP() ); 
    //  Это  класс  для   создания   приложения class   CApp   :   public   
CWinApp ( public: 
    BOOL  Initlnstance () ; ); 
    Вот текст программы: 
    //  Программа,   обрабатывающая  сообщение WM_CHAR 
    finclude   <afxwin.h> 
    #include   <string.h> 
    #include   "messagel.h" 
    char  str[80];   //   строка  для  вывода 
    CMainWin::CMainWin () { 
    Create(NULL,    "Processing  WM_CHAR Messages"); 
    ) 
    //  Инициализация   приложения BOOL  CApp::Initlnstance() { 
    m_pMainWnd  =  new  CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow); 


    3. Обработка сообщений	45 


    m_pMainWnd->UpdateWindow() ; 
    return TRUE; } 
    // Это очередь сообщений приложения BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_CHAR() ENDJMESSAGEJMAP() 
    // Обработка сообщения WM_CHAR 
    afx_msg void CMainWin: .-OnChar (UINT ch, UINT count, UINT flags) 
    { 
    CClientDC dc(this); 
    jdc.. TextOut (1, 1, "   ", 3); // стирает предыдущий символ 
    wsprintf(str, "%c", ch) ; dc.TextOut (1, 1, str, strlen(str)); } 

    CApp App; // создание экземпляра приложения На рис. 3.1 показано окно, 
создаваемое в программе. 

    В Processing WM_CHAR Messages 
    В 

    Рис. 3.1. Пример окна, создаваемого в программе обработки сообщения WMjCHAR 


    46 MFC: основы программирования 


    Контекст устройства 

    В предыдущем параграфе программа для вывода символов на экран должна была 
получать контекст устройства. Настало время разобраться, что же это такое. В 
широком смысле, контекст устройства представляет собой путь, по которому 
приложение Windows, используя соответствующий драйвер устройства, выводит 
информацию в рабочую область окна. Контекст устройства является структурой, 
полностью определяющей состояние этого драйвера и способ вывода информации. 
    Перед тем как приложение начнет выводить информацию в рабочую область 
окна, оно должно получить контекст устройства. Пока это не сделано, нет связи 
между программой и окном, предназначенным для вывода. А поскольку контекст 
устройства требуется для работы TextOut() и других функций вывода, то это 
правило становится обязательным. 
    Как известно, в традиционных Windows-программах контекст устройства 
получают путем вызова функции GetDC(), а освобождают с помощью функции 
Re-IeaseDC(). Поскольку Windows может предоставить лишь небольшое число 
контекстов, важно, чтобы программа освободила контекст по окончании работы с 
ним. К счастью, в MFC есть классы, способные управлять этим процессом. 
Например, при создании экземпляра объекта типа CClientDC программе выделяется 
контекст устройства. Если этот объект необходимо удалить, то вызывается 
функция ReleaseDC() и контекст устройства автоматически освобождается. В этом 
одно из преимуществ использования библиотеки MFC. 
    При создании объекта типа CClientDC программе предоставляется контекст 
устройства, относящийся к рабочей области окна. Конструктор класса CClientDC таков: 

    CClientDC(CWnd  *Window); 

    Параметр Window является указателем на окно, которому выделяется контекст 
устройства. Дл^_активного окнамождо. указать ключевое слово this. Если 
контекст устройства предоставлен быть не может, конструктор CCIientDC() 
обращается к классу CResourceException. 
    Короче, всякий раз, когда необходимо вывести информацию в рабочую область 
окна, необходимо получить контекст устройства с помощью CClientDC. Однако 
существует ^собьп1хдучай, когда контекст устройства должен быть получен 
по-другому. Это происходит при обработке сообщения WM_PAINT, рассматриваемого 
в следующем параграфе. 

    Обработка сообщения WM_PAINT 

    Прежде чем продолжить, запустите приведенную выше программу и введите 
несколько символов. А затем минимизируйте окно и верните его в прежнее 
состояние. В результате, как вы увидите, последний введенный символ не будет 
отображен. Аналогично, если окно закрыть другим окном, а затем вновь 
активизировать, символ не будет заново отображен. Причина этого проста: в 
общем случае Windows не сохраняет содержимое окна. Чтобы этого избежать, 
каждый раз, когда содержимое (жИггдолжно быть обновлено, программе посылается 
сообщение WM_PAINT. (Оно также посылается, когда окно отображается впервые.) И 
каждый раз, когда программа получает это сообщение, она должна обновить 
содержимое окна. В этом параграфе мы добавим в наш пример обработчик сообщения WM_PAINT. 

    3. Обработка сообщений	47 

    Примечание: По техническим причинам содержимое окна обновляется каждый раз 
когда окно перемещается или масштабируется. Этого, однако, не происходит, если 
окне было минимизировано или перекрыто другим окном, а затем вновь активизировано. 
    Прежде чем начать рассматривать процесс обработки сообщения WM_PAINT. 
будет полезно узнать, почему Windows не обновляет автоматически содержимое 
окна. Ответ очевиден: в большинстве ситуаций это значительно проше сделать в 
самой программе, где имеется гораздо больше информации о содержимом окна. Хотя 
преимущества такого подхода все еще широко оспариваются п^одзамшцггами, лучше 
просто его принять, потому что он вряд ли будет изменен. 
    Обработчик сообщения WM_PAINT называется OnPaint(). Его прототип таков: 

    afx_msg  void  OnPaintO; 

    Макрокоманда для этого сообщения называется ON_WM_PAINT(). 
    Для обработки сообщения WM_PAINT необходимо добавить прототип его 
обработчика в объявление класса CMainWin, включить соответствующую 
макрокоманду в очередь сообщений и определить тело обработчика. Например, 
приводимый ниже обработчик позволяет вывести на экран текущее содержимое 
строки str, т.е. последний введенный символ. 

    // Обработка сообщения WM_PAINT 
	afx_msg void CMainWin : :OnPaint () { 
    CPaintDC dc (this); 
    // следующая строка предназначена для 
	// отображения последнего введенного символа 
	dc.TextOut (1, 1, str, strlen (str)); } 

    Давайте рассмотрим этот пример поподробнее. Прежде всего обратите 
внимание, что контекст устройства получается путем создания объекта типа 
CPaintDC, а не CClientDC. По различным причинам при обработке сообщения 
WM_PAINT контекст устройства должен быть получен именно таким образом. 
Конструктор класса CPaintDC имеет следующий прототип: 

    CPaintDC(CWnd *Nindow) ; 

    Здесь параметр Window является указателем на окно, которому выделяется 
контекст устройства. Для активного окна можно задать ключевое слово this. Если 
контекст устройства не может быть предоставлен, конструктор CPaintDC() 
обращается к классу CResourceException. Помните, что такой контекст может 
использоваться тчэлько при обработке сообщения WM_PAINT. 
    Класс CPaintDC включает одну важную переменную, называемую m_ps, которая 
содержит структуру типа PAINTSTRUCT. Когда создается объект типа CPaintDC, то 
в этой структуре содержится информация, относящаяся к текущему состоянию 
контекста устройства. Структура PAINTSTRUCT определена следующим образом: 

    48 MFC: основы программирования 


    typedef   struct   tagPAINTSTRUCT   { 
    HOC  hdc;   //   дескриптор  контекста  устройства 
    BOOL  fErase;   //  TRUE,   если  фон  окна  нужно  стереть 
    RECT   rcPaint;   //   координаты  области  окна,   которую  необходимо // 
 перерисовать 
    BOOL  fRestore;   //   зарезервировано 
    BOOL  fIncUpdate;   //   зарезервировано 
    BYTE   rgbReserved[32];   //   зарезервировано }   PAINTSTRUCT; 

    Здесь hdc — это дескриптор для контекста устройства. В традиционных 
Windows-программах с контекстами устройств работают именно через дескрипторы. 
Однако в MFC используются объекты, поэтому потребность в дескрипторах 
возникает редко. Если фон окна должен быть стерт, то переменная fErase будет 
содержать ненулевое значение. (Сейчас мы не станем ее рассматривать.) В 
переменной rcPaint содержатся координаты области окна, которая должна быть 
перерисована. Тип RECT представляет собой структуру, которая определяет 
координаты верхнего левого и нижнего правого угла прямоугольной области. 
Вид структуры таков: 

    typedef tagRECT   ( 
    LONG   left,   top;   /*   верхний  левый  угол   */ 
    LONG   right,   bottom;   /*   правый  нижний  угол   */ }   RECT; 

    Использовать содержимое rcPaint нам не придется, поскольку мы 
предполагаем, что перерисовать нужно все окно. 
    Ниже приведен пример программы, обрабатывающей сообщение \VM_PAINT. Первым 
показан файл MESSAGE.H, содержащий объявления порождаемых классов. 

    // MESSAGE2.H 
    // Это класс для создания главного окна class CMainWin : public CFrameWnd 
( public: 
    CMainWin () ; 
    afx_msg void OnChar(UINT ch, UINT count, UINT flags); 
    afx_msg void OnPaintO; 
    DECLARE_MESSAGE_MAP() 
    }; 
    // Это класс для создания приложения class CApp : public CWinApp { public: 
    BOOL Initlnstance (); ); 
    Это текст программы: 
    /* Программа, обрабатывающая сообщения WM_CHAR и WM_PAINT */ 
    3. Обработка сообщений	49 

    tinclude	<afxwln.h> 
    #include	<string.h> 
    linclude	"message?.h" 
    char str[80] = "Sample Output"; // строка для вывода 
    CMainWin::CMainWin() { 
    Create(NULL, "Processing WM_PAINT Messages"); } 
    // Инициализация приложения BOOL CApp::Initlnstance() { 
    m_pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow); 
    m_pMainWnd->UpdateWindow() ; 
    return TRUE; } 
    // Это очередь сообщений приложения BEGINJMESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_CHAR() 
    ON_WM_PAINT() END_MESSAGE_MAP() 
    // Обработка сообщения WM_CHAR 
    afx_msg void CMainWin::OnChar(UINT ch, UINT count, UINT flags) 
    { 
    CClientDC dc(this); 
    ъ 
    dc.TextOut (1, 1, "   ", 3); // стирает предыдущий символ 
    wsprintf(str, "%c", ch) ; dc.TextOut (1, 1, str, strlen(str)); } 
    // Обработка сообщения WM_PAINT afx_msg void CMainWin::OnPaint() { 
    CPaintDC dc (this); 
    //   следующая   строка   предназначена  для //   отображения  последнего  
введенного  символа dc.TextOut(I,   I,   str,   strlen(str)); } 
    CApp App;   //   создание   экземпляра  приложения 

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

    50 MFC: основы программирования 

    массив str инициализируется значением "Sample Output", которое 
отображается сразу же после запуска программы. Это происходит потому, что при 
создании окна автоматически генерируется .сообщение \VM_PAINT. 
    Поскольку в данном примере сообщение WM_PAINT обрабатывается довольно 
простым способом, необходимо подчеркнуть, что в большинстве реальных проектов 
это осуществляется значительно сложнее, так как обычно окна содержат гораздо 
больше информации. 
    Поскольку ответственность за восстановление содержимого окна при его 
минимизации или перекрытии лежит на программе, в ней всегда должен быть 
предусмотрен подобный механизм. Обычно он реализуется тремя способами. 
Во-первых, можно просто вычислять координаты выводимых символов. Это легче 
всего осуществить, если ввод данных пользователем не предполагается. 
Во-вторых, иногда можно хранить запись последовательности событий, 
произошедших в окне, и воспроизводить их, когда необходимо обновить содержимое 
окна. И наконец, в программе может поддерживаться так называемое виртуальное 
окно, содержимое которого просто копируется в реальное, окно каждый^раз гтри 
его обновлении. Это наиболее общий метод. 
    (Его реализация будет описана позднее.) Какой из способов лучше, зависит 
от приложения. В большинстве примеров книги содержимое окна не обновляется, 
так как обычно это влечет за собой появление дополнительного кода, который 
чаще всего затрудняет понимание сути примера. Однако в реальных программах, 
претендующих на то, чтобы называться приложениями Windows, обновление 
содержимого окон должно выполняться обязательно. 

    Обработка сообщений мыши 

    Поскольку Windows является операционной системой, в значительной степени 
ориентированной на работу с мышью, все программы для Windows должны 
определенным образом реагировать на действия мыши. Учитывая важность этого 
момента, в Windows имеется несколько типов сообщений мыши. В данном параграфе 
рассматривается два наиболее общих из них, WMJLBUTTONDO 
    WN и WM_RBUTTON-DOWN, генерируемые при нажатии левой и правой кнопок мыши, 
соответственно. Обработчиками этих сообщений являются функции OnLButtonDown() 
и OnRButton-Down(). Их прототипы таковы: 
    afx_msg  void  OnLButtonDown(UINT   Flags,   CPoint   Loc); afxjnsg  void  
OnRButtonDown(UINT   Flags,   CPoint   Loc); 
    Значение параметра Flags указывает на то, была ли при генерировании 
сообщения нажата клавиша [CTRL], [SHIFT] или другая кнопка мыши. Параметр 
может содержать любую комбинацию из следующих значений: 

    MK_CONTROL MK_SHIFT MK_MBUTTON MK_RBUTTON MK LBUTTON 


    3. Обработка сообщений	51 

    Если одновременно с кнопкой мыши была нажата клавиша [CTRL], то параметр 
Flags будет содержать MK_CONTROL. Если была нажата клавиша [SHIFT], то 
значением Flags будет MK_SHIFT. Если при нажатии левой кнопки мыши 
удерживалась и правая кнопка, значением будет MK_RBUTTON. И, наоборот, если 
при нажатии правой кнопки удерживалась левая, значением будет 
    MK_LBUTTON. Если же при нажатии одной из этих кнопок удерживалась также 
средняя кнопка (при наличии оной), то параметр Flags будет содержать 
MK_MBUTTON. Одновременно может быть установлено несколько таких значений. 
Поскольку мы не будем использовать этот параметр, можете сами 
поэкспериментировать с ним. 
    Параметр Loc, являющийся объектом типа CPoint, содержит координаты курсора 
мыши в момент нажатия кнопки. Класс CPoint порождается от структуры POINT, 
определенной следующим образом: 

    typedef struct tagPOINT { 
    LONG x; 
    LONG y; } POINT; 

    Структура POINT содержит координаты (X,Y) курсора мыши. Таким образом, 
координата X находится в переменной Loc.x, а координата У — в переменной Loc.у. 
    В следующем примере в нашу программу вносятся необходимые элементы, 
позволяющие ей обрабатывать сообщения мыши. Каждый раз при нажатии левой или 
правой кнопки мыши выводится строка сообщения, которая отображается в позиции 
курсора мыши. Вот текст файла, содержащего объявления порождаемых классов: 

    // MESSAGES.H 
    // Это класс для создания главного окна 
    class CMainWin : public CFrameWnd	, 
    { 
    public: 
    CMainWin(); 
    afx_msg void OnChar(UINT ch, UINT count, UINT flags); 
    afx_msg void OnPaintO; 
    afx_msg void OnLButtonDown(UINT flags, CPoint loc); 
    afx_msg void OnRButtonDown (UINT flags, CPoint loc); 
    DECLAREJMESSAGE_MAP() }; 
    // Это класс для создания приложения class CApp : public CWinApp { public: 
    BOOL Initlnstance(); }; 


    52 MFC: основы программирования 

    Это текст самой программы: 

    // Программа, обрабатывающая сообщения мыши 
    #include	<afxwin.h> 
    tinclude	<string.h> 
    #include	"messages.h" 
    char str[80] = "Sample Output"; // строка для вывода 
    CMainWin::CMainWin() { 
    Create(NULL, "Processing Mouse Messages"); } 
    // Инициализация приложения BOOL CApp::Initlnstance() ( 
    m_pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow); 
    m_pMainWnd->UpdateWindow(); 
    return TRUE; } 
    // Это очередь сообщений приложения BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_CHAR() 
    ON_WM__PAINT () 
    ON_WM_LBUTTONDOWN() 
    ON_WM_RBUTTONDOWN() END_MESSAGE_MAP()	' 
    // Обработка сообщения WM_CHAR 
    afx_msg void CMainWin::OnChar(UINT ch, UINT count, UINT flags) 
    ( 
    CClientDC dc(this); 
    dc.TextOut (1, I, "   ", 3); // стирает предыдущий символ 
    wsprintf(str, "%c", ch) ; dc.TextOut (1, 1, str, strlen(str)); } 
    // Обработка сообщения WM_PAINT afx_msg void CMainWin::OnPaint() { 
    CPaintDC dc (this); 
    // следующая строка предназначена для 
    // отображения последнего введенного символа 
    dc.TextOut(I, I, str, strlen(str)); 


    3. Обработка сообщений	53 

    // Обработка нажатия левой кнопки мыши 
    afx_msg void CMainWin::OnLButtonDown(DINT flags, CPoint loc) 
    CClientDC dc(this); 
    wsprintf(str, "Left button is down."); dc.TextOut(loc.x, loc.y, str, 
strlen (str)); s 
    // Обработка нажатия правой кнопки мыши 
    afx_msg void CMainWin::OnRButtonDown(UINT flags, CPoint loc) 
    { 
    CClientDC dc(this); 
    wsprintf(str, "Right button is down."); dc.TextOut(loc.x, loc.y, str, 
strlen (str)); 
    1 
    CApp  App;   //   создание   экземпляра   приложения 

    На рис. 3.2. представлен пример работы программы. 

    Left button is down. 
    Right button is down. Left button is down. 
    Left button is down. 
    Left button is down. 
    Left button is down. 
    Left button is down.	I 
    Left button is down.	i; 
    \ 
    Right button is down.	' 

    Рис. З.2. Пример работы программы обработки сообщений мыши 


    54 MFC: основы программирования 


    Генерация сообщения WM PAINT 

    Программа сама может вызвать генерацию сообщения WM_PAINT. Вы можете 
спросить, зачем это нужно, ведь, кажется, программа обновляет содержимое окна 
всякий раз, когда возникает такая необходимость. Однако это предположение 
неверно. Помните, что процесс перерисовки окна может занимать достаточно много 
времени, ведь Windows — это многозадачная система и в ней одновременно могут 
выполняться несколько программ, каждой из которых также требуются ресурсы 
процессора. Поэтому программе нужно просто сообщить Windows о необходимости 
вывода информации, но предоставить системе самой решить, когда это лучше всего 
выполнить. Такой подход позволяет Windows эффективнее распределять ресурсы 
процессора между всеми выполняющимися задачами. В этом случае программа 
приостанавливает вывод на экран до тех пор, пока не будет получено сообщение WM_PAINT. 
    В предыдущих примерах сообщение WM_PAINT принималось, только когда окно не 
было перекрыто. Но если вывод на экран задерживается до принятия WM_PAINT, то 
для организации интерактивного ввода-вывода необходимо найти способ сообщить 
Windows о том, что она должна послать сообщение WM_PAINT в окно, если вывод не 
завершен. Как и следовало ожидать, в Windows есть такая возможность. Поэтому 
при наличии информации, предназначенной для вывода на экран, программа должна 
просто запросить, чтобы сообщение WM_PAINT было послано, когда Windows готова 
к в'ы'водуГ 
    Чтобы побудить Windows послать сообщение WM_PAINT, программа должна 
вызвать функцию InvalidateRect(), являющуюся членом класса CWnd. Прототип 
функции таков: 

    void  CWnd::InvalidateRect(LPCRECT   IpRegion,   BOOL   Erase =   TRUE); 

    Здесь параметр IpRegion является указателем на объект типа CRect, 
определяющий область внутри окна, которая должна быть перерисована. Класс 
CRect порождается от структуры типа RECT, описанной выше. Координаты 
обновляемой области определятся полями структуры RECT. Однако если значение 
параметра равно NULL, то обновить необходимо содержимое всего окна. Если 
параметр Erase не равен нулю, то нужно стереть фон окна. (Это значение по 
умолчанию.) Если значение параметра равно нулю, то фон будет оставлен без изменений. 
    При вызове функции InvalidateRect() системе сообщается о том, что 
содержимое окна некорректно и должно быть обновлено. Это, в свою очередь, 
приводит к тому, что Windows посылает программе сообщение WM_PAINT. 
    Ниже приводится переработанный вариант предыдущей программы, в котором 
весьгюрцесс ^ывода_направляется с помощью обработчика сообщения WM_PAINT. 
Каждый раз, когда необходимо осуществить вывод, посылается сообщение WM_PAINT. 
Другие обработчики просто подготавливают предназначенную для вывода 
информацию, а затем вызывают функцию InvalidateRect(). Вот текст файла, 
содержащего объявления порождаемых классов: 

    // MESSAGE4.Н 
    // Это класс для создания главного окна 


    3. Обработка сообщений	55 


    class CMainWin : public CFrameWnd 
    { 
    public: 
    CMainWin (); 
    afx_msg void OnChar(UINT ch, UINT count, UINT flags); 
    afx_jnsg void OnPaintO; 
    afx_msg void OnLButtonDown(UINT flags, CPoint loc); 
    afx_msg void OnRButtonDown(UINT flags, CPoint loc); 
    DECLARE_MESSAGE_MAP() } ; 
    // Это класс для создания приложения class CApp : public CWinApp { public: 
    BOOL Initlnstance (); }; 
    Это текст программы: 
    /* Программа, организующая вывод на основе сообщения WM_PAINT 
    finclude <afxwin.h> finclude <string.h> finclude "message4.h" 
    char str[80] = "Sample Output"; // строка для вывода int X = 1, Y = 1; // 
текущая позиция вывода 
    CMainWin::CMainWin() { 
    Create(NULL, "Routing Output Through WM_PAINT"); 
    ! 
    // Инициализация приложения BOOL CApp: :Initlnstance () { 
    m_pMainWnd = new CMainWin; 
    m_pMainWnd->ShowWindow(m_nCmdShow) ; 
    m_pMainWnd->UpdateWindow(); 
    return TRUE; } 
    // Это очередь сообщений приложения BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_CHAR() 
    ON_WM_PAINT() 
    ON_WM_LBUTTONDOWN() 
    ON_WM_RBUTTONDOWN() END_MESSAGE_MAP() 
    // Обработка сообщения WM_CHAR 
    afx_msg void CMainWin::OnChar(UINT ch, UINT count, UINT flags) 


    56 MFC: основы программирования 


    i 
    X = Y = 1; 
    wsprintf(str, "%c", ch) ; InvalidateRect(NULL); } 
    // Обработка сообщения WM_PAINT afx_msg void CMainWin::OnPaint() { 
    CPaintDC dc (this); 
    // следующая строка предназначена для 
    // отображения последнего введенного символа 
    dc.TextOut(X, Y, str, strlen (str)); 
    I 
    // Обработка нажатия левой кнопки мыши 
    afxjnsg void CMainWin::OnLButtonDown(UINT flags, CPoint loc) 
    { 
    wsprintf(str, "Left button is down."); 
    X = loc.x; 
    Y = loc.y; 
    InvalidateRect(NULL); l 
    // Обработка нажатия правой кнопки мыши 
    afxjnsg void CMainWin::OnRButtonDown(UINT flags, CPoint loc) 
    ( 
    wsprintf (str, "Right button is down."); 
    X = loc.x; 
    Y = loc.y; 
    InvalidateRect(NULL); 
    CApp App; // создание экземпляра приложения 

    Заметьте, что в программе добавились две новые _глрбальнь1е переменные, X 
и Y, содержащие координаты позиции окна, в которой будет выведен текст при 
получении сообщения WM_PAINT. 
    Как видите, организация вывода на основе сообщения WM_PAINT позволила 
немного сократить программу и сделать ее, в определенном смысле, более 
понятной. Как упоминалось в начале параграфе, это также позволяет Windows 
самой принимать решение OJTOM, когда лучше всего обновить содержимое окна. 
    Примечание: Все (или почти все) приложения Windows организуют вывод на 
основе сообщения WM__PAINT по причинам, упомянутым выше. Однако предыдущие 
программы с технической точки зрения не являются неправильными, поскольку 
также отвечают на это сообщение. Просто примененный в них подход не всегда 
оказывается лучшим. 

    3. Обработка сообщений 57 


    Обработка сообщения WM__DESTROY 

    Есть одно специальное сообщение, на которое программе также требуете» 
отвечать: WM_DESTROY. Оно посылается окну, когда последнее должно был удалено, 
то есть закрыто. Если такое сообщение получает главное окно приложения это 
означает, что программа завершается. В этом случае приложение должно выполнить 
все необходимые операции по выгрузке. Обработчик сообщения WM_DESTRO\ 
называется OnDestroyO, а его прототип таков: 

    afx_msg void OnDestroyO; 

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

    Обработка сообщений таймера 

    Последнее сообщение, рассматриваемое в этой главе, — WM_TIMER. С помощью 
Windows можно управлять работой таймера, который периодически прерывает работу 
программы. Каждый раз, когда происходит прерывание таймера, программе 
посылается сообщение WM_TIMER. Это хороший способ периодически "будить" 
программу. Он особенно полезен, когда программа выполняется в фоновом режиме. 
    Для запуска таймера используется функция SetTimerQ, являющаяся членом 
класса CWnd. Ее прототип таков: 

    UINT CWnd::SetTimer(UINT ID, UINT Length, 
    void(CALLBACK EXPORT *TFunc) (HWND, UINT, UINT, 
    DWORD) 

    Здесь параметр ID задает числовой идентификатор, который будет 
ассоциироваться с данным таймером. (Активными могут быть сразу несколько 
таймеров.) Значение Length определяет длину интервала работы таймера, т.е. 
промежуток времени, проходящий между двумя прерываниями. Функция, на которую 
указывает параметр TFunc, является функцией таймера и вызывается в момент 
прерывания. Это должна быть функция обратного вызова, которая возвращает VOID 
CALLBACK и принимает параметры тех же типов, что и традиционная функция окна. 
Если же значение TFunc равно NULL, как это обычно бывает, то сообщения таймера 
будут посылаться главному окну программы. В таком случае при каждом прерывании 
таймера сообщение WMJT 
    IMER помещается в очередь сообщений программы и обрабатывается также, как 
и все другие сообщения. Этот принцип использован в следующем примере. В случае 
успеха функция возвращает значение ID. Если таймер недоступен, возвращается ноль. 
    Когда таймер запущен, он продолжает периодически прерывать выполнение 
программы до тех пор, пока не будет вызвана функция CWnd::KillTimer(), 
прототип которой приведен ниже: 

    BOOL CWnd:iKillTimer(int ID); 


    58 MFC: основы программирования 

    Здесь параметр ID является идентификатором соответствующего таймера. 
Функция KillTimerQ возвращает ненулевое значение при успешном завершении и 
ноль — в противном случае. 
    Сообщения таймера обрабатываются функцией OnTimer(), которая имеет такой прототип: 

    afx_msg  void  OnTimer(UINT   ID); 

    Каждый раз при вызове функции OnTimer() параметр ID содержит идентификатор 
того таймера, который инициировал прерывание. Если в программе используется 
несколько таймеров, то необходимо проверять значение параметра ID, чтобы 
определить, какой из таймеров послал сообщение. 
    Использование таймера демонстрируется в следующей программе, где с его 
помощью будут созданы "часы". Текущее системное время и дата могут быть 
получены и отображены с помощью функций класса CTime. (Класс CTime включает 
стандартные библиотечные функции даты и времени из С и C++.) При каждом 
прерывании таймера, происходящем примерно раз в секунду, значение времени 
обновляется. Таким образом, время отображается с точностью до секунды. 
Поскольку все таймеры по окончании использования должны быть деактивированы, 
то при получении сообщения WM_DESTROY программа "останавливает" таймер. Ниже 
приведен текст файла, содержащего объявления порождаемых классов. 

    // MESSAGES. H 
    // Это класс для создания главного окна class CMainWin : public CFrameWnd 
    { 
    public: 
    CMainWin (); 
    afx_msg void OnPaintO; afx__msg void OnTimer (UINT ID); afx_msg void 
OnDestroy(); DECLARE_MESSAGE_MAP() 
    }; 
    //  Это  класс  для   создания   приложения class   CApp   :   public   CWinApp 
    { 
    public: 
    BOOL Initlnstance () ; }; 
    Это текст программы: 
    // Программа-"часы" 
    #include	<afxwin.h> 
    #include	<string.h> 
    #include	<time.h> 
    #include	"messages.h" 


    3. Обработка сообщений 59 


    char str[80] = ""; // строка для вывода 
    CMainWin::CMainWin() { 
    RECT r; 
    r.left = r.top = 10; 
    r.right = 200; 
    r.bottom = 60; 
    Create(NULL, "A Clock Program", WS__OVERLAPPEDWINDOW, r); 
    } 
    // Инициализация приложения BOOL CApp: : Initlnstance() { 
    m_pMainWnd = new CMainWin; 
    // запуск таймера 
    if (m_pMainWnd->SetTimer(1, 1000, NULL) != 1) return FALSE; 
    m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow() ; 
    return TRUE; } 
    // Это очередь сообщений приложения BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd) 
    ON_WM_PAINT() 
    ON_WM_TIMER 
    ON_WM_DESTROY() END_MESSAGE_MAP() 
    // Обработка сообщения WM_PAINT afx_msg void CMainWin::OnPaint() { 
    CPaintDC dc (this); 
    // следующая строка предназначена для 
    // отображения последнего введенного символа 
    dc.TextOut(1, 1, str, strlen (str)); 
    } 
    // Обработка сообщения WM__TIMER afx_msg void CMainWin::OnTimer(UINT ID) { 
    CTime curtime = CTime::GetCurrentTime(); 
    struct tm *newtime; 
    newtime = curtime.GetLocalTm(); 
    wsprintf (str, asctime(newtime)); 
    str[strlen(str)-1] = '\0'; // удаляет символы /r/n 


    60	MFC: основы программирования 


    InvalidateRect(NULL, 0); 
    // Завершение приложения 
    afx_msg void CMainWin::OnDestroy() 
    { 
    KillTimer(1); 
    CApp App;   //   создание   экземпляра   приложения 

   Пример работы программы показан на рис. 3.3. 


    Ш A Clock Program 
    Sat Jan 11 16:25:241997 

    Рис. 3.3. Программа-'часы" 

    Для получения значения текущего времени в программе используется класс 
CTime. Статическая функция GetCurrentTime() возвращает значение системного 
времени. Функция GetLocalTmQ возвращает указатель на стандартную структуру ТМ, 
определенную в библиотеке C/C++. Затем он используется в стандартной 
библиотечной функции asctime(), которая возвращает указатель на строку, 
содержащую значение текущего времени. Если впоследствии вы будете работать с 
датой и временем, можете поэкспериментировать с классом CTime. 
    Теперь, когда мы узнали о том, как Windows-программа обрабатывает 
сообщения, давайте перейдем к созданию окон сообщений и меню. Они 
рассматриваются в следующей главе. 

    3. Обработка сообщений 61 