第三章 窗口和消息
进行Windows程序设计实际上是创建“窗口”对象和处理窗口消息的过程,“窗口”对象不仅指应用程序窗口,还包括对话框,按钮,单选钮等许多许多。在Windows中,“窗口”是无所不在的。窗口以“消息”的形式来接收用户的输入的,而“消息”是操作系统发送给应用程序的。比如,我们用鼠标按下一个按钮时,Windows操作系统把按鼠标的这个“消息”告诉按钮:“嘿!按钮我现在正在按你呢!”,按钮收到这个“消息”后,想了想:“那我就卧倒吧。”这时我们就看到按钮被鼠标按下了。就这么简单!
每一个窗口是在特定的“窗口类”的基础上创建的,窗口类中标识了“窗口过程”,而窗口是通过窗口过程接收和处理消息的。窗口过程实际上是一个函数,函数的参数含有消息的若干信息,我们可以利用这些信息来进行相应的处理。一个窗口可以接收到许多个消息,这些消息不能同时被窗口接收处理,它们就像在食堂打饭一样按顺序排成队,一个一个被窗口处理,这就是“消息列队”;但是,也有例外的情况,如同一个人有急事可能插队打饭,有些消息可以直接发送个窗口过程,不用放入消息队列中(用SendMessage函数)。
现在我知道了:创建一个窗口首先需要注册一个窗口类,需要一个窗口过程来处理窗口消息。书中的例子说明窗口和消息到底是什么,这个例子几乎会出现在每一个Windows程序中,也就是说,每一个Windows程序都是以这个程序为基础进行扩充的。熟悉它的每一个细节非常重要。(下面程序中的注释几乎解释了每一条语句的意义)
#include <windows.h>
//申明窗口过程函数
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
//Windows程序的入口函数
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;//定义一个Windows句柄的变量
MSG msg ;//定义一个消息的结构变量
/ //第一步: 定义一个窗口类的结构,此结构中的10个域描述了窗口类的特征,下面依次给这10个域赋值
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;//基于此窗口类创建的窗口风格
wndclass.lpfnWndProc = WndProc ;//指向窗口过程函数的指针
wndclass.cbClsExtra = 0 ;//窗口类结构的预留空间
wndclass.cbWndExtra = 0 ;//同上
wndclass.hInstance = hInstance ;//Windows程序的实例句柄,这是由编译器提供的
//为所有基于这个窗口类建立的窗口设置一个图标
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
//为所有基于这个窗口类建立的窗口设置一个鼠标
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
//指定了基于这个窗口类创建的窗口背景颜色,用取得一个白色刷子的句柄来实现
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
//指定窗口类的菜单,用NULL赋值表示这个程序中没有菜单
wndclass.lpszMenuName = NULL ;
//指定窗口类的名称
wndclass.lpszClassName = szAppName ;
/ // 第二步:向Windows注册一个窗口类,如果在Win98下运行RegisterClass函数,将返回0表明有错误发生
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
//第三步:基于上面注册的窗口类创建一个窗口,分配一块内存保存窗口的信息
hwnd = CreateWindow (szAppName, // 窗口类的名称,基于这个窗口类创建窗口
TEXT ("The Hello Program"), //窗口标题栏的名称
WS_OVERLAPPEDWINDOW, // 窗口的风格
CW_USEDEFAULT, //窗口位置的X坐标
CW_USEDEFAULT, //窗口位置的Y坐标
CW_USEDEFAULT, //窗口大小宽
CW_USEDEFAULT, //窗口大小高
NULL, // 父窗口的句柄
NULL, // 窗口菜单的句柄
hInstance, // Windows应用程序的实例句柄
NULL) ; // 创建参数的指针,可以用它访问程序中的数据
//第四步: //第四步:显示窗口,在显示屏上显示窗口(ShowWindow),并给窗口客户区刷上背景颜色(UpdateWindow)
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
//此时窗口已经出现在显示器上等待用户的键盘和鼠标的输入,随时进行相应的处理
/ //第五步:利用下面的循环,在消息队列中不断解析消息,然后Windows把消息发给窗口过程函数进行相应的处理。
while (GetMessage (&msg, NULL, 0, 0)) //从消息队列中取得消息
{
TranslateMessage (&msg) ;//将msg结构传给Windows,进行一些键盘转换
DispatchMessage (&msg) ; //Windows将消息发送给窗口过程函数(WndPro)进行相应的处理
}
//上面循环的终止条件是:取得WM_QUIT消息。
return msg.wParam ;
}
//定义窗口过程函数,在开发中,我们主要在这个函数中添加处理各种消息的代码。
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
{
case WM_CREATE: //当创建一个窗口时,Windows向WndProc发送WM_CREATE
return 0 ;
case WM_PAINT: //当绘制窗口客户区域时
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY: //清除窗口
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;//消息的默认处理
}
实际上Windows把消息的处理机制封装起来,使我们只要用窗口类去标识一个窗口过程函数(WndPro),就可以在这个过程函数中处理Windows向窗口发送的各种消息了。
最后,需要注意一个窗口类可以创建多个不同的窗口。