第二章 Windows窗口设计.docx
《第二章 Windows窗口设计.docx》由会员分享,可在线阅读,更多相关《第二章 Windows窗口设计.docx(20页珍藏版)》请在冰豆网上搜索。
第二章Windows窗口设计
第二章:
WIN32窗体程序设计
学习目标
⏹Win32编程中需要掌握的一些重要概念
⏹详解Win32程序
⏹理解Win32程序的消息机制
⏹Win32程序对鼠标和键盘的控制
Win32编程中需要掌握的一些重要概念
学习windows编程是一个比较难的过程,主要的障碍是因为windows程序中很多新的东西,我们一下子不能适应,下面我们就来扫清这些障碍:
⏹我们在上一章中看到了一个简单的windows程序,大家会发现windows程序中有很多全大写定义的东西,这就是我们的障碍之一,因为,太多的东西我们以前没有见过,windows程序中全大写的东西可以分为这样三种:
第一种是:
windows定义的变量
比如这句:
LRESULTCALLBACKWinProc(HWNDhWnd,UINTmessage,WPARAMwParam,LPARAMlParam)
这是一个方法的定义,这和我们以前的程序就有太多的不同。
下面的列表是从相应windows的头文件中摘下来的,看完这个后,你是否可以理解上面这句话的意思。
#defineFARfar
#defineNEARnear
#defineCONSTconst
#defineCALLBACK__stdcall
#defineWINAPI__stdcall
#defineWINAPIV__cdecl
#defineAPIENTRYWINAPI
#defineAPIPRIVATE__stdcall
#definePASCAL__stdcall
typedefunsignedlongDWORD;
typedefintBOOL;
typedefunsignedcharBYTE;
typedefunsignedshortWORD;
typedeffloatFLOAT;
typedefFLOAT*PFLOAT;
typedefBOOLnear*PBOOL;
typedefBOOLfar*LPBOOL;
typedefBYTEnear*PBYTE;
typedefBYTEfar*LPBYTE;
typedefintnear*PINT;
typedefintfar*LPINT;
typedefWORDnear*PWORD;
typedefWORDfar*LPWORD;
typedeflongfar*LPLONG;
typedefDWORDnear*PDWORD;
typedefDWORDfar*LPDWORD;
typedefvoidfar*LPVOID;
typedefCONSTvoidfar*LPCVOID;
typedefintINT;
typedefunsignedintUINT;
typedefunsignedint*PUINT;
typedefUINTWPARAM;
typedefLONGLPARAM;
typedefLONGLRESULT;
其实,windows程序把很多变量进行了重新定义,还记得typedef这个类型定义关键字吧!
其实上面的UINT、WPARAM、LPARAM,其实都是unsignedint(无符号整数),其实HWND也是无符号整数,后面我们详细介绍HWND,今后,如果大家见到了一个类型是全大写的,那么它一定是在windows的头文件中重新定义了。
还有就是:
LRESULTCALLBACK,从上面的表中,大家可以看到他们是返回类型和宏定义,其中,LRESULT是long,而CALLBACK是函数的调用方式__stdcall。
第二种是windows的宏定义:
在前面,大家看到了__stdcall是一个宏定义,在windows程序中有大量的宏定义,宏定义用于这样几个地方:
◆常量
所有的消息都是常量,比如前面看到的WM_KEYDOWN、WM_PAINT等,所有的键盘标识,如VK_ESCAPE,还有就是我们在函数调用中用到的一些参数:
如WS_OVERLAPPEDWINDOW,WHITE_BRUSH、IDI_APPLICATION等都是常量
◆简单函数用宏
如:
#defineMAKEWORD(a,b)
((WORD)(((BYTE)(a))|((WORD)((BYTE)(b)))<<8))
#defineMAKELONG(a,b)
((LONG)(((WORD)(a))|((DWORD)((WORD)(b)))<<16))
#defineLOWORD(l)((WORD)(l))
#defineHIWORD(l)((WORD)(((DWORD)(l)>>16)&0xFFFF))
#defineLOBYTE(w)((BYTE)(w))
#defineHIBYTE(w)((BYTE)(((WORD)(w)>>8)&0xFF))
以上的都是简单的函数,如LOWORD是表示取一个DWORD(32位整数)的低16位,而HIWORD取高16位
◆程序结构中用到宏,今后,我们要学习的消息映射中的
BEGIN_MESSAGE_MAP、DECLARE_MESSAGE_MAP等就是在程序结构中用到的宏。
第三中是windows中的结构:
windows程序中大量使用结构,对这些结构的掌握程度,可以作为衡量一个程序员对windows程序的熟悉程度,windows程序的结构难,而且结构
中的成员非常多,是我们学习的难点。
在上一章的程序中,我们看到的结构有:
WNDCLASS、MSG等,学习中,大家应该掌握这些常用结构的常用成员的用法,我们来看看这两个结构
typedefstructtagWNDCLASSA{
UINTstyle;
WNDPROClpfnWndProc;
intcbClsExtra;
intcbWndExtra;
HINSTANCEhInstance;
HICONhIcon;
HCURSORhCursor;
HBRUSHhbrBackground;
LPCSTRlpszMenuName;
LPCSTRlpszClassName;
}WNDCLASSA;
typedefstructtagMSG{
HWNDhwnd;
UINTmessage;
WPARAMwParam;
LPARAMlParam;
DWORDtime;
POINTpt;
}MSG;
结构中成员的具体含义请看后面的说明。
⏹理解句柄
上一章的程序中,我们看到了这样的类型定义
HINSTANCE执行实体(程序自身)句柄
HWND窗口句柄
在windows程序中,以H定义开始的变量类型,这个H,我们叫HANDLE(句柄)。
handle的本意是把柄,把手的意思。
是你与操作系统打交道的东西。
举个例子:
比如你做了亏心事(我说的是比如,呵呵),不幸让我抓住了把柄,那么我让你做什么你就得做什么,因为你的把柄在我这。
我们编程的时候也是这样,比如我们要想操纵一个窗口,那我们就必须"抓住它的把柄",只有这样,我们才能改变它的属性,改变它的式样,甚至销毁它。
句柄在Windows中使用非常频繁。
句柄是一个,句柄是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数。
(通常为32位的)整数,这就和我们每个人都会有一个身份证编号,我们用这个编号区分不同的人,同样windows用不同的句柄来区分和使用各种各样的应用程序实例,窗口,控制,位图,GDI对象等,如:
HWND窗口
HINSTANCE执行程序实体
HMENU菜单
HDC图形设备
HBITMAP位图
HICON图标
HCURSOR鼠标游标
HBRUSH画刷
我们也可以把Windows中的句柄看成传统C或者MS-DOS程序设计中使用的文件句柄。
应用程序几乎总是通过调用一个WINDOWS函数来获得一个句柄,之后其他的WINDOWS函数就可以使用该句柄,以引用相应的对象。
句柄的实际值对程序来说是无关紧要的。
只是,我们要知道在程序中间怎样去使用这个句柄,以后,在很多的函数中,第一个参数就会是该对象句柄,如:
ShowWindow,UpdateWindow等函数的第一个参数都是窗口句柄。
如果想更透彻一点地认识句柄,我可以告诉大家,句柄是一种指向指针的指针。
我们知道,所谓指针是一种内存地址。
应用程序启动后,组成这个程序的各对象是住留在内存的。
如果简单地理解,似乎我们只要获知这个内存的首地址,那么就可以随时用这个地址访问对象。
但是,如果您真的这样认为,那么您就大错特错了。
我们知道,Windows是一个以虚拟内存为基础的操作系统。
在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。
对象被移动意味着它的地址变化了。
如果地址总是如此变化,我们该到哪里去找该对象呢?
为了解决这个问题,Windows操作系统为各应用程序腾出一些内存储地址,用来专门登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的。
Windows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。
这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。
这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统。
句柄地址(稳定)→记载着对象在内存中的地址————→对象在内存中的地址(不稳定)→实际对象
本质:
WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的,相反的,WINDOWSAPI给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。
但是必须注意的是程序每次从新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确不一样的。
假如我们把进入电影院看电影看成是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电影院售给我们的门票总是不同的一个座位是一样的道理。
详解Win32程序
一个典型的windows程序结构是这样的:
WinMain部分:
填充窗口结构-->注册窗口信息-->建立窗口-->显示窗口-->消息循环
消息处理部分:
是一个大的switchcase结构,根据不同的消息类型,进行不同的处理。
对于WinMain部分,一般都是固定不变的,我们不需要做太多的变动,只是,我们想改变窗口的一些特性时才需要修改,比如,我们想改变窗口的大小、背景色、图标等,我们就需要更改相应的部分,下面我们看看改动的两个主要地方:
⏹窗体结构WNDCLASS
typedefstructtagWNDCLASSA
{
UINTstyle;
WNDPROClpfnWndProc;
intcbClsExtra;
intcbWndExtra;
HINSTANCEhInstance;
HICONhIcon;
HCURSORhCursor;
HBRUSHhbrBackground;
LPCSTRlpszMenuName;
LPCSTRlpszClassName;
}
WNDCLASSA,*PWNDCLASSA,NEAR*NPWNDCLASSA,FAR*LPWNDCLASSA;
在这里提示一下资料型态和匈牙利表示法:
其中的lpfn字首代表「指向函数的长指针」。
(在Win32API中,长指针和短指针(或者近程指针)没有区别。
这只是16位Windows的遗物。
)cb字首代表「字节数」而且通常作为一个常数来表示一个字节的大小。
h字首是一个句柄,而hbr字首代表「一个画刷的句柄」。
lpsz字首代表「指向以0结尾字符串的指针」。
假如我们定义了WNDCLASSwndcls;
wndcls.style=CS_VREDRAW|CS_HREDRAW;
style窗口类型定义如下:
#defineCS_VREDRAW0x0001
#defineCS_HREDRAW0x0002
#defineCS_KEYCVTWINDOW0x0004
#defineCS_DBLCLKS0x0008
#defineCS_OWNDC0x0020
。
。
。
由于每个识别字都可以在一个复合值中设置一个位的值,所以按这种方式定义的识别字通常称为「位旗标」。
通常我们只使用少数的窗口类别样式。
上面用到的这两个识别字表示,所有依据此类别建立的窗口,每当窗口的水平方向大小(CS_HREDRAW)或者垂直方向大小(CS_VREDRAW)改变之后,窗口要完全重画。
WNDCLASS结构的第二个栏位由以下叙述进行初始化:
wndcls.lpfnWndProc=WndProc;
这条叙述将这个窗口类别的窗口消息处理程序设定为WndProc,即消息过程处理函数。
这个过程将处理依据这个窗口类别建立的所有窗口的全部消息。
在C语言中,像这样在结构中使用函数名时,真正提供的是指向函数的指针。
下面两个栏位用于在窗口类别结构和Windows内部保存的窗口结构中预留一些额外空间:
wndclass.cbClsExtra=0;
wndclass.cbWndExtra=0;
程序可以根据需要来使用预留的空间。
下一个栏位就是程序的执行实体句柄(它也是WinMain的参数之一):
wndclass.hInstance=hInstance;
wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
为所有依据这个窗口类别建立的窗口设置一个图标。
图标是一个小的点阵图图像,它对使用者代表程序,将出现在Windows工作列中和窗口的标题列的左端。
在本书的后面,您将学习如何为您的Windows程序自定义图标。
现在,为了方便起见,我们将使用预先定义的图标。
要取得预先定义图标的句柄,可以将第一个参数设定为NULL来调用LoadIcon。
在载入程序写作者自定义的图标时(图标应该存放在磁片上的.EXE程序文件中),这个参数应该被设定为程序的执行实体句柄hInstance。
第二个参数代表图标。
对于预先定义图标,此参数是以IDI开始的识别字(「ID代表图标」),识别字在WINUSER.H中定义。
IDI_APPLICATION图标是一个简单的窗口小图形。
LoadIcon函数传回该图标的句柄。
我们并不关心这个句柄的实际值,它只用于设置hIcon栏位的值。
该栏位在WNDCLASS结构中定义为HICON型态,此型态名的含义为「handletoanicon(图标句柄)」。
wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);
与前一条叙述非常相似。
LoadCursor函数载入一个预先定义的鼠标游标(命名为IDC_ARROW),并传回该游标的句柄。
该句柄被设定给WNDCLASS结构的hCursor栏位。
当鼠标游标在依据这个类别建立的窗口的显示区域上出现时,它变成一个小箭头。
下一个栏位指定依据这个类别建立的窗口背景颜色。
hbrBackground栏位名称中的hbr字首代表「handletoabrush(画刷句柄)」。
画刷是个绘图词汇,指用来填充一个区域的著色样式。
Windows有几个标准画刷,也称为「备用(stock)」画刷。
这里所示的GetStockObject调用将传回一个白色画刷的句柄:
wndclass.hbrBackground=GetStockObject(WHITE_BRUSH);
这意味著窗口显示区域的背景完全为白色,这是一种极其普遍的做法。
下一个栏位指定窗口类别菜单。
没有应用程序菜单,可以把该栏位设定为NULL:
wndclass.lpszMenuName=NULL;
最后,必须给出一个类别名称。
对于小程序,类别名称可以与程序名相同,即存放在szAppName变数中的「HelloWin」字符串。
wndclass.lpszClassName=szAppName;
⏹建立窗口中改动的地方
当窗口类注册完毕之后,并不会有窗口显示出来,因为注册的过程仅仅是为创建窗口所做的准备工作。
实际创建一个窗口的是通过调用CreateWindow()函数完成的。
窗口类中已经预先定义了窗口的一般属性,而CreateWindow()中的参数可以进一步指定一个窗口的更具体的属性,下面是调用CreateWindow()函数来创建窗口的:
hwnd=CreateWindow("EasyWin",//创建窗口所用的窗口类的名称*
"一个基本的Win32程序",//窗口标题
WS_OVERLAPPEDWINDOW,//窗口风格,定义为普通型*
100,//窗口位置的x坐标100,//窗口位置的y坐标
400,//窗口的宽度
300,//窗口的高度
NULL,//父窗口句柄
NULL,//菜单句柄
hInstance,//应用程序实例句柄*
NULL);//一般都为NULL
第一个参数是创建该窗口所使用的窗口类的名称,注意这个名称应与前面所注册的窗口类的名称一致。
第三个参数为创建的窗口的风格,下表列出了常用的窗口风格:
WS_OVERLAPPEDWINDOW创建一个层叠式窗口,有边框、标题栏、系统菜单、最大最小化按钮,是以下几种风格的集合:
WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU,WS_THICKFRAME,WS_MINIMIZEBOX,WS_MAXIMIZEBOX
WS_POPUPWINDOW创建一个弹出式窗口,是以下几种风格的集合:
WS_BORDER,WS_POPUP,WS_SYSMENU。
风格
含义
WS_OVERLAPPED
创建一个层叠式窗口,它有标题栏和边框,与WS_TILED风格一样
WS_POPUP
该窗口为弹出式窗口,不能与WS_CHILD同时使用
WS_BORDER
窗口有单线边框
WS_CAPTION
窗口有标题栏
WS_CHILD
该窗口为子窗口,不能与WS_POPUP同时使用
WS_DISABLED
该窗口为无效,即对用户操作不产生任何反应WS_HSCROLL窗口有水平滚动条
WS_ICONIC
窗口初始化为最小化
WS_MAXIMIZE
窗口初始化为最大化
WS_MAXIMIZEBOX
窗口有最大化按钮
WS_MINIMIZE
与WS_MAXIMIZE一样
WS_MINIMIZEBOX
窗口有最小化按钮
WS_SIZEBOX
边框可进行大小控制的窗口
WS_SYSMENU
创建一个有系统菜单的窗口,必须与WS_CAPTION风格同时使用
WS_THICKFRAME
创建一个大小可控制的窗口,与WS_SIZEBOX风格一样.
WS_TILED
创建一个层叠式窗口,有标题栏
WS_VISIBLE
窗口为可见
WS_VSCROLL
窗口有垂直滚动条
程序中使用了WS_OVERLAPPEDWINDOW标志,它是创建一个普通窗口常用的标志。
而在DirectX编程中,我们常用的是WS_POPUP,用这个标志创建的窗口没有标题栏和系统菜单,如果设定窗口为最大化,客户区可以占满整个屏幕,以满足DirectX编程的需要。
CreateWindow()函数后面的参数中,仍用到了该应用程序的实例句柄hInstance。
如果窗口创建成功,返回值是新窗口的句柄,否则返回NULL。
窗口创建后,并不会在屏幕上显示出来,要真正把窗口显示在屏幕上,还得使用ShowWindow()函数,其原型如下:
BOOLShowWindow(HWNDhWnd,intnCmdShow);
参数hWnd指定要显示得窗口的句柄,nCmdShow表示窗口的显示方式,这里指定为从WinMain()函数的nCmdShow所传递而来的值。
由于ShowWindow()函数的执行优先级不高,所以当系统正忙着执行其它的任务时,窗口不会立即显示出来,此时,调用UpdateWindow()函数以可以立即显示窗口。
其函数原型如下:
BOOLUpdateWindow(HWNDhWnd);
消息处理机制
windows程序中有两个消息队列,分别叫系统消息队列和窗口消息队列,操作系统的执行过程,就是不断产生消息和处理消息的过程,这里的消息又叫事件,前面介绍过windows的事件驱动的概念,核心的思想就是windows程序是通过不断处理消息来完成任务的,那么到底消息是怎么产生的,消息有是什么样子的呢?
消息的产生有几个来源:
⏹用户产生消息
比如,我们点击了窗口上的一个按钮,按下了键盘上的键等等,都会产生消息。
⏹有些消息是应用程序产生的
比如,一个窗口被最大化时,它就会产生重画的消息,我们在后面讲图形设计的时候,会详细说明
⏹操作系统会产生消息
比如,我们在程序中有一个定时器,系统隔相应的时间就会产生WM_TIMER(计时器事件)。
我们不需要关心,消息是从什么地方来的,我们只要关心消息的处理过程就可以了,下面看看消息循环AfxGetAppName获得应用程序名;
在Win32编程中,消息循环是相当重要的一个概念,看似很难,但是使用起来却是非常简单。
在WinMain()函数中,创建了应用程序主窗口之后,就要启动消息循环,其代码如下:
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Windows应用程序可以接收以各种形式输入的信息,这包括键盘、鼠标动作、记时器产生的消息,也可以是其它应用程序发来的消息等等。
Windows系统自动监控所有的输入设备,并将其消息放入该应用程序的消息队列中。
GetMessage()函数则是用来从应用程序的消息队列中按照先进先出的原则将这些消息一个个的取出来,放进一个MSG结构中去。
GetMessage()函数原型如下:
BOOLGetMessage(
LPMSGlpMsg,//指向一个MSG结构的指针,用来保存消息
HWNDhWnd,//指定哪个窗口的消息将被获取
UINTwMsgFilterMin,//指定获取的主消息值的最小值
UINTwMsgFilterMax//指定获取的主消息值的最大值
);
GetMessage()将获取的消息复制到一个MSG结构中。
如果队列中没有任何消息,GetMessage()函数将一直空闲直到队列中又有消息时再返回。
如果队列中已有消息,它将取出一个后返回。
MSG结构包含了一条Windows消息的完整信息,其定义如下:
typedefstructtagMSG{
HWNDhwnd;//接收消息的窗口句柄
UINTmessage;//主消息值
WPARAMwParam;//副消息值,其具体含义依赖于主消息值