Смекни!
smekni.com

Ознакомление с приложениями Windows (стр. 13 из 18)

Вполне очевиден выход — разнести обработку сообщений по отдельным функциям, которые будут вызываться из процедуры обработки сообщений. Однако для каждого сообщения передаются свои данные, упакованные в двух параметрах — wParam и lParam. Иногда они не используются, иногда содержат какие–либо значения, иногда — указатели. Естественно, было бы удобным передавать в вызываемую функцию уже распакованные параметры. Затруднение здесь вызывает то, что для Windows API и для Win32 API одни и те же данные одного и того же сообщения могут быть упакованы по разному[18].

При разработке windowsx.h это все было учтено (для Windows API и для Win32 API распаковщики определяются по разному). Так, для каждого сообщения WM_xxx определен свой макрос с именем HANDLE_WM_xxx. Например, для сообщения WM_CREATE определен макрос:

HANDLE_WM_CREATE(hwnd, wParam, lParam, fn)

Параметры всех макросов одинаковые, что позволяет передавать им непосредственно параметры сообщения (окно–адресат hwnd, параметры wParam и lParam), а также имя функции–обработчика fn. Этот макрос должен использоваться внутри конструкции switch для вызова нужной функции и передачи ей распакованных параметров. Например фрагмент следующего вида:

switch ( uMsg ) {
case WM_CREATE: return HANDLE_WM_CREATE(hWnd,wParam,lParam,fnOnCreate);
// ...
}

будет превращен компилятором в следующий фрагмент (подробнее см. исходный текст windowsx.h):

switch ( uMsg ) {
case WM_CREATE:
return ((fnOnCreate)((hWnd),(LPCREATESTRUCT)(lParam)) ?
0L : (LRESULT)-1L);
// ...
}

То есть при раскрытии макроса HANDLE_WM_xxx осуществляется распаковка параметров, вызов функции и анализ возвращаемого результата. Здесь, кстати, скрыта одна ловушка (по счастью крайне редкая): результат, возвращаемой функцией–обработчиком не всегда будет совпадать с результатом, описанным в справочнике для данного сообщения. Случай с WM_CREATE именно такой — согласно описанию обработчик WM_CREATE должен вернуть 0L, если все в порядке. А, как мы видим в приведенном фрагменте, функция, вызываемая распаковщиком, должна вернуть TRUE, то есть не 0, если все в порядке (распаковщик сам заменит TRUE на 0L).

При рассмотрении этого примера возникает вопрос — а как должна быть описана функция–обработчик, что бы распаковщик ее правильно вызывал? Ответ прост — в самом файле windowsx.h перед определением соответствующего макроса приводится прототип этой функции. То есть нам надо сделать следующее: открыть windowsx.h, найти в нем строку, где определяется распаковщик для WM_CREATE (это легко делается поиском) и посмотреть на приведенный там текст:

/* BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) */
#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \
((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L)
#define FORWARD_WM_CREATE(hwnd, lpCreateStruct, fn) \
(BOOL)(DWORD)(fn) ((hwnd), WM_CREATE, 0L, \
(LPARAM)(LPCREATESTRUCT)(lpCreateStruct))

Описание функции Cls_OnCreate мы и ищем. Далее нам надо его просто скопировать в наше приложение и исправить при желании имя функции. Единственное, что остается не слишком удобным — так это вызов макроса–распаковщика — уж очень длинная строка получается. Для этого в windowsx.h содержится отдельный небольшой макрос:

HANDLE_MSG( hWnd, uMsg, fn )

Используется он таким способом:

switch ( uMsg ) {
HANDLE_MSG( hWnd, WM_CREATE, Cls_OnCreate );
// ...
}

При этом он сам вставляет “case WM_xxx: return...” и прочее. Важно следить, что бы в описании оконной процедуры параметры wParam и lParam назывались именно так и не иначе. Дело в том, что HANDLE_MSG при обращении к макросу HANDLE_WM_xxx указывает ему именно эти имена.

Чтобы закончить разговор о распаковщиках сообщений надо ответить только на два вопроса — зачем нужны макросы FORWARD_WM_xxx, определенные в том–же windowsx.h и как можно добавить распаковщики для каких–либо сообщений, там не определенных (например, нестандартных).

Рассмотренные пока макросы–распаковщики позволяли нам вызвать функцию–обработчик сообщения, имея для нее данные, упакованные в wParam и lParam. Однако иногда возникает другая необходимость — имея параметры функции передать какое–либо сообщение. Для этого предназначены макросы FORWARD_WM_xxx. Для использования этих макросов, необходимо, что–бы функция, получающая параметры сообщения, имела следующий вид:

LRESULT proc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );

Макросы FORWARD_WM_xxx получают в качестве параметров распакованные данные (как и функция–обработчик), упаковывают их в параметры сообщения и вызывают указанную функцию. По счастью практически все функции, которые придется вызывать с помощью макросов FORWARD_WM_xxx (SendMessage, PostMessage, DefWindowProc и пр.) соответствуют приведенному описанию.

Например, сообщение WM_SETFONT посылается окну (стандартного класса) для того, что бы назначить ему нужный шрифт. Параметры этого сообщения следующие: wParam содержит хендл шрифта, а младшее слово lParam указывает, надоли перерисовывать окно сразу после смены шрифта. Предположим, что ваше окно имеет дочернее окно, и вам хочется сделать так, чтобы при смене шрифта в вашем окне одновременно менялся шрифт в дочернем. Соответственно вы должны включить в оконную процедуру обработку сообщения WM_SETFONT и в его обработчике передать такое–же сообщение дочернему окну.

void Cls_OnSetFont( HWND hwnd, HFONT hfont, BOOL fRedraw );

