Windows窗口就是通过消息来与操作系统互动,窗口不能直接与使用者交流,使用者移动鼠标、敲键盘都是通过操作系统与窗口通信的,因此WinMain()函数中的消息循环很重要,没有它窗口便不能工作,而窗口消息处理函数更为重要,也是我们编程的主要对象。
消息循环共用到3个函数,GetMessage()的返回值一直不为0,直到接受WM_QUIT时才为0(也就是false)。DispatchMessage()是将消息传给窗口消息处理函数。对初学者来说,这3个函数写法比较固定,小雅不作详细说明。因为内容多了反而把人的思想搞乱,但初学者不要认为只能这样写。
对消息的正确理解是比较关键的,不要编了很多程序,仍不知消息究竟是什么。我们一起看一下消息的结构体。
typedef struct
{
HWND hwnd; //接受消息的窗口的句柄
UINT message; //消息(应用程序只能用低字节,高字节留给系统用)
WPARAM wParam; //消息的第一附加信息
LPARAM lParam; //消息的第二附加信息
DWORD time; //Specifies the time at which the message was posted.
POINT pt; //Specifies the cursor position, in screen coordinates, when the message was posted.
} MSG, *PMSG;
注意:上面消息循环中的三个函数用的消息是这个结构体的类型,而下面要阐述的消息处理函数的参数,用的是结构体中的第2-4元素即message、wParam、lParam。
Windows中消息的种类大致有2种,即系统消息和应用程序消息,消息的前缀也多种多样。不过日常用到的消息大都以“WM_”开头,而且许多消息的wParam和lParam都为0(也就是不使用),一般只有“WM_COMMAND”菜单命令或键盘鼠标的消息才使用wParam和lParam。
Windows中消息虽然很多,但并不是每一消息都要编程处理,我们一般把不需要特别处理的消息交给缺省消息处理函数DefWindowProc(),例子中的default部分便是。另外,有一函数必须处理,也就是关闭窗口消息WM_DESTROY,这时必须调用PostQuitMessage(0)来向操作系统发送一条终止程序的消息。
#include
<windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE, LPCSTR); BOOL InitInstance(HINSTANCE, LPCSTR,int
); //主入口函数int
WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine,int
nCmdShow) { MSG msg;char
szClassName[] ="DrawRect"; //窗口名 //注册窗口类if
(!InitApp(hCurInst, szClassName))return
FALSE; //初始化窗口if
(!InitInstance(hCurInst, szClassName, nCmdShow)) {return
FALSE; } //消息循环while
(GetMessage(&msg, NULL, NULL, NULL)) { TranslateMessage(&msg); //消息解释 DispatchMessage(&msg); //消息发送 }return
(int
)msg.wParam; }
用的最多的消息可能是WM_PAINT,当窗口改变大小、或有别的窗口从这个窗口面前经过,这个窗口中的文字或图案被冲掉,这就必须重新描画。因此,窗口中要显示的文字或图像一般都是在WM_PAINT中实现的。下面我们以在窗口上显示一行字作例子,不过,这时要稍微知道一点GDI(Graphic Device Interface)的知识。
要在窗口描画文字或图像,首先要先取得设备文本(Device Contexts)。取设备文本一般有2种方法,BeginPaint()函数和GetDC()函数。描画结束后必须调用EndPaint()函数和ReleaseDC来释放设备文本。后面2章将继续介绍GDI的基础知识。
要在窗口的某个位置描画文字,必须取窗口的有效区域(ClientRect),因为窗口的边框、菜单、工具条、状态条等部分是不能利用的。例如,要在窗口的正中间写一个字,用窗口宽度和高度的一半来取正中位置是不对的,除非这个窗口无任何边框、菜单、工具条、状态条等。正确的方法是先定义一个区域变量(类型为RECT),然后用GetClientRect()函数取出窗口有效区域放入这个变量中。最后,调用DrawText()函数在矩形区输出文字。DrawText()将在下一章细说,所谓区域类型RECT其实就是一个存放矩形块座标的结构体,下面是RECT的原型。
int
DrawText( //|typedef struct
_RECT { HDC hDC, //设备文本的句柄 //| LONG left; //左边位置 LPCTSTR lpString, //要输出的字符串 //| LONG top; //上边位置int
nCount, //字符串长度 //| LONG right; //右边位置 LPRECT lpRect, //要输出的区域 //| LONG bottom; //下边位置 UINT uFormat //输出的格式 //| } RECT, *PRECT; ); //|
使用BeginPaint()函数、EndPaint()函数时会同时得到一个结构体,里面有画图时的一些状态,一般为系统保留使用,小雅在初学VC时就想,不修改仅仅使用应该可以的吧?例如,窗口有效区域,不调用GetClientRect()函数,而直接从这个结构体中就可取出使用。下面例子中11行的“ps.rcPaint”这样也能得到正确结果。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; TCHAR szHello[50]="Wecome you to Http://www.quanxue.cn/";switch
(message) {case
WM_PAINT: //重画窗口消息 hdc = BeginPaint(hWnd, &ps); //开始描画 DrawText(hdc, szHello, (int
)strlen(szHello), &ps.rcPaint, DT_CENTER|DT_SINGLELINE|DT_VCENTER); //显示文字(横向对中|不自动回行|纵向对中) EndPaint(hWnd, &ps); //结束描画break
;case
WM_DESTROY: //关闭窗口消息 PostQuitMessage(0); //发送关闭消息break
;default
:return
DefWindowProc(hWnd, message, wParam, lParam); //缺省窗口消息处理函数 } return 0; }
事实上,这段程序是错误的!当改变窗口位置大小时程序没错,一旦窗口的一部分被其它窗口遮盖过(如下图红线部分),显示就不对了。原因是什么呢?其实是未真正了解这个“ps”的作用,ps中的rcPaint是窗口需要重新刷新的部分,并不是在任何情况都等于窗口的有效区域。下面部分是更正后的程序,另外,将以前的程序修改了窗口名、大小、背景等。
//登记窗口类 BOOL InitApp(HINSTANCE hInst, LPCSTR szClassName) { WNDCLASS wc; ZeroMemory(&wc,
sizeof
(wc)); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = (LPCSTR)szClassName;return
(RegisterClass(&wc)); } //初始化窗口类 BOOL InitInstance(HINSTANCE hInst, LPCSTR szClassName,int
nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "劝学网SDK教程", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 120, NULL, NULL, hInst, NULL);if
(!hWnd)return
FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);return
TRUE; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; TCHAR szHello[50]="Wecome you to Http://www.quanxue.cn/";switch
(message) {case
WM_PAINT: //重画窗口消息 hdc = BeginPaint(hWnd, &ps); //开始描画 RECT rc; GetClientRect(hWnd, &rc); //取窗口有效区域 //显示文字(横向对中|不自动回行|纵向对中) DrawText(hdc, szHello, (int
)strlen(szHello), &rc, DT_CENTER|DT_SINGLELINE|DT_VCENTER); EndPaint(hWnd, &ps); //结束描画break
;case
WM_DESTROY: //关闭窗口消息 PostQuitMessage(0); //发送关闭消息break
;default
:return
DefWindowProc(hWnd, message, wParam, lParam); //缺省窗口消息处理函数 } return 0; }