消息分流器定义在Microsoft Visual C++中提供的Wi n d o w s X . h文件里。通常在Wi n d o w s . h文件之后紧接着包含这个文件。Wi n d o w s X . . h文件就是一组# d e f i n e指令,建立了一组供我们使用的宏。Wi n d o w s X . h的宏实际上分为三组:消息分流器、子控件宏和A P I宏。这些宏以下述的方式为我们提供帮助:
• 利用这些宏可以减少程序中要做的转换( c a s t i n g)的数量,并可使所要求的转换是无错误的。使用C / C + +的Wi n d o w s编程中一个大的问题是所要求的转换数量。你很难看到一个不要求某种转换的Wi n d o w s函数调用。但应该尽量避免使用转换,因为转换阻碍编译器发现代码中的潜在错误。一个转换是在告诉编译程序:“我知道我在这里传递了错误的转换,但就要这样做。我知道我在干什么。”当你做了许多转换时,就很容易出错。编译程序应该尽可能对此提供帮助。
• 使代码的可读性更好。
• 可简化1 6位Wi n d o w s、3 2位Wi n d o w s和6 4位Wi n d o w s之间的代码移植工作。
• 易于理解(只是一些宏)
• 这些宏容易结合到已有的代码中。可以不管老的代码而立即在新的代码中使用这些宏。不必修改整个程序。
• 在C和C + +代码中都可以使用这些宏,尽管当使用C + +类时它们不是必需的。
• 如果需要某一个特性,而这些宏不直接支持这个特性,可以根据这个头文件中的宏,很容易地编写自己的宏。
• 不需要参照或记住费解的Wi n d o w s构造。例如,许多Wi n d o w s中的函数,要求一个l o n g型参数,其中这个长参数的高字( h i g h - w o r d)的值代表一个东西,而其低字( l o w - w o r d)又代表另一个东西。在调用这个函数之前,你必须用两个单独的值构造一个l o n g型值。通常利用Wi n D e f . h中的M A K E L O N G宏来做这种事。我简直记不清有多少次把两个值的次序给弄反了,造成对函数传递了一个错误的值。而Wi n d o w s X . h中的宏可以帮我们的忙。
消息分流器(message cracker)使窗口过程的编写更加容易。通常,窗口过程是用一个大的s w i t c h语句实现的。在我的经验中,我见过有的窗口过程的s w i t c h语句包含5百多行代码。我们都知道按这种方式实现窗口过程是一种坏的习惯,但我们都这么做过。而利用消息分流器可将s w i t c h语句分成小的函数,每个窗口消息对应一个函数。这样使代码更容易管理。
有关窗口过程的另一个问题是每个消息都有w P a r a m和l P a r a m参数,并且根据消息的不同,这些参数的意思也不同。在某些情况下,如对W M _ C O M M A N D消息,w P a r a m包含两个不同的值。w P a r a m参数的高字是通知码,而低字是控件的I D。或者是反过来?我总是忘了次序。如果使用消息分流器,就不用记住或查阅这些内容。消息分流器之所以这样命名,是因为它们对任何给定的消息进行分流。为了处理W M _ C O M M A N D消息,你只需编写这样一个函数:
void Cls_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { switch(id) { case ID_SOMELISTBOX: if(codeNotify != LBN_SELCHANGE) break; // Do LBN_SELCHANGE processing. break; case ID_SOMEBUTTON: break; } }
为了使用消息分流器,必须对你的窗口过程的s w i t c h语句做一些修改。看一看下面的窗口过程:
LRESULT WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { HANDLE_MSG(hwnd, WM_COMMAND, Cls_OnCommand); HANDLE_MSG(hwnd, WM_PAINT, Cls_OnPaint); HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy); default: return(DefWindowProc(hwnd, uMsg, wParam, lParam)); } }
#define HANDLE_MSG(hwnd, message, fn) \ case(message): \ return HANDLE_##message((hwnd), (wParam), (lParam), (fn));
case(WM_COMMAND): return HANDLE_WM_COMMAND((hwnd),(wParam), (lParam), (Cls_OnCommand));
#define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) \ ( (fn) ((hwnd), (int) (LOWORD(wParam)), (HWND)(lParam), (UINT) HIWORD(wParam)), 0L)
在使用消息分流器来处理一个消息之前,应该打开Wi n d o w s X . h文件并搜索要处理的消息。例如,如果搜索W M _ C O M M A N D,将会找到文件中包含下面代码行的部分:
/* void Cls_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify); */ #define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), (int)(LOWORD(wParam)), (HWND)(lParam), \ (UINT)HIWORD(wParam)), 0L) #define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \ (void)(fn)((hwnd), WM_COMMAND, \ MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), \ (LPARAM)(HWND)(hwndCtl))
void Cls_OnCommand (HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { // Do some normal processing. // Do default processing. FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, DefWindowProc); }
子控件宏(Child Control Macro)使发送消息到子控件变得更加容易。这些宏同F O RWA R O _W M _ *宏很相似。每个宏的定义以一个控件类型开始(这个控件是要对它发送消息的控件),后面跟一个下横线和消息名。例如,向一个列表框发送一个L B _ G E T C O U N T消息,就使用Wi n d o w s X . h中的这个宏:
#define ListBox_GetCount(hwndCtl) \ ((int)(DWORD)SendMessage((hwndCtl), LB_GETCOUNT, 0, 0L))
关于子控件宏,有一件事我不喜欢,就是这些宏要用控件窗口的句柄。许多时候,你要发送消息到一个控件,而这个控件是一个对话框的子控件。所以最终你总要调用G e t D l g I t e m,产生这样的代码:
int n = ListBox_GetCount(GetDlgItem(hDlg, ID_LISTBOX));
HWND hwndCtl = GetDlgItem(hDlg, ID_LISTBOX); int n = ListBox_GetCount(hwndCtl); ListBox_AddString(hwndCtl, "Another string");
A P I宏可以简化某些常用的操作,如建立一种新字体,选择字体到设备环境,保存原来字体的句柄。代码的形式如下:
HFONT hfontOrig = (HFONT) SelectObject(hdc, (HGDIOBJ) hfontNew);
#define SelectFont(hdc, hfont) \ ((HFONT) SelectObject( (hdc), (HGDIOBJ) (HFONT) (hfont)))
HFONT hfontOrig = SelectFont(hdc, hfontNew);
在Wi n d o w s X . h中还有其他一些A P I宏,有助于常用的Wi n d o w s任务。建议读者了解并使用这些宏。