LRESULT WINAPI _export proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch ( uMsg ) {
// ...
HANDLE_MSG( hWnd, WM_SETFONT, Cls_OnSetFont );
// ...
}
}

// ...

void Cls_OnSetFont( HWND hwnd, HFONT hfont, BOOL fRedraw )
{
HWND hwndChild = ...; // определениехендладочернегоокна
FORWARD_WM_SETFONT( hwndChild, hfont, fRedraw, SendMessage );
}

Здесь, кстати, можно было бы воспользоваться макросом SetWindowFont из того же windowsx.h. Этот макрос обращается к FORWARD_WM_SETFONT, как в рассмотренном примере, однако текст при этом становится более читаемым:

void Cls_OnSetFont( HWND hwnd, HFONT hfont, BOOL fRedraw )
{
HWND hwndChild = ...; // определение хендла дочернего окна
SetWindowFont( hwndChild, hfont, fRedraw );
}

Добавление собственных распаковщиков не должно вызвать больших затруднений — достаточно только разработать реализации макросов HANDLE_WM_xxx и FORWARD_WM_xxx аналогично уже сделанному в windowsx.h.

Пример 1B — использование распаковщиков сообщений

Этот пример иллюстрирует применение распаковщиков сообщений на примере простейшего приложения. Фактически он соответствует слегка измененному примеру 1A, в котором оконная процедура переписана для использования распаковщиков сообщений. Функция WinMain в этом примере осталась без изменений.

#defineSTRICT
#include <windows.h>
#include <windowsx.h>

#define UNUSED_ARG(arg) (arg)=(arg)

static char szWndClass[] = "test window";

BOOL Cls_OnCreate( HWND hwnd, LPCREATESTRUCT lpCreateStruct )
{
UNUSED_ARG( hwnd );
UNUSED_ARG( lpCreateStruct );
return TRUE;
}

void Cls_OnPaint( HWND hwnd )
{
PAINTSTRUCT ps;

BeginPaint( hwnd, &ps );
TextOut( ps.hdc, 0, 0, "Hello, world!", 13 );
EndPaint( hwnd, &ps );
}

void Cls_OnDestroy( HWND hwnd )
{
UNUSED_ARG( hwnd );

PostQuitMessage( 0 );
}

LRESULT WINAPI _export WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch ( uMsg ) {
HANDLE_MSG( hWnd, WM_CREATE, Cls_OnCreate );
HANDLE_MSG( hWnd, WM_PAINT, Cls_OnPaint );
HANDLE_MSG( hWnd, WM_DESTROY, Cls_OnDestroy );
default: break;
}
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}

static BOOL init_instance( HINSTANCE hInstance )
{
WNDCLASS wc;

wc.style = 0;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClass;
return RegisterClass( &wc ) == NULL ? FALSE : TRUE;
}

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
UNUSED_ARG( lpszCmdLine );
MSG msg;
HWND hWnd;

if ( !hPrevInst ) {
if ( !init_instance( hInst ) ) return 1;
}

hWnd= CreateWindow(
szWndClass, // class name
"window header", // window name
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT,CW_USEDEFAULT, // window position
CW_USEDEFAULT,CW_USEDEFAULT, // window size
NULL, // parent window
NULL, // menu
hInst, // current instance
NULL // user-defined parameters
);

if ( !hWnd ) return 1;

ShowWindow( hWnd, nCmdShow );
UpdateWindow( hWnd );

while ( GetMessage( &msg, NULL, NULL, NULL ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}

return msg.wParam;
}

Немного об объектах

Здесь мы рассмотрим некоторые основные особенности реализации объектно–ориентированного программирования в Windows. В последнее время получили огромное распространение библиотеки объектов для создания приложений в среде Windows. Особенно широко они стали распространяться с развитием систем визуального программирования. Наибольшее распространение получили библиотеки объектов компаний

· Borland — Object Windows Library (OWL), поддерживается компиляторами Borland C++ (рассматривается версия v2.5, сопровождающая компилятор Borland C/C++ v4.5).

· Microsoft — Microsoft Foundation Classes (MFC), поддерживается наибольшим количеством компиляторов, среди которых Microsoft Visual C++, Watcom C++, Symantec C++ и другие (рассматривается версия v4.0, сопровождающая Visual C/C++ v4.0).

Такие библиотеки достаточно многофункциональны и громоздки, размер исполняемого файла, созданного с их помощью редко бывает меньше 300–400K. Конечно, при разработке больших систем, поддерживающих такие инструменты как OLE, DAO или WOSE, регистрирующих свои собственные типы файлов и т.д., использование этих библиотек может существенно сократить время, необходимое для разработки приложения.

Эти библиотеки объектов, хотя и имеют огромное количество различий, неизбежно имеют и много общего, что определяется платформой, на которой они работают — Windows. Для обеспечения эффективной работы приложений эти библиотеки вынуждены предусматривать простой механизм доступа посредством методов объектов к функциям API, что их неизбежно сближает между собой. Кроме того реализация и иерархия объектов в Windows неизбежно приводит к появлению чем–то сходной иерархии классов в библиотеках ООП.

В этом разделе мы рассмотрим простейшее приложение в среде Windows, построенное средствами ООП, причем все классы будут оригинальными — ни MFC, ни OWL не применяется. Это сделано для того, что бы “извлечь” на поверхность некоторые аспекты разработки классов для Windows–приложений. Здесь будут использоваться существенно упрощенные методы реализации объектов, по сравнению с “большими” библиотеками.

Возможно, что в некоторых частных случаях использование такого подхода может оказаться и более продуктивным, чем применение MFC или OWL. Особенно, если ваше приложение похоже на простейшее “Hello, world!” (в этом случае, правда, еще удобнее может быть обойтись совсем без классов).