Московский инженерно-физический институт

Вид материалаПрактикум

Содержание


Макрос HANDLE_MSG и структурированиепрограммы
LRESULT CALLBACK WndProc(HWND hwnd,UINT msg
Подобный материал:
1   2   3   4   5   6   7   8   9   ...   24

Макрос HANDLE_MSG и структурирование
программы


Как было показано в предыдущем разделе, оконная функция должна представлять собой длинную конструкцию с оператором switch и со столькими блоками case, сколько сообщений Windows предполагается обрабатывать в программе. При обработке ряда сообщений, например WMCOMMAND, внутрь блоков case приходится включать вложенные операторы switch-case, да еще не одного уровня вложенности. В результате функция WndProc() становится чрезвычайно длинной и запутанной. Фактически все приложение оказывается состоящим из едва ли не единственной функции WndProc() со множеством разветвлений внутри.

Заметного упрощения структуры программы можно добиться, используя группу макросов HANDLEMSG, определенных в файле WINDOWSX.H. При использовании этих макросов все процедуры обработки сообщений выделяются в отдельные функции, а в оконной функции WndProc() остаются только строки с макросами HANDLEMSG (по числу обрабатываемых в программе сообщений), которые обеспечивают передачу управления на эти функции при приходе того или иного сообщения. Оконная функция, даже при большом количестве обрабатываемых сообщений, становится короткой и чрезвычайно наглядной; наличие же для обработки каждого сообщения отдельной прикладной функции также весьма упрощает разработку и отладку их алгоритмов.

Модифицируем программу 1-1, введя в ее оконную функцию макрос HANDLEMSG. Фактически изменению подвергнется только оконная функция, которая распадется на две: собственно оконную функцию и вынесенный в отдельную функцию (названную нами OnDestroy()) алгоритм обработки сообщения WMDESTROY. Ниже приведены эти изменения.

/*Пример 1-1а (фрагменты).
/*Оконная функция на основе макроса HANDLE_MSG */


/*Прототипы используемых в программе функций пользователя*/

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

void OnDestroy(HWND);

/*Оконная функция WndProc главного окна*/

LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,

WPARAM wParam,LPARAM lParam){

switch(msg){

HANDLE_MSG(hwnd,WM_DESTROY,OnDestroy);

default:

return(DefWindowProc(hwnd,msg,wParam,lParam));

}

}

/*Функция OnDestroy обработки сообщения WM_DESTROY*/

void OnDestroy(HWND){

PostQuitMessage(0);//Вызов функции завершения приложения

}

В нашей программе обрабатывается единственное сообщение WMDESTROY. Соответственно в программу введена функция обработки этого сообщения OnDestroy(). В документации к Win­dows рекомендуется образовывать имена функций обработки сообщений из имени класса окна, значка подчеркивания, слова On (при, в случае) и имени соответствующего сообщения. Применительно к нашей программе имена функций обработки сообщений должны выглядеть таким образом:

MainWindow_OnDestroy();

MainWindow_OnPaint();

MainWindow_OnCommand();

и т. д. Однако мы для функций обработки сообщений, поступающих в главное окно, будем ради краткости опускать префикс, характеризующий класс, и называть эти функции просто OnDest­roy(), OnPaint() и т. д. Для функций, относящихся к внутренним окнам, префикс придется использовать, так как разные функции, разумеется, должны иметь разные имена.

Введение в программу новой функции требует определения ее прототипа. Соответственно в раздел прототипов приложения включена строка

void OnDestroy(HWND);//Прототип функции OnDestroy()

При описании прототипов функций обработки отдельных сообщений и при составлении текстов самих этих функций возникает вопрос об их параметрах и возвращаемых значениях. Наша функция OnDestroy() ничего не возвращает и получает при ее вызове один параметр типа HWND (очевидно, дескриптор главного окна). Однако в случае других функций это не так. Вообще говоря, для каждой функции обработки того или иного сообщения характерен свой набор параметров и свой тип возвращаемого значения. По существу параметры определяются, конечно, характером сообщения, формально же состав и порядок параметров задаются макросами HANDLEMSG. Для того, чтобы разобраться в этом вопросе, придется рассмотреть структуру макроса HANDLEMSG и его макрорасширения.

Макрос HANDLEMSG, коротко говоря, разворачивается в предложение языка C++ с ключевым словом case. Общий же для всех ключевых слов case оператор switch включается в текст оконной функции в явной форме:

LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,

