今天和大家说下如何创建一个带有窗口的程序,注意窗口的程序是以后写Windows程序的基础,今天的程序很重要,一定要看懂再去看以后的Windows程序
#include <windows.h>
/*
1.窗口消息处理程序WndProc
2.在32位的Windows中,WPARAM被定义为一个UINT,
而LPARAM被定义为一个LONG(这就是C中的long整数型态),
因此窗口消息处理程序的这两个参数都是32位的值
3.WndProc函数传回一个型态为LRESULT的值,
该值简单地被定义为一个LONG。
WinMain函数被指定了一个WINAPI型态(在表头文件中定义的所有Windows函数都被指定这种型态),
而WndProc函数被指定一个CALLBACK型态。
这两个标识符都被定义为_stdcall,
表示在Windows本身和使用者的应用程序之间发生的函数呼叫的呼叫参数传递方式
*/
LRESULT CALLBACK WndProc(Hwnd,UINT,WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{
static TCHAR szAppName[]=TEXT("Hello Window");
HWND hwnd; //窗口句柄
MSG msg; //消息结构
/*
MSG 的结构,查看的MSDN文档
typedef struct tagMSG { // msg
HWND hwnd; //决定消息是发给哪个窗口的,句柄用于标识窗口
UINT message; //定义消息的编号
WPARAM wParam; //由消息自身决定
LPARAM lParam; //由消息自身决定
DWORD time; //决定消息发送的时间
POINT pt; //记录消息发送时鼠标的位置
} MSG;
*/
WNDCLASS wndclass; //窗口类别结构
/*
WNDCLASS的结构,参考MSDN文档
typedef struct _WNDCLASSEX {
UINT cbSize; //决定WNDCLASS的大小,便于系统获得结构
UINT style; //决定样式,样式可以用|符号,复合多种
WNDPROC lpfnWndProc; //指向窗口处理函数的指针
int cbClsExtra; //
int cbWndExtra;
HANDLE hInstance;
HICON hIcon; //窗口的ICON句柄
HCURSOR hCursor; //窗口的HCURSOR句柄
HBRUSH hbrBackground; //窗口的背景画刷句柄
LPCTSTR lpszMenuName; //窗口的菜单字符串的指针
LPCTSTR lpszClassName; //窗口类的指针
HICON hIconSm; //窗口最小化时的ICON句柄
} WNDCLASSEX;
*/
/*
由于每个标识符都可以在一个复合值中设置一个位的值,
所以按这种方式定义的标识符通常称为「位旗标」。
通常我们只使用少数的窗口类别样式。
HELLOWIN中用到的这两个标识符表示
,所有依据此类别建立的窗口,
每当窗口的水平方向大小(CS_HREDRAW)或者垂直方向大小(CS_VREDRAW)改变之后,
窗口要完全重画。改变HELLOWIN的窗口大小,可以看到字符串仍然显示在窗口的中央,
这两个标识符确保了这一点。
不久我们就将看到窗口消息处理程序是如何得知这种窗口大小的变化的。
*/
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
/*
这条叙述将这个窗口类别的窗口消息处理程序设定为WndProc,
即HELLOWIN.C中的第二个函数。
这个过程将处理依据这个窗口类别建立的所有窗口的全部消息。
在C语言中,像这样在结构中使用函数名时,真正提供的是指向函数的指针
*/
wndclass.lpfnWndProc = WndProc;
//下面两个字段用于在窗口类别结构和Windows内部保存的窗口结构中预留一些额外空间
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
//下一个字段就是程序的执行实体句柄(它也是WinMain的参数之一)
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION); //图标句柄
wndclass.hCursor = LoadCursor(NULL,IDC_ARROW); //鼠标光标句柄
/*
下一个字段指定依据这个类别建立的窗口背景颜色。
hbrBackground字段名称中的hbr前缀代表「handle to a brush(画刷句柄)」。
画刷是个绘图词汇,指用来填充一个区域的着色样式。
Windows有几个标准画刷,也称为「备用(stock)」画刷。
这里所示的GetStockObject呼叫将传回一个白色画刷的句柄
*/
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //画刷句柄
//下一个字段指定窗口类别菜单。HElLOWIN没有应用程序菜单,所以该字段被设定为NULL
wndclass.lpszMenuName=NULL;
//最后,必须给出一个类别名称。对于小程序,类别名称可以与程序名相同,即存放在szAppName变量中的「HelloWin」字符串
wndclass.lpszClassName=szAppName;
/*
窗口依照某一窗口类别建立,窗口类别用以标识处理窗口消息的窗口消息处理程序。
不同窗口可以依照同一种窗口类别建立。例如,Windows中的所有按钮窗口-包括按键、复选框,以及单选按钮-都是依据同一种窗口类别建立的。
窗口类别定义了窗口消息处理程序和依据此类别建立的窗口的其它特征。在建立窗口时,要定义一些该窗口所独有的特征。
在为程序建立窗口之前,必须首先呼叫RegisterClass注册一个窗口类别。该函数只需要一个参数,即一个指向型态为WNDCLASS的结构指针。
此结构包括两个指向字符串的字段
*/
if(!RegisterClass(&wndclass))
{
MessageBox(NULL,TEXT("This Programme requires Windows NT!!"),szAppName, MB_ICONERROR);
return 0;
}
/*
窗口类别定义了窗口的一般特征,因此可以使用同一窗口类别建立许多不同的窗口。
实际呼叫CreateWindow建立窗口时,可能指定有关窗口的更详细的信息。
Windows程序设计新手有时会混淆窗口类别和窗口之间的区别,
以及为什么一个窗口的所有特征不能被一次设定好。
实际上,以这种方式分开这些样式信息是非常方便的。
例如,所有的按钮窗口都可以依据同样的窗口类别来建立,与这个窗口类别相关的窗口消息处理程序位于Windows内部。
由窗口类别来负责处理按钮的键盘和鼠标输入,并定义按钮在屏幕上的外观形象。从这一点看来,所有的按钮都是以同样的方式工作的。
但是并非所有的按钮都是一样的。它们可以有不同的大小,不同的屏幕位置,以及不同的字符串。
后面的这样一些特征是窗口定义的一部分,而不是窗口类别定义的
*/
/*
传递给RegisterClass函数的信息会在一个数据结构中设定好,
而传递给CreateWindow函数的信息会在函数单独的参数中设定好。
下面是HELLOWIN.C中的CreateWindows呼叫,
每一个字段都做了完整的说明
*/
hwnd=CreateWindow(szAppName, //窗口类名称
TEXT("The Hello Programme"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口样式
CW_USEDEFAULT, //窗口左上角x坐标
CW_USEDEFAULT, //窗口左上角y坐标
CW_USEDEFAULT, //x方向窗口的大小
CW_USEDEFAULT, //y方向窗口的大小
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //执行体(自身)句柄
NULL);
/*
1.在CreateWindow呼叫传回之后,
Windows内部已经建立了这个窗口。
这就是说,Windows已经配置了一块内存,
用来保存在CreateWindow呼叫中指定窗口的全部信息跟一些其它信息,
而Windows稍后就是依据窗口句柄找到这些信息的
2.第一个参数是刚刚用CreateWindow建立的窗口句柄。
第二个参数是作为参数传给WinMain的iCmdShow。
它确定最初如何在屏幕上显示窗口,是一般大小、最小化还是最大化。
在开始菜单中安装程序时,使用者可能做出最佳选择。如果窗口按一般大小显示,那么WinMain接收到后传递给ShowWindow的就是SW_SHOWNORMAL﹔
如果窗口是最大化显示的,则为SW_SHOWMAXIMIZED。而如果窗口只显示在工作列上,则是SW_SHOWMINNOACTIVE。
ShowWindow函数在显示器上显示窗口。
如果ShowWindow的第二个参数是SW_SHOWNORMAL,则窗口的显示区域就会被窗口类别中定义的背景画刷所覆盖。
函数呼叫
3.会重画显示区域。它经由发送给窗口消息处理程序(即HELLOWIN.C中的WndProc函数)一个WM_PAINT消息做到这一点。
后面,我们将说明WndProc如何处理这个消息
*/
ShowWindow(hwnd,iCmdShow);
UpdateWindow(hwnd);
/*
1.消息循环以GetMessage呼叫开始,它从消息队列中取出一个消息
2.这一呼叫传给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
*/
while(GetMessage(&msg,NULL,0,0))
{
//将msg结构传给Windows,进行一些键盘转换
TranslateMessage(&msg);
//又将msg结构回传给Windows
DispatchMessage(&msg);
}
return msg.wParam;
}
/*
1.以上我们所讨论的都是必要的负担:注册窗口类别,建立窗口,然后在屏幕上显示窗口,程序进入消息循环,然后不断从消息队列中取出消息来处理。
实际的动作发生在窗口消息处理程序中。窗口消息处理程序确定了在窗口的显示区域中显示些什么以及窗口怎样响应使用者输入。
在HELLOWIN中,窗口消息处理程序是命名为WndProc的函数。窗口消息处理程序可任意命名(只要求不和其它名字发生冲突)。
一个Windows程序可以包含多个窗口消息处理程序。一个窗口消息处理程序总是与呼叫RegisterClass注册的特定窗口类别相关联。
CreateWindow函数根据特定窗口类别建立一个窗口。但依据一个窗口类别,可以建立多个窗口
2.窗口消息处理程序的四个参数与MSG结构的前四个字段是相同的。第一个参数hwnd是接收消息的窗口的句柄,它与CreateWindow函数的传回值相同。
对于与HELLOWIN相似的程序(只建立一个窗口),这个参数是程序所知道的唯一窗口句柄。
如果程序是依据同一窗口类别(同时也是同一窗口消息处理程序)建立多个窗口,则hwnd标识接收消息的特定窗口。
第二个参数与MSG结构中的message字段相同,它是标识消息的数值。
最后两个参数都是32位的消息参数,提供关于消息的更多信息。
这些参数包含每个消息型态的详细信息。有时消息参数是两个存放在一起的16位值,而有时消息参数又是一个指向字符串或数据结构的指针。
程序通常不直接呼叫窗口消息处理程序,窗口消息处理程序通常由Windows本身呼叫。
通过呼叫SendMessage函数,程序能够直接呼叫它自己的窗口消息处理程序。我们将在后面的章节讨论SendMessage函数
*/
LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam, LPARAM lParam)
{
HDC hdc; //设备内容句柄
PAINTSTRUCT ps; //绘图结构
/*
PAINTSTRUCT 结构的定义
typedef struct tagPAINTSTRUCT {
HDC hdc; //
标识显式上下文
BOOL fErase; //
背景是否重画
RECT rcPaint; //
涂色矩形, 是BeginPaint返回的设备上下文句柄,有了从//BeginPaint获取的设备上下文句柄,就可以也只能在ps指出的//rcPaint的矩形内绘图,EndPaint调用使这一区域有效
BOOL fRestore; //系统保留的变量
BOOL fIncUpdate; //系统保留的变量
BYTE rgbReserved[16]; //系统保留的变量
} PAINTSTRUCT;
*/
RECT rect; //矩形结构
switch(message)
{
/*
窗口消息处理程序接收的第一个消息-也是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函数来做到这一点。
该函数说明在/Platform SDK/Graphics and Multimedia Services/Multimedia Audio/Waveform Audio中,
而文件在/Platform SDK/Graphics and Multimedia Services/Multimedia Reference/Multimedia Functions中。
PlaySound的第一个参数是声音文件的名称(它也可能是在Control Panel的Sounds中定义的一种声音的别名,或者是一个程序资源)。
第二个参数只有当声音文件是一种资源时才被使用。第三个参数指定一些选项。在这个例子中,我指定第一个参数是一个文件名,
并且异步地播放声音,即PlaySound函数呼叫在声音文件开始播放时立即传回,而不会等待它的完成。
在这种方法下,程序能够继续初始化。
WndProc通过从窗口消息处理程序中传回0,结束了整个WM_CREATE的处理
*/
case WM_CREATE:
PlaySound(TEXT("Windows XP 启动"),NULL,SND_FILENAME | SND_ASYNC);
return 0;
/*
1.WndProc处理的第二个消息为WM_PAINT。这个消息在Windows程序设计中是很重要的。
当窗口显示区域的一部分显示内容或者全部变为「无效」,以致于必须「更新画面」时,将由这个消息通知程序。
显示区域的显示内容怎么会变得无效呢?在最初建立窗口的时候,整个显示区域都是无效的,因为程序还没有在窗口上画什么东西。
第一条WM_PAINT消息(通常发生在WinMain中呼叫UpdateWindow时)指示窗口消息处理程序在显示区域上画一些东西。
在使用者改变HELLOWIN窗口的大小后,显示区域的显示内容重新变得无效。
读者应该还记得,HELLOWIN中wndclass结构的style字段设定为标志CS_HREDRAW和CS_VREDRAW,这样的格式设定指示Windows,
在窗口大小改变后,就把整个窗口显示内容当成无效。
然后,窗口消息处理程序将收到一条WM_PAINT消息。
当使用者将HELLOWIN最小化,然后再次将窗口恢复为以前的大小时,Windows将不会保存显示区域的内容。
在图形环境下,窗口显示区域涉及的数据量很大。因此,Windows令窗口无效,窗口消息处理程序接收一条WM_PAINT消息,
并自动恢复其窗口的内容。
在移动窗口以致其相互重迭时,Windows不保存一个窗口中被另一个窗口所遮盖的内容。
在这一部分不再被遮盖之后,它就被标志为无效。
窗口消息处理程序接收到一条WM_PAINT消息,以更新窗口的内容。
对WM_PAINT的处理几乎总是从一个BeginPaint呼叫开始
2.在这两个呼叫中,第一个参数都是程序的窗口句柄,第二个参数是指向型态为PAINTSTRUCT的结构指针。
PAINTSTRUCT结构中包含一些窗口消息处理程序,可以用来更新显示区域的内容。
我们将在下一章中讨论该结构的各个字段。现在我们只在BeginPaint和EndPaint函数中用到它。
在BeginPaint呼叫中,如果显示区域的背景还未被删除,则由Windows来删除。
它使用注册窗口类别的WNDCLASS结构的hbrBackground字段中指定的画刷来删除背景。
在HELLOWIN中,这是一个白色备用画刷。
这意味着,Windows将通过把窗口背景设定为白色来删除窗口背景。
BeginPaint呼叫令整个显示区域有效,并传回一个「设备内容句柄」。
设备内容是指实体输出设备(如视讯显示器)及其设备驱动程序。
在窗口的显示区域显示文字和图形需要设备内容句柄。
但是从BeginPaint传回的设备内容句柄不能在显示区域之外绘图,读者可以试一试。EndPaint释放设备内容句柄,使之不再有效。
如果窗口消息处理程序不处理WM_PAINT消息(这是很罕见的),它们必须被传送给DefWindowProc。
DefWindowProc只是依次呼叫BeginPaint和EndPaint,以使显示区域有效。
呼叫完BeginPaint之后,WndProc接着呼叫GetClientRect
3.DrawText可以输出文字(正如其名字所表明的一样)。由于该函数要输出文字,
第一个参数是从BeginPaint传回的设备内容句柄,第二个参数是要输出的文字,第三个参数是 -1,指示字符串是以字节0终结的。
DrawText最后一个参数是一系列位旗标,它们均在WINUSER.H中定义(虽然由于其显示输出的效果,使得DrawText像一个GDI函数呼叫,但它确实因为相当高级的画图功能而成为User模块的一部分。
此函数在/Platform SDK/Graphics and Multimedia Services/GDI/Fonts and Text中说明)。旗标指示了文字必须显示在一行上,水平方向和垂直方向都位于第四个参数指定的矩形中央。
因此,这个函数呼叫将让字符串「Hello, Windows 98!」显示在显示区域的中央。
一旦显示区域变得无效(正如在改变大小时所发生的情况一样),
WndProc就接收到一个新的WM_PAINT消息。
WndProc通过呼叫GetClientRect取得变化后的窗口大小,并在新窗口的中央显示文字
*/
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
GetClientRect(hwnd,&rect);
DrawText(hdc,TEXT("Hello ,Windows XP!!"),-1,&rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
/*
呼叫DefWindowProc来为窗口消息处理程序不予处理的所有消息提供内定处理,这是很重要的。
不然一般动作,如终止程序,将不会正常执行。
*/
return DefWindowProc(hwnd,message,wParam,lParam);
}
/*
前缀指示该常数所属的类别
CS 窗口类别样式
CW 建立窗口
DT 绘制文字
IDI 图示ID
IDC 游标ID
MB 消息框
SND 声音
WM 窗口消息
WS 窗口样式
*/
/*
经常用到的变量前缀
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 指针
*/
/*
Windows 程序设计的难点
即使有了对HELLOWIN的说明,读者对程序的结构和原理可能仍然觉得神秘。
在为传统环境编写简单的C程序时,整个程序可能包含在main函数中。
而在HELLOWIN中,WinMain只包含了注册窗口类别,建立窗口,从消息队列中取出消息和发送消息所必须的程序代码。
程序的所有实际动作均在窗口消息处理程序中发生。
在HELLOWIN中,这些动作不多,WndProc只是简单地播放了一个声音文件并在窗口中显示一个字符串。
但是在后面的章节中,读者将发现,Windows程序所作的一切,都是响应发送给窗口消息处理程序的消息。
这是概念上的主要难点之一,在开始写作Windows程序之前,必须先搞清楚。
别呼叫我,我会呼叫您
前面我们提到过,程序写作者已经熟悉了使用操作系统呼叫的做法。例如,C程序写作者使用fopen函数打开文件。
fopen函数最终通过呼叫操作系统来打开文件,这一点问题也没有。
但是Windows不同,尽管Windows有1000个以上的函数可供程序呼叫,
但Windows也呼叫使用者程序,比如前面定义的窗口消息处理程序WndProc。
窗口消息处理程序与窗口类别相关,窗口类别是程序呼叫RegisterClass注册的。
依据该类别建立的窗口使用这个窗口消息处理程序来处理窗口的所有消息。
Windows通过呼叫窗口消息处理程序对窗口发送消息。
在第一次建立窗口时,Windows呼叫WndProc。在窗口关闭时,Windows也呼叫WndProc。
窗口改变大小、移动或者变成图示时,从菜单中选择某一项目、挪动滚动条、按下鼠标按钮或者从键盘输入字符时,
以及窗口显示区域必须被更新时,Windows都要呼叫WndProc。
所有这些WndProc呼叫都以消息的形式进行。在大多数Windows程序中,程序的主要部分都用来处理消息。
Windows可以发送给窗口消息处理程序的消息通常都以WM开头的名字标识,并且都在WINUSER.H表头文件中定义。
实际上,从程序外呼叫程序内的例程这一种做法,在传统的程序设计中并非前所未闻。
C中的signal函数可以拦截Ctrl-C中断或操作系统的其它中断。
为MS-DOS编写的老程序中经常有拦截硬件中断的程序代码。
但在Windows中,这种概念扩展为包括一切事件。窗口中发生的一切都以消息的形式传给窗口消息处理程序。
然后,窗口消息处理程序以某种方式响应这个消息,或者将消息传给DefWindowProc,进行内定处理。
在HELLOWIN中,窗口消息处理程序的wParam和lParam参数除了作为传递给DefWindowProc的参数外,不再有其它用处。
这些参数给出了关于消息的其它信息,参数的含义与具体消息相关。
让我们来看一个例子。一旦窗口的显示区域大小发生了改变,Windows就呼叫窗口的窗口消息处理程序。
窗口消息处理程序的hwnd参数是改变大小的窗口的句柄(请记住,一个窗口消息处理程序能处理依据同一个窗口类别建立的多个窗口的消息。
参数hwnd让窗口消息处理程序知道是哪个窗口在接收消息)。
参数message是WM_SIZE。消息WM_SIZE的参数wParam的值是SIZE_RESTORED、SIZE_MINIMIZED、SIZE_MAXIMIZED、SIZE_MAXSHOW或SIZE_MAXHIDE (在WINUSER.H表头文件中分别定义为数字0到4)。
也就是说,参数wParam表明窗口是非最小化还是非最大化,是最小化、最大化,还是隐藏。
lParam参数包含了新窗口的大小,新宽度和新高度均为16位值,合在一起成为32位的lParam。WINDEF.H中提供了帮助程序写作者从lParam中取出这两个值的宏,我们将在下一章说明这个宏。
有时候,DefWindowProc处理完消息后会产生其它的消息。例如,假设使用者执行HELLOWIN,并且使用者最终单击了 Close按钮,
或者假设用键盘或鼠标从系统菜单中选择了 Close, DefWindowProc处理这一键盘或者鼠标输入,在检测到使用者选择了Close选项之后,
它给窗口消息处理程序发送一条WM_SYSCOMMAND消息。
WndProc将这个消息传给DefWindowProc。DefWindowProc给窗口消息处理程序发送一条WM_CLOSE消息来响应之。
WndProc再次将它传给DefWindowProc。DestroyWindow呼叫DestroyWindow来响应这条WM_CLOSE消息。
DestroyWindow导致Windows给窗口消息处理程序发送一条WM_DESTROY消息。
WndProc再呼叫PostQuitMessage,将一条WM_QUIT消息放入消息队列中,以此来响应此消息。
这个消息导致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)变量或整体变量中。
当然,读者将在下面几章对此有一个更清楚的了解,因为窗口消息处理程序将处理更多的消息。
行动迅速
Windows 98和Windows NT都是优先权式的多任务环境。这意味着当一个程序在进行一项长时间工作时,
Windows可以允许使用者将控制切换到另一个程序中。这是一件好事,也是现在的Windows优越于以前16位Windows的地方。
然而,由于Windows设计的方式,这种优先权式多任务并不总是以您希望的样子工作。
例如,假设您的程序花费一分钟左右来处理某一个消息。是的,使用者可以将控制切换到另一个程序,但是却无法对您的程序进行任何动作。
使用者无法移动您的程序窗口、缩放它、最小化、关闭它、什么都不能做。这是因为您的窗口消息处理程序正忙于进行一项长时间的作业。
表面上并不是窗口消息处理程序在执行它自己的移动和缩放操作,但实际上确实是它在做。这就是DefWindowProc部分的工作,它必须被考虑为您的窗口消息处理程序的一部分。
如果您的程序在处理某些消息时需要长时间的作业的话,可以选择我在第二十章里描述的那些方法来做得更有优雅一些。即使是在优先权式多任务环境中,也不应该让您的程序呆在屏幕上一动不动。
这会让使用者讨厌的,他们会认为您的程序中有bug、不标准的动作,说明文件没写好。最好让使用者觉得程序只停了一下子就把全部消息中快速料理完了
*/