Win32编程基础知识
Win32编程的基础知识,供那些想进行DirectX编程而又没有Win32编程基础的人学习使用。如果读者已经对Win32编程有一定的了解,并且有一定的实践经验,那么,你可以不必学习此章,返回前面,直接学习DirectX编程。Windows应用程序千变万化,令人眼花缭乱,但,消息机制和窗口过程却始终它们的基础,掌握了这两项技术,也就相当于把握住了问题的关键。DirectX编程也是建立在这个基础之上的,所以,在你可以熟练的进行简单的Win32编程之后,DirectX编程也就触手可得。C程序员或是MFC的忠实用户,只要你学习过C语言的语法,自己亲手编过一些简短的C程序,理解以下的Win32编程基础也不是一件困难的事。Win32程序C语言编程中,一个最简单的程序可以只有两行。 Windows程序却最少也要写几十行,这并不是说明Windows应用程序效率低下,难于掌握,只是说明程序在Windows环境下有更丰富的内涵。Windows程序的效率其实不低,在所有的Windows应用程序中,都有一个程序初始化的过程,这得用上几十条语句,这段初始化的代码对于任何Windows应用程序而言,都是大同小异的。下面以一个实现最简单功能的程序EasyWin为例,说明Windows程序的基本框架。Visual C++ 5.0。 File菜单的New,在出现的对话框中,选择Projects栏目(新建工程),并点取其下的Win32 Application项,表示使用Win32环境创建应用程序。先在Locatin(路径)中填入“c:/”,然后在Project Name(项目名称)中填入“EasyWin”,其它按照缺省设置,单击OK按钮。 File菜单的New,在出现的对话框中,选择Files栏目(新建文件),并点取其下的C++ Source File项,表示新建一个C++源文件。在右边的File栏中输入“EasyWin”,最后确定让Add to project检查框打上勾,单击OK按钮。 EasyWin.cpp文件中输入以下源程序代码。这个文件也可以从配套光盘上找到。 :/easywin/easywin.cpp
本章内容是关于
尽管
如果你以前是
在以前的
void main(void)
{ printf "Hello World!"; }
而要实现同样功能的
//******************************************************************* ESC键时,会弹出一个对话框以表示你的操作。Win32应用程序的框架,在以后所有的程序中,你会发现它们都是在这个程序的基础之上再添加代码。
文件路径:光盘
// 工程:easywin
// 文件:easywin.cpp
// 内容:一个基本的Win32程序
//*******************************************************************
#include <windows.h>
#include <windowsx.h>
//函数声明
BOOL InitWindow( HINSTANCE hInstance, int nCmdShow );
LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
//*******************************************************************
//函数:WinMain()
//功能:Win32应用程序入口函数。创建主窗口,处理消息循环
//*******************************************************************
int PASCAL WinMain( HINSTANCE hInstance, //当前实例句柄
HINSTANCE hPrevInstance, //前一个实例句柄
LPSTR lpCmdLine, //命令行字符
int nCmdShow) //窗口显示方式
{
MSG msg;
//创建主窗口
if ( !InitWindow( hInstance, nCmdShow ) )
return FALSE;
//进入消息循环:
//从该应用程序的消息队列中检取消息,送到消息处理过程,
//当检取到WM_QUIT消息时,退出消息循环。
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//程序结束
return msg.wParam;
}
//******************************************************************
//函数:InitWindow()
//功能:创建窗口。
//******************************************************************
static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )
{
HWND hwnd; //窗口句柄
WNDCLASS wc; //窗口类结构
//填充窗口类结构
wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = (WNDPROC)WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = "EasyWin";
//注册窗口类
RegisterClass( &wc );
//创建主窗口
hwnd = CreateWindow(
"EasyWin", //窗口类名称
"一个基本的Win32程序", //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格,定义为普通型
100, //窗口位置的x坐标
100, //窗口位置的y坐标
400, //窗口的宽度
300, //窗口的高度
NULL, //父窗口句柄
NULL, //菜单句柄
hInstance, //应用程序实例句柄
NULL ); //窗口创建数据指针
if( !hwnd ) return FALSE;
//显示并更新窗口
ShowWindow( hwnd, nCmdShow );
UpdateWindow( hwnd );
return TRUE;
}
//******************************************************************
//函数:WinProc()
//功能:处理主窗口消息
//******************************************************************
LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message )
{
case WM_KEYDOWN://击键消息
switch( wParam )
{
case VK_ESCAPE:
MessageBox(hWnd,"ESC键按下了!","Keyboard",MB_OK);
break;
}
break;
case WM_RBUTTONDOWN://鼠标消息
{
MessageBox(hWnd,"鼠标右键按下了!","Mouse",MB_OK);
break;
}
case WM_PAINT://窗口重画消息
{
char hello[]="你好,我是EasyWin !";
HDC hdc;
PAINTSTRUCT ps;
hdc=BeginPaint( hWnd,&ps ); //取得设备环境句柄
SetTextColor(hdc, RGB(0,0,255)); //设置文字颜色
TextOut( hdc, 20, 10, hello, strlen(hello) );//输出文字
EndPaint( hWnd, &ps ); //释放资源
break;
}
case WM_DESTROY://退出消息
PostQuitMessage( 0 );//调用退出函数
break;
}
//调用缺省消息处理过程
return DefWindowProc(hWnd, message, wParam, lParam);
}
程序输入完毕,即可编译执行。在窗口中击鼠标键或按
其实,这个程序可以看成是所有
WinMain()
int PASCAL WinMain( HINSTANCE hInstance, //当前实例句柄
HINSTANCE hPrevInstance, //前一个实例句柄
LPSTR lpCmdLine, //命令行字符
int nCmdShow) //窗口显示方式
第一个参数
第二个参数是
第三个参数是
最后一个参数是
一个应用程序可以有许多窗口,但只有一个是主窗口,它是与该应用程序的实例句柄唯一关联的。上面的例程中,创建主窗口的函数是
通常要对填充一个窗口类结构
在
typedef struct _WNDCLASS {
UINT style; //窗口的风格*
WNDPROC lpfnWndProc; //指定窗口的消息处理函数的远指针*
int cbClsExtra; //指定分配给窗口类结构之后的额外字节数*
int cbWndExtra; //指定分配给窗口实例之后的额外字节数
HANDLE hInstance; //指定窗口过程所对应的实例句柄*
HICON hIcon; //指定窗口的图标
HCURSOR hCursor; //指定窗口的鼠标
HBRUSH hbrBackground; //指定窗口的背景画刷
LPCTSTR lpszMenuName; //窗口的菜单资源名称
LPCTSTR lpszClassName; //该窗口类的名称*
} WNDCLASS;
在
typedef struct _WNDCLASSEX {
UINT cbSize; //指定WNDCLASSEX结构的大小
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm; //窗口的小图标
} WNDCLASSEX;
WNDCLASS和WNDCLASSEX这两个结构基本上是一致的,只是WNDCLASSEX结构中多了cbSize和hIconSm这两个成员。WNDCLASS结构的各成员中,其注释后打了星号的表示该项应特别注意。
WNDCLASS结构的第一个成员style表示窗口类的风格,它往往是由一些基本的风格通过位的“或”操作(操作符位“|”)组合而成。下表列出了一些常用的基本窗口风格:
风格 |
含义 |
CS_HREDRAW |
如果窗口客户区宽度发生改变,重绘整个窗口 |
CS_VREDRAW |
如果窗口客户区高度发生改变,重绘整个窗口 |
CS_DBLCLKS |
能感受用户在窗口中的双击消息 |
CS_NOCLOSE |
禁用系统菜单中的“关闭”命令 |
CS_OWNDC |
为该窗口类的各窗口分配各自独立的设备环境 |
CS_CLASSDC |
为该窗口类的各窗口分配一个共享的设备环境 |
CS_PARENTDC |
指定子窗口继承其父窗口的设备环境 |
CS_SAVEBITS |
把被窗口遮掩的屏幕图象部分作为位图保存起来。当该窗口被移动时, Windows使用被保存的位图来重建屏幕图象 |
在
wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = (WNDPROC)WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = "EasyWin";
可以看到,
第二个成员
接下来的
然后的
下面的
同样,调用
hbrBackground
上面的
lpszMenuName
WNDCLASS
填充完毕后,对于
ATOM RegisterClass( CONST WNDCLASS *lpWndClass );
ATOM RegisterClassEx( CONST WNDCLASSEX *lpwcx );
该函数如调用成功,则返回一个非
当窗口类注册完毕之后,并不会有窗口显示出来,因为注册的过程仅仅是为创建窗口所做的准备工作。实际创建一个窗口的是通过调用
hwnd = CreateWindow(
"EasyWin", //创建窗口所用的窗口类的名称*
"一个基本的Win32程序", //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格,定义为普通型*
100, //窗口位置的x坐标
100, //窗口位置的y坐标
400, //窗口的宽度
300, //窗口的高度
NULL, //父窗口句柄
NULL, //菜单句柄
hInstance, //应用程序实例句柄*
NULL ); //一般都为NULL
CreateWindow()函数的参数的含义在上面的注释中已有介绍,注释后打了星号标记的参数应该着重注意,其它的参数都很简单,不多做介绍,可参看VC的帮助。
第一个参数是创建该窗口所使用的窗口类的名称,注意这个名称应与前面所注册的窗口类的名称一致。
第三个参数为创建的窗口的风格,下表列出了常用的窗口风格:
风格 |
含义 |
WS_OVERLAPPEDWINDOW |
创建一个层叠式窗口,有边框、标题栏、系统菜单、最大最小化按钮,是以下几种风格的集合: WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX |
WS_POPUPWINDOW |
创建一个弹出式窗口,是以下几种风格的集合: WS_BORDER,WS_POPUP,WS_SYSMENU。WS_CAPTION与WS_POPUPWINDOW风格必须一起使用才能使窗口菜单可见 |
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 |
窗口有垂直滚动条 |
程序中使用了
CreateWindow()
如果窗口创建成功,返回值是新窗口的句柄,否则返回
窗口创建后,并不会在屏幕上显示出来,要真正把窗口显示在屏幕上,还得使用
BOOL ShowWindow( HWND hWnd, int nCmdShow );
参数
由于
BOOL UpdateWindow( HWND hWnd );
WS_OVERLAPPEDWINDOW标志,它是创建一个普通窗口常用的标志。而在DirectX编程中,我们常用的是WS_POPUP,用这个标志创建的窗口没有标题栏和系统菜单,如果设定窗口为最大化,客户区可以占满整个屏幕,以满足DirectX编程的需要。 函数后面的参数中,仍用到了该应用程序的实例句柄hInstance。NULL。ShowWindow()函数,其原型如下: hWnd指定要显示得窗口的句柄,nCmdShow表示窗口的显示方式,这里指定为从WinMain()函数的nCmdShow所传递而来的值。ShowWindow()函数的执行优先级不高,所以当系统正忙着执行其它的任务时,窗口不会立即显示出来,此时,调用UpdateWindow()函数以可以立即显示窗口。其函数原型如下: Win32编程中,消息循环是相当重要的一个概念,看似很难,但是使用起来却是非常简单。在WinMain()函数中,调用InitWindow()函数成功的创建了应用程序主窗口之后,就要启动消息循环,其代码如下: 应用程序可以接收以各种形式输入的信息,这包括键盘、鼠标动作 、记时器产生的消息,也可以是其它应用程序发来的消息等等。Windows系统自动监控所有的输入设备,并将其消息放入该应用程序的消息队列中。函数则是用来从应用程序的消息队列中按照先进先出的原则将这些消息一个个的取出来,放进一个MSG结构中去。GetMessage()函数原型如下: 将获取的消息复制到一个MSG结构中。如果队列中没有任何消息,GetMessage()函数将一直空闲直到队列中又有消息时再返回。如果队列中已有消息,它将取出一个后返回。MSG结构包含了一条Windows消息的完整信息,其定义如下: 函数还可以过滤消息,它的第二个参数是用来指定从哪个窗口的消息队列中获取消息,其它窗口的消息将被过滤掉。如果该参数为NULL,则GetMessage()从该应用程序线程的所有窗口的消息队列中获取消息。MSG结构中主消息值的,主消息值在wMsgFilterMin和wMsgFilterMax之外的消息将被过滤掉。如果这两个参数为0,则表示接收所有消息。GetMessage()函数在获取到WM_QUIT消息后,将返回0值,于是程序退出消息循环。函数的作用是把虚拟键消息转换到字符消息,以满足键盘输入的需要。DispatchMessage()函数所完成的工作是把当前的消息发送到对应的窗口过程中去。switch语句分配到不同的处理程序中去。Windows的消息处理函数都有一个确定的样式,即这种函数的参数个数和类型以及其返回值的类型都有明确的规定。在VC的说明书中,消息处理函数的原型是这样定义的: EasyWin中的WinProc()函数就是这样一个典型的消息处理函数。GetMessage()函数从消息队列中获得MSG结构,然后分解后得到的。第二个参数uMsg和MSG结构中的message值是一致的,代表了主消息值。程序中用switch语句来将不同类型的消息分配到不同的处理程序中去。 EasyWin 应用程序中,是按如下方式对 WNDCLASS 结构进行填充和注册的: wc.style 被设为 CS_VREDRAW | CS_HREDRAW ,表示只要窗口的高度或宽度发生变化,都会重画整个窗口。 lpfnWndProc 的值为 (WNDPROC)WinProc 。表明该窗口类的消息处理函数是 WinProc() 函数。这里,要指定窗口的消息处理函数的远指针,输入消息处理函数的函数名称即可,必要时应该进行强制类型转换,将其转换成 WNDPROC 型。 cbClsExtra 和 wc.cbWndExtra 在大多数情况下都会设为 0 。 hInstance 成员,给它的值是由 WinMain() 传来的应用程序的实例句柄,表明该窗口与该实例是相关联的。事实上,只要是注册窗口类,该成员的值始终是该程序的实例句柄,你应该象背书一样记住它。 hIcon ,是让你给这个窗口指定一个图标,调用 LoadIcon( hInstance, IDI_APPLICATION ) ,可以调用系统内部预先定义好的标志符为 IDC_APPLICATION 的图标作为该窗口的图标。 LoadCursor( NULL, IDC_ARROW ) 为该窗口调用系统内部预先定义好的箭头型鼠标。 成员用来定义窗口的背景画刷颜色,也就是该窗口的背景色。调用 GetStockObject(WHITE_BRUSH) 可以获得系统内部预先定义好的白色画刷作为窗口的背景色。 LoadIcon() 、 LoadCursor() 、 GetStockObject() 都是 Windows 的 API 函数,它们的用法可以参看 VC 的帮助,这里就不多介绍了。 成员的值我们给它 NULL ,表示该窗口将没有菜单。如果你想让你的窗口拥有菜单,就把 lpszMenuName 成员赋值为标志菜单资源的字符串。 结构的最后一个成员 lpszClassName 是让你给这个窗口类起一个唯一的名称,因为 Windows 操作系统中有许许多多的窗口类,必须用一个独一无二的名称来代表它们。通常,你可以用你的程序名来命名这个窗口类的名称。这个名称将在创建窗口的 CreateWindow() 函数中用到。 WNDCLASS 结构,调用 RegisterClass() 函数进行注册;对于 WNDCLASSEX 结构,调用 RegisterClassEx() 函数进行注册,它们的原型分别如下: 0 值,表明系统中已经注册了一个名为 EasyWin 的窗口类。如果失败,则返回 0 。 CreateWindow() 函数完成的。窗口类中已经预先定义了窗口的一般属性,而 CreateWindow() 中的参数可以进一步指定一个窗口的更具体的属性,在 EasyWin 程序中,是如下调用 CreateWindow() 函数来创建窗口的:
在
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Windows
GetMessage()
BOOL GetMessage(
LPMSG lpMsg, //指向一个MSG结构的指针,用来保存消息
HWND hWnd, //指定哪个窗口的消息将被获取
UINT wMsgFilterMin, //指定获取的主消息值的最小值
UINT wMsgFilterMax //指定获取的主消息值的最大值
);
GetMessage()
typedef struct tagMSG {
HWND hwnd; //接收消息的窗口句柄
UINT message; //主消息值
WPARAM wParam; //副消息值,其具体含义依赖于主消息值
LPARAM lParam; //副消息值,其具体含义依赖于主消息值
DWORD time; //消息被投递的时间
POINT pt; //鼠标的位置
} MSG;
该结构中的主消息表明了消息的类型,例如是键盘消息还是鼠标消息等,副消息的含义则依赖于主消息值,例如:如果主消息是键盘消息,那么副消息中则存储了是键盘的哪个具体键的信息。
GetMessage()
第三个和第四个参数是用来过滤
当且仅当
TranslateMessage()
消息处理函数又叫窗口过程,在这个函数中,不同的消息将用
LRESULT CALLBACK WindowProc(
HWND hwnd, //接收消息窗口的句柄
UINT uMsg, //主消息值
WPARAM wParam, //副消息值
LPARAM lParam //副消息值
);
如果你的程序中还有其它的消息处理函数,也都必须按照上面的这个样式来定义,但函数名称可以随便取。
消息处理函数的四个参数是由
WinProc()函数明确的处理了4个消息,分别是WM_KEYDOWN(击键消息)、WM_RBUTTONDOWN(鼠标右键按下消息)、WM_PAINT(窗口重画消息)、WM_DESTROY(销毁窗口消息)。WM_SIZE、WM_MINIMIZE、WM_CREATE、WM_MOVE等这样频频使用的消息就有几十条。为了减轻编程的负担,Windows的API提供了DefWindowProc()函数来处理这些最常用的消息,调用了这个函数后,这些消息将按照系统默认的方式得到处理。Alt+F4或单击窗口右上角的退出按钮,系统就向应用程序发送一条WM_DESTROY的消息。在处理此消息时,调用了PostQuitMessage()函数,该函数会给窗口的消息队列中发送一条WM_QUIT的消息。在消息循环中,GetMessage()函数一旦检索到这条消息,就会返回FALSE,从而结束消息循环,随后,程序也结束。Win32编程的基础知识,在进行DirectX编程之前,掌握它们是十分必要的。Win32应用程序工程 RegisterClass()函数注册一个窗口类,再立即调用CreateWindow()函数创建一个窗口的实例 DefWindowProc()函数以处理那些缺省的消息 PostQuitMessage()函数以结束消息循环
值得注意的是,应用程序发送到窗口的消息远远不止以上这几条,象
当用户按
本章介绍的是
通过本章的学习,你应该学到以下知识: