一、HELLOWIN程序
建立一个视窗首先需要注册一个视窗类别,那需要一个视窗消息处理函数来处理视窗消息。处理视窗消息对每个Windows程序都带来了些负担。源代码如下:
HELLOWIN.C
/*------------------------------------------------------------------------
HELLOWIN.C -- Displays "Hello, Windows 98!" in client area
(c) Charles Petzold, 1998
-----------------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;
MSG msg ;
WNDCLAS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuNam = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox ( NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow( szAppName, // window class name
TEXT ("The Hello Program"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL) ; // creation parameters
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
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:
PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
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) ;
}
1、涉及到的函数说明:
- LoadIcon 载入图标供程序使用。
- LoadCursor 载入滑鼠游标供程序使用。
- GetStockObject 取得一个图形句柄(在这个例子中,是取得绘制视窗背景的画刷句柄)。
- RegisterClass 为程序视窗注册视窗类别。
- MessageBox 显示消息对话框。
- CreateWindow 根据视窗类别建立一个视窗。
- ShowWindow 在屏幕上显示视窗。
- UpdateWindow 指示视窗自我更新。
- GetMessage 从消息队列中取得消息。
- TranslateMessage 转译某些键盘消息。
- DispatchMessage 将消息发送给视窗消息处理程序。
- PlaySound 播放一个音乐文件。
- BeginPaint 开始绘制视窗。
- GetClientRect 取得视窗显示区域的大小。
- DrawText 显示字串。
- EndPaint 结束绘制视窗。
- PostQuitMessage 在消息队列中插入一个「退出程序」消息。
- DefWindowProc 执行内定的消息处理。
二、部分参数说明: - UINT unsigned int (无正负号整数)32位
- PSTR 是一个char *
- WPARAM 为一个UINT,即unsigned int(无正负号整数)32位
- LPARAM 为一个LONG(这就是C中的long整型)32位
- LRESULT 为一个LONG
- CALLBACK _stdcall
三、句柄说明:
句柄是一个(通常为32位的)整数,它代表一个对象,程序在其他Windows函数中使用这个句柄,以使用它代表的对象。句柄的实际值对程序来说是无关紧要的。但是,向您的程序提供句柄使Windows模块儿知道如何利用它来使用相对应的对象。
HINSTANCE 执行实体(程序自身)句柄
HWND 视窗句柄
HDC 设备内容句柄
HICON 图示句柄
HCURSOR 滑鼠游标句柄
HBRUSH 画刷句柄
四、匈牙利表示法
字首
代表类型
msg
MSG型态的结构
wndclass
WNDCLASSEX
rect
RECT
ps
PAINTSTRUCT
c
char或WCHAR或TCHAR
by
BYTE (无正负号字符)
n
Short
i
Int
x, y
int分别用作x坐标和y坐标
cx, cy
int分别用作x长度和y长度;C代表「计数器」
b或f
BOOL (int);f代表「旗标」
w
WORD (无正负号短整数)
l
LONG (长整数)
dw
DWORD (无正负号长整数)
fn
function(函数)
s
string(字串)
sz
以0结尾的字串
h
句柄
p
lp指针
16位windows遗留下来的产物,现与p相同
五、注册视窗类型
视窗依照某一视窗类型建立,视窗类型用以标识处理视窗消息的视窗消息处理程序。在为程序建立视窗之前,必须首先调用RegisterClass注册一个视窗类型。该函数只需要一个参数,即一个指向类型为WNDCLASS的结构指针。WNDCLASS的两种形式如下:
1、ASCII版的WNDCLASSA:typedef struct tagWNDCLASSA { UINT style ; WNDPROC lpfnWndProc ; int cbClsExtra ; int cbWndExtra ; HINSTANCE hInstance ; HICON hIcon ; HCURSOR hCursor ; HBRUSH hbrBackground ; LPCSTR lpszMenuName ; LPCSTR lpszClassName ; } WNDCLASSA, * PWNDCLASSA, NEAR * NPWNDCLASSA, FAR * LPWNDCLASSA ;
2、Unicode版的结构定义如下: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;
二者唯一的区别在于最后两个栏位定义为指向宽字串常数,而不是指向ASCII字串常数。以后程序里依据ifdef UNICODE来
判断使用那个版本的结构。WNDCLASS结构的第二个栏位由以下叙述进行初始化:
wndclass.lpfnWndProc = WndProc;
这条叙述将这个视窗类型的视窗消息处理程序设定为WndProc。
下一个栏位就是程序的执行实体句柄(它也是WinMain的参数之一):
wndclass.hInstance = hInstance ;
最后,必须给出一个类别名称。对于小程序,类别名称可以与程序名相同,即存放在szAppName变量中的
「HelloWin」字符串。wndclass.lpszClassName = szAppName ;
接下来,HELLOWIN调用RegisterClass来注册这个视窗类型。还有,一个老经验是:在一些Windows范例程序中,您可能在WinMain中看到以下程序代码:
if (!hPrevInstance) { wndclass.cbStyle = CS_HREDRAW | CS_VREDRAW ; 初始化其他 wndclass RegisterClass (&wndclass) ; }
这是出于「旧习难改」的原因。在16位元的Windows中,如果您启动正在执行的程式的一个新执行实体,WinMain的hPrevInstance
参数将是前一个执行实体的执行实体代号。为节省记忆体,两个或多个执行实体就可能会共用相同的视窗类别。这样,视窗类别就只
在hPrevInstance是NULL的时候才注册,这表明程式没有其他执行实体。在32位的Windows中,hPrevInstance总是NULL。此程序代码会正常执行,而实际上也没必要检查hPrevInstance。
六、建立视窗
视窗类型定义了视窗的一般特征,因此可以使用同一视窗类型建立许多不同的视窗。实际调用CreateWindow建立视窗时,
可能指定有关视窗的更详细的信息。Windows程序设计新手有时会混淆视窗类型和视窗之间的区别,以及为什么一个视窗的所有特征不能被一次设定好。
实际上,以这种方式分开这些样式信息是非常方便的。例如,所有的按钮视窗都可以依据同样的视窗类型来建立,与这个
视窗类型相关的视窗消息处理程序位于Windows内部。由视窗类型来负责处理按钮的键盘和鼠标输入,并定义按钮在屏幕
上的外观形状。从这一点看来,所有的按钮都是以同样的方式工作的。但是并非所有的按钮都是一样的。它们可以有不同
的大小,不同的屏幕位置,以及不同的字符串。后面的这样一些特征是视窗定义的一部分,而不是视窗类别定义的。传递给RegisterClass函数的信息会在一个结构中设定好,而传递给CreateWindow函数的信息会在函数单独的参数中
设定好。下面是HELLOWIN.C中的CreateWindows调用,每一个栏位都做了完整的说明:hwnd = CreateWindow (szAppName, // window class name TEXT ( "The Hello Program"), // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL) ; // creation parameters
CreateWindow返回值是视窗的句柄,该句柄存放在变量hwnd中,后者被定义为HWND类型。Windows中的每个
视窗都有一个句柄,程序用句柄使用视窗。许多Windows函数需要使用hwnd作为参数,这样,Windows才能知道
函数是针对哪个视窗的。如果一个程序建立了许多视窗,则每个视窗均有一个代号。视窗句柄是Windows程序所
处理最重要的句柄之一。七、显示视窗
在CreateWindow调用传回后,Windows内部已经建立了这个视窗。这就是说,Windows已经配置了一内存,用来
保存在CreateWindow调用中指定视窗的全部信息跟一些其他信息,而Windows稍后就是依据视窗句柄到这些消息
的。然而,光是这样子,视窗并不会出现在显示器上。您还需要两个函数调用,一个是:ShowWindow (hwnd, iCmdShow) ;
第一个参数是刚刚用CreateWindow建立的视窗句柄。第二个参数是作为参数传给WinMain的iCmdShow。它
确定最初如何在屏幕上显示视窗,是一般大小、最小化还是最大化。
视窗按一般大小显示 SW_SHOWNORMAL
视窗是最大化显示的 SW_SHOWMAXIMIZED
视窗只显示在工作列上 SW_SHOWMINNOACTIVEShowWindow函数在显示器上显示视窗。如果ShowWindow的第二个参数是SW_SHOWNORMAL,则视窗的显示
区域就会被视窗类别中定义的背景画刷所覆盖。函数调用UpdateWindow (hwnd) ;
会重画显示区域。它经由发送给视窗消息处理程序(即HELLOWIN.C中的WndProc函数)一个WM_PAINT消息做到
这一点。后面,我们将说明WndProc如何处理这个消息。八、消息循环
调用UpdateWindow之后,视窗就出现在显示器上。程序现在必须准备读入使用者用键盘和鼠标输入的信息。
Windows为当前执行的每个Windows程序维护一个「消息队列」。在发生输入事件之后,Windows将事件转换为
一个「消息」并将消息放入程式的消息队列中。程序通过执行一块称之为「消息循环」的程序码从消息队列中取出
消息:while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; }
msg变量是类型为MSG的结构,类型MSG在WINUSER.H中定义如下:
typedef struct tagMSG { HWND hwnd ; UINT message ; WPARAM wParam ; LPARAM lParam ; DWORD time ; POINT pt ; } MSG, * PMSG ;
消息循环以GetMessage调用开始,它从消息队列中取出一个讯息:
GetMessage (&msg, NULL, 0, 0)
这一调用传给Windows一个指针,指向名为msg的MSG结构。第二、第三和第四个参数设定为NULL或者0,表示程序接收它
自己建立的所有视窗的所有消息。Windows用从消息队列中取出的下一个消息来填充消息结构的各个栏位,结构的各个栏位
包括:- hwnd 接收消息的视窗代号。在HELLOWIN程序中,这一参数与CreateWindow返回的hwnd值相同,因为这是该程数
拥有的唯一视窗。
- message 消息识别字。这是一个数值,用以标识消息。对于每个消息,均有一个对应的识别字,这些识别字定义于
Windows头文件(其中大多数在WINUSER.H中),以字首WM(「window message」,视窗消息)开头。例如,使用者将
鼠标放在HELLOWIN显示区域之内,并按下滑鼠左按钮,Windows就在消息队列中放入一个消息,该消息的message栏位
等于WM_LBUTTONDOWN。这是一个常数,其值为0x0201。- wParam 一个32位的「message parameter(消息参数)」,其含义和数值根据消息的不同而不同。
- lParam 一个32位的消息参数,其值与消息有关。
- time 消息放入消息列中的时间。
- pt 消息放入消息队列时的鼠标坐标。
只要从消息队列中取出消息的message栏位不为WM_QUIT(其值为0x0012),GetMessage就传回一个非零值。
WM_QUIT消息将导致GetMessage传回0。叙述
TranslateMessage (&msg) ;
将msg结构传给Windows,进行一些键盘转换。
叙述DispatchMessage (&msg) ;
又将msg结构回传给Windows。然后,Windows将该消息发送给适当的视窗消息处理程序,让它进行处理。这也就是说,
Windows将调用视窗消息处理程序。在HELLOWIN中,这个视窗讯息处理程序就是WndProe函数。处理完消息之后,
WndProc返回到Windows。此时,Windows还停留在DispatchMessage调用中。在结束DispatchMessage调用的处理之后,
Windows回到HELLOWIN,并且接着从下一个GetMessage调用开始消息循环。九、视窗消息息处理过程
视窗消息处理程序接收的第一个消息-也是WndProc选择处理的第一个消息-是WM_CREATE。当Windows
在WinMain中处理CreateWindow函数时,WndProc接收这个消息。就是说,在HELLOWIN调用CreateWindow时,
Windows将做一些它必须做的工作。在这些工作中,Windows调用WndProc,将第一个参数设定为视窗句柄,
第二个参数设定为WM_CREATE。WndProc处理WM_CREATE消息并将控制传回给Windows。 Windows然后可以
从CreateWindow调用返回到HELLOWIN中,继续在WinMain中进行下一步的处理。通常,视窗消息处理程序在
WM_CREATE处理期间进行一次视窗初始化。HELLOWIN对这个消息的处理中播放一个名为HELLOWIN.WAV的音频
文件。它使用简单的PlaySound函数来做到这一点。在这个例子中,我指定第一个参数是一个文件名,并且非同步
地播放声音,即PlaySound函数调用在音频文件开始播放时立即返回,而不会等待它的完成。在这种方法下,程序
能够继续初始化。WndProc通过从视窗消息处理程序中返回0,结束了整个WM_CREATE的处理。
WM_DESTROY消息是另一个重要消息。这一个消息指示,Windows正在根据使用者的指示关闭视窗。该消息是
使用者单击Close按钮或者在程序的系统功能表上选择 Close时发生的。
HELLOWIN通过调用PostQuitMessage以标准方式回应WM_DESTROY消息:PostQuitMessage (0) ;
该函数在程序的消息队列中插入一个WM_QUIT消息。前面提到过,GetMessage对于除了WM_QUIT之外的从消息队
列中取出的所有消息都返回非0值。而当GetMessage得到一个WM_QUIT消息时,它返回0。这将导致WinMain退出消
息循环,并终止程序。然后程序执行下面的叙述:return msg.wParam ;
结构的wParam栏位是传递给PostQuitMessage函数的值(通常是0)。然后return叙述将退出WinMain并终止程式。
十、序列化消息与非序列化消息
我们已经谈到过,Windows给视窗发送消息,这意味著Windows调用视窗消息处理程序。但是,Windows程序
也有一个消息循环,它调用GetMessage从消息队列中取出消息,并且调用DispatchMessage将消息发送给视窗消息
处理程序。那么,Windows程序是依次等待消息(类似于普通程序中相同的键盘输入),然后将消息送到某地方去的吗?
或者,它是直接从程序外面接收消息的吗?实际上,两种情况都存在。消息能够被分为「序列化的」和「非序列化的」。序列化的消息是由Windows放入程序消息队列中的。在程序的
消息循环中,重新传回并分配给视窗消息处理程序。非序列化的消息在Windows调用视窗时直接送给视窗消息处理程序。
也就是说,序列化的消息被「发送」给消息队列,而非序列化的消息则「发送」给视窗消息处理程序。任何情况下,视窗
消息处理程序都将获得视窗所有的消息--包括序列化的和非序列化的。视窗消息处理程序是视窗的「消息中心」。序列化消息基本上是使用者输入的结果,以击键(如WM_KEYDOWN和WM_KEYUP消息)、击键产生的字符
(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标按钮(WM_LBUTTONDOWN)的形式给出。序列化消息还包含时钟
消息(WM_TIMER)、更新消息(WM_PAINT)和退出消息(WM_QUIT)。非序列化消息则是其他消息。在许多情况下,非序列化消息来自调用特定的Windows函数。例如,当WinMain调用
CreateWindow时,Windows将建立视窗并在处理中给视窗讯息处理程序发送一个WM_CREATE消息。当WinMain调用
ShowWindow时,Windows将给视窗消息处理程序发送WM_SIZE和WM_SHOWWINDOW消息。当WinMain调用
UpdateWindow时,Windows将给视窗消息处理程序发送WM_PAINT消息。键盘或滑鼠输入时发出的伫列化讯息信号,
也能在非序列化消息中出现。例如,用键盘或鼠标选择了一个功能表项时,键盘或滑鼠消息就是伫列化的,而说明功能表
项已选中的WM_COMMAND消息则可能就是非序列化的。这一过程显然很复杂,但幸运的是,其中的大部分是由Windows解决的,不关我们的程序的事。从视窗消息处理程序的
角度来看,这些消息是以一种有序的、同步的方式进出的。视窗消息处理程序可以处理它们,也可以不处理。当我说消息是
以一种有序的同步的方式进出时,我是说首先消息与硬件的中断不同。在一个视窗消息处理程序中处理消息时,程序不会被
其他消息突然中断。虽然Windows程序可以多执行绪执行,但每个执行绪的消息队列只为视窗消息处理程序在该执行绪中执行的视窗处理消息。
换句话说,消息循环和视窗消息处理程序不是并发执行的。当一个消息循环从其消息队列中接收一个消息,然后调用
DispatchMessage将消息发送给视窗消息处理程序时,直到视窗消息处理程序将控制传回给Windows,DispatchMessage才
能结束执行。当然,视窗消息处理程序能调用给视窗消息处理程序发送另一个消息的函数。这时,视窗消息处理程序必须在
函数调用返回之前完成对第二个消息的处理。那时视窗消息处理程序将处理最初的消息。例如,当视窗程序调用
UpdateWindow时,Windows将调用视窗消息处理程式来处理WM_PAINT消息。视窗消息处理程序处理WM_PAINT消息结束
以后,UpdateWindow调用将把控制返回给视窗消息处理程序。这也就是说视窗消息处理程序必须是可重入。在大多数情况下,这不会带来问题,但是程序写作者应该意识到这一点。例如,
假设您在视窗消息处理程序中处理一个消息时设置了一个静态变量,然后调用了一个Windows函数。在这个函数返回时,您
还能保证那个变量 值还是原来那个吗?难说--很可能您调用的Windows函数产生了另外一个消息,并且视窗消息处理程序在
处理这个消息时改变了该变量的值。这也是在编译Windows程序时,有些编译最佳化选项必须关闭的原因之一。在许多情况下,视窗消息处理程序须保存它从消息中取得的信息,并在处理另一个消息时使用这些信息。这些信息可以
储存在视窗的静态(static)变量或全局变量中。
(OVER!!!)