WPARAM wParam,LPARAM lParam){

switch(msg){

HANDLE_MSG(hwnd,WM_PAINT,OnPaint);/*Фактически

case WM_PAINT: OnPaint()*/

HANDLE_MSG(hwnd,WM_DESTROY,OnDestroy;/*Фактически

case WM_DESTROY: OnDestroy()*/

В действительности, однако, расширение HANDLEMSG оказывается не таким простым.

Для макроса HANDLE_MSG в составе файла WINDOWSX.H имеется следующее определение:

#define HANDLE_MSG(hwnd,message,fn)\

case(message):return HANDLE_##message\

((hwnd),(wParam),(lParam),(fn))

(знак обратной косой черты (\) в макроопределении служит для перехода на следующую строку и никак не влияет на макрорасширение). Знак ## в составе макроопределения обозначает сцепление (конкатенацию) и в данном случае служит для получения составных имен новых макросов HANDLEWMPAINT, HANDLEWMDE­STROY и др. Таким образом, предложение

HANDLE_MSG(hwnd,WM_PAINT,OnPaint);

преобразуется в промежуточное макрорасширение

case (WM_PAINT):return HANDLE_WM_PAINT((hwnd),

(wParam),(lParam),(OnPaint));

а предложение

HANDLE_MSG(hwnd,WM_DESTROY,OnDestroy;

преобразуется в макрорасширение

case (WM_DESTROY):return HANDLE_WM_DESTROY((hwnd),

(wParam),(lParam),(OnDestroy));

Для каждого сообщения Windows в составе файла WINDOWSX.H имеется отдельный макрос вида HANDLEWMсооб­щение, причем их макроопределения уже неодинаковы и зависят от характеристик конкретного сообщения.

Для макроса HANDLEWMDESTROY дано следующее макроопределение:

#define HANDLE_WM_DESTROY(hwnd,wParam,lParam,fn)\

((fn)(hwnd),0L)

Подставив это определение вместо HANDLEWMDESTROY и опустив ненужные скобки, получим окончательное макрорасширение:

case WM_ DESTROY:return (OnDestroy (hwnd),0L);

Таким образом, по ходу расширения макроса убрались лишние (для данного сообщения) параметры wParam и lParam и образовалось синтаксически правильное предложение case. Как выполняется это предложение? Если пришло сообщение WMDESTROY, выполняется оператор return с двумя аргументами. Прежде всего выполняется оператор, стоящий на месте первого аргумента, т. е. вызывается функция OnDestroy (hwnd). Эта функция не должна возвращать каких-либо значений. После ее завершения срабатывает оператор return, возвращающий указанное значение – длинный 0. Любопытно, что завершающий знак “;”, который обязательно должен быть в конце любого предложения языка C++, переходит в окончательный текст из нашей строки с макросом HANDLEMSG.

Схожим образом расширяется строка для сообщения, например, WMPAINT:

case WM_PAINT:return(OnPaint(hwnd),0L);

Для других сообщений макросы вида HANDLEсообщение имеют более сложные определения, в которых выполняются необходимые преобразования аргументов функции WndProc() wParam и lParam в параметры функций обработки сообщений.

Таким образом, параметры прикладных функций обработки сообщений определяются макросами HANDLEWMсообщение. Однако извлечь интересующую нас информацию о прототипе функции из текста макроса затруднительно. Для облегчения программирования в файле WINDOWSX.H почти для каждого макроса HANDLE­WMсообщение приведен прототип соответствующей функции с указанием типа, порядка и в какой-то степени смысла ее параметров. Более детальную информацию о данных, поступающих в приложение вместе с сообщением, можно получить с помощью интерактивного справочника среды программирования Borland C++, вызвав справку по интересующему нас сообщению (например, WMDESTROY). Таким образом, при написании функций обработки сообщений приходится постоянно обращаться к файлу WINDOWSX.H и справочной системе среды разработки.

В табл. 1.1 приведены прототипы некоторых наиболее употребительных функций обработки сообщений; имена функций образованы из слова On и имени сообщения (в реальной программе эти имена могут быть какими угодно).

Следует еще раз подчеркнуть, что введение в программу макросов HANDLEMSG никоим образом не изменяет содержательную часть программы. Эти макросы служат лишь для повышения структурированности программы, что существенно облегчает ее чтение и отладку.

Таблица 1.1. Прототипы функций обработки сообщений

Сообщение

Прототип функции

WM_CHAR

void OnChar(HWND hwnd, UINT ch, int cRepeat);

WM_COMMAND

void OnCommand(HWND hwnd,
int id, HWND hwndCtl,
UINT codeNotify);

WM_CREATE

BOOL OnCreate(HWND hwnd,
LPCREATESTRUCT lpCreateStruct);

WM_CTLCOLORDLG

HBRUSH OnCtlColorDlg(HWND hwndDlg, HDC hdcDlg,
HWND hwndDlg, int code)

WM_CTLCOLORSTATIC

HBRUSH OnCtlColorStatic(HWND hwnd, HDC hdcDlg, HWND hwndCtl, int code)

WM_DESTROY

void OnDestroy(HWND hwnd);

WM_GETMINMAXINFO

void OnGetMinMaxInfo(
HWND hwnd,
LPMINMAXINFO lpMinMaxInfo);

WM_INITDIALOG

BOOL OnInitDialog(HWND hwnd, HWND hwndFocus,
LPARAM lParam);

WM_LBUTTONDOWN

void OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags);

WM_MOUSEMOVE

void OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags);

WM_NOTIFY

BOOL OnNotify(HWND hwnd,
INT idCtrl, NMHDR* pnmh);

WM_PAINT

void OnPaint(HWND hwnd);

WM_RBUTTONDOWN

void OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags);

WM_SYSCOMMAND

void OnSysCommand(HWND hwnd, UINT cmd, int x, int y);

WM_TIMER

void OnTimer(HWND hwnd,
UINT id);