Windows程序设计
窗口与消息
windows向应用程序发送了一条消息,其实现是windows调用了该程序内部的一个函数——这个函数是你写的,而且是该程序的核心,此函数的参数描述了由windows所发送了并由你的程序所接收的特定消息,这个函数被称为“窗口过程”。
WINCLASS结构体
在调用窗口前需要对WINCLASS结构体进行初始化,设置窗口的属性,然后再调用RegisterClass函数进行窗口类的注册。
typedef struct tagWNDCLASSW {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
} WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;
style为设定的类风格,使用按位或运算,
lpfnWndProc为设置窗口过程。
cbClsExtra和cbWndExtra 用于在类结构和Windows内部维护的窗口结构中预留一些额外的空间。
hInstance为设置应用程序的实例句柄,为WinMain中的第一个参数
hIcon为设置所有基于该窗口类的窗口设定一个图标,当获取预定义的图标句柄是,需要调用函数LoadIcon,并将第一个参数设为NULL,而当加载自定义图标时,一个参数为hInstance(程序的实例句柄),第二个参数用于标识该图标。
hCursor为设置鼠标指针的句柄,使用的是LoadCursor函数。与LoadIcon类似。
hbrBackGround为设置窗口的客户区的背景色
lpszMenuName为设置窗口类的菜单
lpszClassName为设置窗口类的名字。
窗口创建
由于窗口类只是定义了窗口的一般的特征,英雌基于同一个窗口类可以创建许多不同的窗口,在调用CreateWindow函数来创建窗口时,可以指定许多与窗口有关的细节信息。
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("The Hello Program"), //窗口标题
WS_OVERLAPPED, //窗口风格,或称为窗口格式
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数
窗口的显示
当CreateWindow调用返回时,窗口已在Windows内部被创建。但是要将窗口显示在屏幕上,还需要调用另外两个函数,第一个函数为:ShowWindow(hwnd, iCmdShow); 第一个参数是指向刚才由CreateWindow所创建的窗口的句柄,第二个参数是WinMain函数所接收的iCmdShow值。该参数决定着窗口在屏幕中的初始显示形式。
第二个函数为UpdateWindow使得客户区重绘。
消息循环
在UpdateWindow被调用之后,新建窗口在屏幕中便完全可见了,此时,该程序必须能够接收来自用户的键盘输入和鼠标输入,Windows为当前在其中运行的每一个Windows程序都维护了一个消息队列。当输入事件发生后,Windows会自动将这些事件转换为消息,并将其放置在应用程序的消息队列中。
程序中执行一段名为“消息循环”的代码段来从该消息队列中获取消息
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
其中msg是一个结构变量
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
其中hwnd 为消息所指向的窗口的句柄。
message 消息标识符,这是一个用于标识消息的数字。
wParam 一个32位的消息参数,该参数的含义和取值取决于具体的消息
lParam 另外一个32位的消息参数,该参数的含义和取值同样取决于具体消息
time 消息进入消息队列的时间
pt 消息进入消息队列中的时鼠标指针的位置坐标。
TranslateMessage(&msg);为将msg结构返还给Windows以进行某些键盘消息的转换。
DispatchMessage(&msg);为将msg结构再次返回给Windows。
窗口过程
窗口过程的名称可以任意命名,一个Windows程序可包含多个窗口过程,但一个窗口过程总是与一个通过调用RegisterClass注册的特定窗口类相关联。CreateWindow函数基于特定的窗口类创建窗口,而基于同一个窗口类则可创建多个窗口。
窗口过程总是按照如下方式来定义:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
窗口过程的4个参数与MSG结构的前4个字段是一一对应的。第一个参数是hwnd,表示接收消息的窗口的句柄,它与CreateWindow函数返回的句柄相同。
第二个参数与MSG结构的message字段对应,是一个标识消息的数字。最后两个参数是32位的消息参数,用于提供关于该消息的更丰富的信息。
应用程序通常并不直接对窗口过程进行调用,窗口过程几乎总是由Windows自身调用的。应用程序如果希望调用自身的窗口过程,则可通过调用函数SendMessage来实现。
消息的处理
窗口过程所接收的每条消息都由一个数字来标识,即窗口过程的message参数。在对与所有窗口没有处理的消息进行默认处理可以使用DefWindowProc函数。这个过程是非常重要的。
绘制和重绘
Windows是一个消息驱动的系统。他使用两种方式把各种事件通知给应用程序:把消息放在应用程序的消息队列中,或者向适当的窗口程序直接发送到消息。
WM_PAINT 消息发生的事件如下:
- 用户移动一个窗口,导致原来被遮盖的部分窗口暴露出来
- 用户调整窗口的大小。
- 程序调用ScrollWindow或ScrollDC函数滚动客户区
- 程序调用InvalidateRect或InvalidateRgn函数显示生成WM_PAINT消息
有效矩形和无效矩形
尽管窗口过程必须能够在收到WM_PAINT消息时更新整个客户区,但通常它只需要更新其中的一部分,最常见的是更新其中的一个矩形区域。需要重新绘制的部分被称为“无效区域”或“更新区域”
GDI
绘制一个窗口的客户区需要调用Windows的图像设备接口(GDI)函数。在GDI函数中,几乎所有的GDI函数都需要设备环境句柄作为函数的第一个参数。
设备环境
设备环境句柄是程序窗口使用GDI函数的“通行证”。有了设备环境句柄,就可以随心所欲地绘制你的客户区。
设备环境实际上是GDI内部维护的一个数据结构。设备环境与特定的显示设备想关联。对于视频显示,设备环境通常与屏幕上的一个特定的窗口相关联。
设备环境红的某些值是图形“属性”。这些属性决定了GDI绘图函数的工作细节。例如文本的颜色、文本背景的颜色、函数参数x和y如何映射到窗口的客户区,以及windows用什么显示字体显示文本。
程序在绘制前必须获取一个设备环境句柄,在获取环境句柄后,Windows会在内部设备环境结构中填入默认的属性值。
当程序完成了对客户区的绘制后,它必须释放设备环境句柄。在程序释放句柄之后,这个句柄不再有效并且不能再被使用。
获取设备环境句柄
在处理WM_PAINT消息时可以使用以下两个函数获取设备环境句柄:BeginPaint和EndPaint。两个函数都需要两个参数:一是窗口的句柄,这是窗口消息处理过程的参数;另一个是一个类型为PAINTSTRUCT结构的变量的地址(通常被命名为ps)。
在处理WM_PAINT消息时,窗口过程首先调用BeginPaint函数,该函数擦除无效区域的背景以便绘图。同时还会填充ps结构的各个字段。函数的返回值就是设备环境句柄(HDC hdc)
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
....//使用GDI函数
EndPaint(hwndm &ps);
return 0;
PAINTSTRUCT结构
typedef struct tagPAINtSTRUCT
{
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
}PAINTSTRUCT;
其中hdc为设备环境句柄,为BeginPaint函数的返回值
fErase字段一般被设置为FALSE。这意味着WIndows在先前的BeginPaint函数中已经擦除了无效区域的背景。
尽管最好让程序在处理WM_PAINT消息时才更新整个客户去,但有时也会发现在处理非WM_PAINT消息时绘制部分客户区也是很有用的。有些时候,还需要设备环境句柄用作其他用途,例如获取设备环境信息。
可以调用GetDC函数来获得窗口客户区的设备环境句柄,在使用完以后,调用ReleaseDC函数将它释放
hdc = GetDC(hwnd);
....
ReleaseDC(hwnd, hdc);
此两个函数必须成对使用,在处理一条消息时,当调用GetDC函数后,应该在退出窗口过程之前调用ReleaseDC。
与BeginPaint函数返回的设备环境句柄不同,从GetDC返回的设备环境句柄中裁剪矩形是整个客户去。这意味着你可以在客户区的任意部分绘制,而不仅仅是在无效矩形里,也就是说如果不存在无效矩形也没有关系。GetDC不会将无效区域有效化,如果需要将整个客户区有效化,可以调用下面的函数:
ValidateRect(hwnd, NULL);
GetDC和ReleaseDC函数用于处理键盘消息或鼠标消息。
另一个与GetDC类似的函数是GetWindowDC。GetDC返回的是窗口客户区的设备环境句柄,GetWindowDC返回的则是整个窗口的设备环境句柄。
TEXTOUT函数详解
TEXTOUT是显示文本的最重要的GDI函数。
TextOut(hdc, x, y, psText, iLength);
第一个参数为设备环境句柄:它既可以从GetDC返回的hdc值,也可以是处理WM_PAINT消息时从BeginPaint返回的hdc值。
参数psText是只想字符串的指针,而iLength是字符串中的字符数。如果psText指向一个Unicode字符串,则字符串占用的字节数将是iLength的两倍。
参数x和y决定着输出字符串在客户区的起始位置。x是水平位置,而y是垂直位置。
不同的显示器有着不同的分配率,程序可以调用GetSystemMetrics函数来获取用户界面的尺寸,同样的,通过调用GetTextMetrics函数,程序可以获取字体尺寸。GetTextMetrics函数需要一个设备环境句柄。
客户区的尺寸
在Windows应用程序中,窗口的尺寸可以有很大的变化。我们可以用CetClientRect函数来获取客户区的大小。但是这种方法效率比较低,更好的办法是在窗口过程处理WM_SIZE消息时获取窗口的客户区大小。当窗口的大小发生变化时,Windows会向窗口过程发送一条WM_SIZE消息。相应的lParam变量的低位字是客户区的宽度,而高位字是高度。可以在窗口过程中定义两个静态变量来保存着两个值
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
其中LOWORD和HIWORD是两个宏定义函数。分别取低位字和高位字。
滚动条
滚动条是图形用户界面中最好的特性之一。它们很容易使用,并且提供了很好的视觉效果。滚动条既可以垂直放置,也可以水平放置。
在应用程序的窗口中包括滚动条相当容易。只需在CreateWindow的第三个参数中包括窗口风格标识符WS_VSCROLL(垂直)或WS_HSCROLL(水平)。垂直滚动条的宽度和水平滚动条的高度都是固定的,如果需要,可以通过调用GetSystemMetrics函数来获取它们的值。
Windows负责处理滚动条中的所有鼠标消息,但是,滚动条并没有自动对应的键盘接口。如果想将方向键对应到滚动条上,则必须显式地提供相应的对应关系。
滚动条的范围和位置
每个滚动条都有相应的“范围”和“位置”。滚动条的范围是一对整数。位置是指滑块在范围中所处的位置。
在默认状态下,滚动条的范围是0~100.不过可以调用函数SetScrollRange()来设置范围。
SetScrollRange(hwnd, iBar, iMin, iMax, bRedraw);
里面的iBar参数要么是SB_VERT或者SB_HORZ,如果需要Windows根据新的范围来重绘滚动条时,请将bRedraw参数设为TRUE。
滑块的位置总是一个离散的整数值。
在程序中使用滚动条时,程序需要和Windows共同负责维护滚动条以及滑块在滚动条中的位置
Windows负责如下任务:
- 处理滚动条中的所有鼠标消息
- 当用户单机滚动条时,提供一种反向显示的闪烁
- 当用户拖动滑块时,在滚动条内移动滑块。
- 想拥有滚动条的窗口的窗口过程发送滚动条消息
程序负责如下任务:
- 初始化滚动条的范围和位置
- 处理传送给窗口过程的滚动条消息
- 更新滑块的位置
- 根据滚动条的变化更行客户区的内容
滚动条消息
当用户单击滚动条或拖动滑块时,Windows向窗口过程发送WM_VSCROLL消息或WM_HSCROLL消息。在滚动条上的任何鼠标动作会产生至少两条消息:一条在鼠标按键下,另一条在鼠标松开时。
像所有的消息一样,上两条消息都伴随着wParam和lParam消息参数,当滚动条时窗口的一部分时,可以囫囵lParam参数:它只用于滚动条时子窗口时,通常在对话框中。
wParam参数被分为低位字和高位字。wParam的低位字代表了鼠标在滚动条上的动作。这个值被称为“通知码”。
如果在滚动条的不同部分按住鼠标键不放,程序可能收到多条滚动条消息。当松开鼠标键时,程序会收到一条带有SB_ENDSCROLL通知码的消息。
将鼠标放在滑块上然后按下鼠标键时,可以移动滑块。这将会生成带SB_THUMBTRACK和SB_THUMBPOSITION通知码的滚动消息。当wParam的低位字是SB_THUMBTRACK时,wParam的高位字是用户拖动的当前位置,这个位置处于滚动条范围的最小值和最大值之间。当wParam的低位字是SB_THUMBPOSITION时,wParam的高位字是用户松开鼠标键时滑块的最终位置。对于其他的滚动条动作,wParam的高位字应被忽略。