Win32 Api编程指南

上传人:cl****1 文档编号:571502965 上传时间:2024-08-11 格式:PDF 页数:37 大小:512.74KB
返回 下载 相关 举报
Win32 Api编程指南_第1页
第1页 / 共37页
Win32 Api编程指南_第2页
第2页 / 共37页
Win32 Api编程指南_第3页
第3页 / 共37页
Win32 Api编程指南_第4页
第4页 / 共37页
Win32 Api编程指南_第5页
第5页 / 共37页
点击查看更多>>
资源描述

《Win32 Api编程指南》由会员分享,可在线阅读,更多相关《Win32 Api编程指南(37页珍藏版)》请在金锄头文库上搜索。

1、Win32 APIWin32 API 编程指南编程指南( ( 1 1 ) ) 起步起步 本书的内容本书的内容 本书将会展示给您使用 Win32 API 来开发 windows 程序的基本方法。开发语言采用 C 语言,大多数 C+编译器也都能够正常编译 C 语言的程序。事实上,书中的绝大部分内容对于任何调用 API 的语言都是有效的,像JAVA,汇编语言,以及 Visual Basic。但我不会用这些语言来编写范例代码,先前也有一些人尝试用其它的语言来实现本书中的例子,很可惜,没有几个人成功。 本书不会教您什么是 C 语言,也不会教您如何使用特定的编译器( Borland C+, Visual

2、C+,LCC-Win32,等等 )。最后,我会在附录中提供一些我个人的使用编译器的经验。 如果您不知道宏或 typedef 是什么,不知道 switch 语句是如何工作的,请现在就放下本书,先找一本介绍 C 语言的书读一读。 重要的说明重要的说明 在全书中,通常我会指出一些非常重要的东西,这些都是必读的。因为它们常常把人弄得糊里糊涂,如果您不读这些东西,很有可能也会身陷其中。 首先: 范例压缩包里的源代码是必读的!范例压缩包里的源代码是必读的! 我没有在书中包含全部的代码, 而只包含了那些与我们当前讨论的内容有关的代码片断。如果您想详细了解代码在程序中的体现,就必须去参看压缩包内提供的源代码。

3、 其次: 通读所有的内容!通读所有的内容!如果您在书中的某个部分有什么疑问,请耐心的读下去,也许不久,在后面的部分中就会找到答案。如果您非想要立刻就弄清楚这个问题,至少也要保证,在通过 IRC 或者 Email 来寻求答案之前,你已经浏览或者搜索了其余的部分了。 另外,请记住,题目 A 中涉及的问题,有可能在 B 或者 C 甚至 L 中才会做出解答,所以还是先泛读一遍比较好。 好了,以上就是我想说的全部注意事项了,接下来让我们看一些实际的例子吧。 最简单的最简单的 Win32Win32 程序程序 即使您完全是个初学者, 至少也要保证能够正确编译一个最基本的 windows 程序。 将下面的代码

4、敲入编辑器,如果一切正常,您就能创建一个最基本的程序了。 切记,要将它作为一个 C 源文件来编译,而不是 C+源文件。虽然这可能没什么关系,但是,既然我们的代码完全是用 C 写的,就保证它从一开始的时候就朝着一个有益的方向前进吧。本书中,绝大多数情况下,都要求将代码保存为 a.c 而不是 a.cpp。如果您觉得这个名字很不爽,那就将它保存为 test.c 吧。 #include int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) MessageBox(

5、 NULL, Goodbye, cruel world!, Note, MB_OK ); return 0; 如果程序报错, 首先请看一看您是否理解错误的提示信息, 在您的编译器的帮助或者联机文档中搜索一下这个错误的详细描述。有一点需特别注意,确保已将您的程序指明为确保已将您的程序指明为 Win32 GUIWin32 GUI 程序,而不是控制台程序。程序,而不是控制台程序。很抱歉,对于排错的问题,我也没什么好办法。因为根据编译器的不同,错误提示的信息也不同( 而且人与人也不同 )。 程序编译时也可能会有一些警告,例如没有使用 WinMain( )函数中的参数,这是正常现象,不用理会。至此,我们

6、已经建立了一个能正常运行的程序,接下来让我们来分析一下这段代码. int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) WinMain( )函数等价于 DOS 或者 UNIX 编程中的 main( )函数。这是我们程序执行的入口点。参数的含义如下: HINSTANCE hInstance 程序执行模块的句柄。( 内存中的.exe 文件 )。 HINSTANCE hPrevInstance 在 Win32 程序中总为 NULL。 LPSTR lpCmdLi

7、ne 一个字符串的命令行参数。不包括程序名。 int nCmdShow 一个可能传递给 ShowWindow( )函数的整数。稍后我会解释。 hInstance 主要用来装载资源和执行一些基于其它模块的功能。模块是指装载到程序中的 exe 或者 dll 文件。在本书的大部分( 不是全部 )内容中,它们都是指 exe 文件。 hPrevInstance 在过去的 Win16 程序中,用来指向一个已经运行了的程序的实例。现在已经不再使用了,在 Win32 中,您完全可以忽略这个参数。 调用方式调用方式 WINAPI 指明了调用的方式,它被定义为_stdcall。即使您不明白,也不用担心。这并不影响

8、我们的学习。记住在这里它是必需的就行了。 Win32Win32 数据类型数据类型 您可能发现了,许多关键字在 windows 下面都被重新定义了,例如 UINT 是 unsigned int,LPSTR 是 char* 等等。至于用哪种写法完全取决于您自己。如果您觉得用 char* 比用 LPSTR 更爽,您尽可以自由的使用。只要确定您所使用的数据类型能够正确的匹配重新定义的数据类型就行了。 其实只要记住几个要点也就很容易理解了。LP 前缀代表指向长整形的指针( long pointer )。在 Win32中,long 是一种古老的类型了,这里不用细说。如果您不知道指针是什么,您有两种选择:1

9、 )去找本 C 的书读一读。 2 )继续读下去, 可能会弄得一团糟。 我强烈建议您选择第一种, 但还是有很多人坚持选择第二种( 我已经给了您建议哦: ) 别怪我没提醒您! ) 接下来是跟在 LP 后面的一个表示常量的字符 C,LPCSTR 表示一个指向字符串常量的长指针, 它指向的内容是不可修改的。LPSTR 是指向字符串的长指针,没有 const 关键字,它指向的内容是可以修改的。 Win32 APIWin32 API 编程指南编程指南( ( 2 2 ) ) 一个简单的窗口一个简单的窗口 范例:simple_window 许多人到 IRC 里询问,“如何创建一个窗口?”.其实,这并不是一件非

10、常容易的小事。但一旦您明白了我们正在学的东西,也就不那么困难了。为了创建一个能够正常显示的窗口,确实需要做很多事情;这不是在聊天室里三言两语能说得清楚的。 我总是倾向于先实际的做点什么,然后再进行分析讨论。.先给出一段创建简单窗口的代码,稍后再对其进行说明。 #include const char g_szClassName = myWindowClass; / Step 4: the Window Procedure / 步骤 4: 窗口过程函数 LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPar

11、am ) switch( msg ) case WM_CLOSE: DestroyWindow( hwnd ); break; case WM_DESTROY: PostQuitMessage( 0 ); break; default: return DefWindowProc( hwnd, msg, wParam, lParam ); return 0; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) WNDCLASSEX wc; HWND hw

12、nd; MSG Msg; /Step 1: Registering the Window Class /步骤 1: 注册窗口类 wc.cbSize = sizeof( WNDCLASSEX ); wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hbrBa

13、ckground = ( HBRUSH )( COLOR_WINDOW+1 ); wc.lpszMenuName = NULL; wc.lpszClassName = g_szClassName; wc.hIconSm = LoadIcon( NULL, IDI_APPLICATION ); if ( !RegisterClassEx( &wc ) ) MessageBox( NULL, Window Registration Failed!, Error!, MB_ICONEXCLAMATION | MB_OK ); return 0; / Step 2: Creating the Wind

14、ow / 步骤 2: 创建窗口 hwnd = CreateWindowEx ( WS_EX_CLIENTEDGE, g_szClassName, The title of my window, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, hInstance, NULL ); if ( hwnd = NULL ) MessageBox( NULL, Window Creation Failed!, Error!, MB_ICONEXCLAMATION | MB_OK ); return 0; S

15、howWindow( hwnd, nCmdShow ); UpdateWindow( hwnd ); / Step 3: The Message Loop / 步骤 3: 消息循环 while( GetMessage( &Msg, NULL, 0, 0 ) 0 ) TranslateMessage( &Msg ); DispatchMessage( &Msg ); return Msg.wParam; 这段代码能够创建一个真正具有窗口功能的最简单的 windows 程序,仅仅 70 左右行代码。如果第一个范例能够正常编译通过,这个也应该没有什么问题。 步骤步骤 1: 1: 注册窗口类注册窗口类

16、 窗口类中保存着窗口类型的相关信息, 包括控制窗口行为的窗口过程函数、 窗口最大化和最小化时的图标、窗口的背景颜色等等。利用这种方法,您就能够基于创建的这个窗口类,创建多个窗口,而不需要在创建每一个窗口的时候都重新设置这些属性。我们设置的大多数的属性的值都可以在窗口创建之前进行更改。 窗口类和 C+中的类没有任何关系。 const char g_szClassName = myWindowClass; 上面的数组中存储着我们要创建的窗口类的名字,我们将用它向系统注册我们的窗口类。 WNDCLASSEX wc; wc.cbSize = sizeof( WNDCLASSEX ); wc.style

17、 = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hbrBackground = ( HBRUSH )( COLOR_WINDOW+1 ); wc.lpszMenuName = NULL; wc.lpszClassName = g_szClassName; wc.hIco

18、nSm = LoadIcon( NULL, IDI_APPLICATION ); if ( !RegisterClassEx( &wc ) ) MessageBox( NULL, Window Registration Failed!, Error!, MB_ICONEXCLAMATION | MB_OK ); return 0; 这是在 WinMain( )函数中注册窗口类的代码。我们为 WNDCLASSEX 结构的各个成员赋值然后调用RegisterClassEx( )函数来注册这个窗口类。 结构的各个成员的含义如下: cbSize 结构的大小。 style 窗口类的风格( CS_* ),

19、不要同窗口风格( WS_* )弄混了,通常置 0。 lpfnWndProc 指向这个窗口类对应的窗口过程函数的指针。 cbClsExtra 为这个类在内存中保留的扩展空间,通常置 0。 cbWndExtra 为这个类创建的窗口在内存中保留的扩展空间,通常置 0。 hInstance 应用程序实例化句柄( WinMain( )函数中的第一个参数 )。 hIcon 当用户按下 Alt+Tab 键时显示的程序的大图标( 通常为 32*32 )。 HCursor 在窗口上显示的指针。 hbrBackground 设置窗口的背景色。 lpszMenuName 窗口类使用的菜单资源的名字。 lpszCla

20、ssName 窗口类的名字。 hIconSm 程序最小化到任务栏和程序窗口左上角显示的小图标( 通常为 16*16 )。 如果您对前面所讲的这些不是很理解,也不用担心,后面不久还会详细解释的。另外还有一点要提醒您,不用刻意的把每个成员都背下来, 我很少见有人能完全记下这个结构中的每一个成员、 或者函数的每一个参数。不要在这上面浪费精力,时间很宝贵。如果您知道要调用哪个函数,只需要花几秒钟去帮助文档里面搜一下那个函数的详细说明就行了。如果您还没有帮助文档,赶快弄一份。没有这个您就落伍了。通过查阅帮助文档,久而久之,那些常用函数的参数基本上也就都记住了。 接着,我们调用了 RegisterClas

21、sEx( )函数并且检验调用是否成功,如说调用失败了,会弹出一个对话框提示出错,然后结束程序。 步骤步骤 2: 2: 创建窗口创建窗口 一旦窗口类注册成功,我们就能够通过它来创建窗口了。您可能迫不及待的想知道 CreateWindowEx( )函数的参数了( 当接触到一个新的 API 函数的时候,您可能总是这样 ),接下来让我简要地介绍一下。 HWND hwnd; hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, g_szClassName, The title of my window, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,

22、CW_USEDEFAULT, 240, 120, NULL, NULL, hInstance, NULL ); 第一个参数( WS_EX_CLIENTEDGE )是扩展的 windows 窗口风格。在这个例子中,我创建了一个内部完全是客户区的窗口。 如果您想比较一下不同的值, 效果有什么不同, 可以将它设置为 0, 或者将它设置为其它的值。 接下来是窗口类名( g_szClassName ),这个参数通知系统创建一个哪种类型的窗口。既然我们想要创建一个基于刚才注册的窗口类的窗口, 我们就要将那个窗口类名作为参数。 然后, 指定窗口标题上显示的内容( 就是在窗口左上角显示的内容,也称为窗口标题

23、)。 WS_OVERLAPPEDWINDOW 这个参数是要创建的窗口的风格。 关于这个参数有很多不同的值, 您可以自己去查阅。后面我们也会涉及到。 接下来的四个参数( CW_USEDEFAULT, CW_USEDEFAULT, 320, 240 )分别是窗口左上角的 x 坐标、y 坐标、窗口宽度和高度。我将 x 和 y 设置为 CW_USEDEFAULT,让 windows 来设定窗口在屏幕上显示的位置。需要注意的是,屏幕的 x 坐标是自左向右由 0 开始递增的,y 坐标是从上到下递增的。递增的单位是像素,是屏幕所能显示的最基本单位。 接下来的参数( NULL, NULL, g_hInst,

24、NULL )分别是,窗口的父窗口句柄,菜单句柄,程序实例化句柄和指向窗口数据的指针。在 Windows 中,窗口是具有继承性的。您可能看到有的窗口上有一个按钮,这个按钮就是窗口的子窗口,而窗口就是按钮的父窗口。在这个例子中,父窗口的句柄为 NULL,因为我们这个窗口并没有父窗口,这个窗口就是我们的主窗口或称为最顶级窗口。到目前为止,我们还没有创建菜单,所以菜单窗口句柄也为空。程序实例化句柄是由 WinMain( )函数的第一个参数传递过来的。窗口数据的指针( 我几乎从来没有用过 )传递一些窗口创建时候需要的附加数据。 如果您想知道神秘的 NULL 是什么,呵呵,它其实只是被简单的定义为 0。在

25、 C 中,因为经常被用于指针,它的定义为( ( void* )0 )。所以,当你把它用在整形的位置的时候,很可能编译器会有警告信息,当然,这和您的编译器以及警告的设置有关。您可以忽略这些警告或者用 0 来代替 NULL。 有时候,我们实在不知道该死的 bug 是怎么出现的,其中很大的可能性是没有测试函数的返回值。即使是一个有经验的老手,调用 CreateWindow( )函数也经常会失败。调用的时候,确实有许多错误很容易就犯了。这种情况直到我们学会如何快速的找出错误, 或者至少能够推断哪里可能有问题, 并且总是检验函数的返回值的时候,才能有所改善。 if ( hwnd = NULL ) Mes

26、sageBox( NULL, Window Creation Failed!, Error!, MB_ICONEXCLAMATION | MB_OK ); return 0; 当我们创建了窗口并且确定返回的指针有效的时候,就可以显示这个窗口了。传递 WinMain( )函数的最后一个参数,然后更新窗口,使窗口重画到显示器上。 ShowWindow( hwnd, nCmdShow ); UpdateWindow( hwnd ); nCmdShow 参数是任意的。您也可以一直简单的传递一个 SW_SHOWNORMAL。通过这个参数,可以指定程序启动的方式,任何人运行的时候都一样。启动的时候是否可见

27、、最大化、最小化等等。您可以在 Windows 的快捷方式里面找到这一项,这个参数就是指明程序如何启动的。 步骤步骤 3: 3: 消息循环消息循环 这是整个程序的核心部分,通过这里的控制合理的安排程序的执行。 while( GetMessage( &Msg, NULL, 0, 0 ) 0 ) TranslateMessage( &Msg ) DispatchMessage( &Msg ); return Msg.wParam; GetMessage( )函数从系统消息队列中取得消息。无论何时,用户移动鼠标,敲击键盘,点击程序的菜单或者其余的一些操作,操作系统都会产生消息并且将它们发送到系统消息

28、队列中。调用 GetMessage( )函数,能够将属于本窗口的消息取回到本程序消息队列处理,然后删除这条消息。如果当前程序没有消息,GetMessage( )函数就会被阻塞。如果您对这一点不了解,可以这样认为,被阻塞的时候,这个函数会一直等待,直到有一个消息了,才会返回一个值。 TranslateMessage( )函数是当接收到键盘消息时, 例如 WM_CHAR和 WM_KEYDOWN 消息一起传送过来的时候,做一些附加的处理。最后 DispatchMessage( )函数将消息发送给窗口处理。( 译者注:实际上是发送给窗口的窗口过程函数处理 )这个窗口也许是我们的主窗口,也许是别的窗口,

29、或者仅仅是一个操作,很多情况下,窗口的创建是不显示在屏幕上的,可能由系统隐式的创建或者由另一个程序创建,而这些都不需要我们操心,因为我们通信的途径就是通过发送和接受消息,其余的事情操作系统会为我们打理。 步骤步骤 4: 4: 窗口过程窗口过程 如果说消息循环是程序的心脏, 那么窗口过程就是程序的大脑了。 所有发送给程序的消息都是在这里被处理的。 LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) switch( msg ) case WM_CLOSE: DestroyWindow( hwnd

30、 ); break; case WM_DESTROY: PostQuitMessage( 0 ); break; default: return DefWindowProc( hwnd, msg, wParam, lParam ); return 0; 处理每一个消息都会调用窗口过程函数。HWND 参数是消息所属的窗口的句柄。当我们的程序有基于同一个窗口类的两个或多个窗口的时候,这个参数就特别重要了。这些窗口共用窗口过程函数( WndProc( ) )。区别这些窗口的方法就是靠 hwnd 参数的不同。举个例子,当我们接收到一个 WM_CLOSE 消息的时候,通过不同的hwnd 参数,销毁相对应

31、的那个窗口,而其它的窗口不会受到任何影响。 当用户点击关闭按钮或者 Alt+F4 键的时候,都会触发 WM_CLOSE 消息,Windows 会默认的关闭这个窗口。但我还是习惯自己来处理这个消息,因为有些事情可能需要在关闭窗口之前解决,像清除某些选项,或者询问用户是否要保存文件等等。 当我们调用了 DestroyWindow( )函数,系统就会发送一个 WM_DESTROY 消息给窗口,然后销毁窗口的子窗口,释放掉窗口占用的资源。既然我们的程序只有一个窗口,就可以让程序立即结束,所以我们继续调用PostQuitMessage( )函数。这个函数会将 WM_QUIT 消息发送到消息循环中。我们永

32、远也不会处理这个消息,因为它使 GetMessage( )函数的返回值为 FALSE,您将会在我们的消息循环代码中了解到这一点。当接收到这条消息以后,窗口过程就停止处理任何消息了。WM_QUIT 消息的 wParam 成员包含着传递给 PostQuitMessage( )函数的值。只有当您的程序是要启动别的程序的时候,这个函数的返回值才有用。( 译者注: 实际上这个函数的返回值表示程序是否是正常退出 ) 步骤步骤 5: 5: 没有步骤没有步骤 5 5 嘿!就这样吧!如果我还有什么没解释清楚的,就先放着吧。希望随着我们不断地深入学习更多的程序,疑问会逐渐的解开。 Win32 APIWin32 A

33、PI 编程指南编程指南( ( 3 3 ) ) 处理消息处理消息 范例:window_click 我们已经能够创建一个窗口了,可是这个窗口除了默认处理函数 DefWindowProc( )能执行的一些诸如改变窗口大小、最大化等操作之外,再没什么别的操作了,仔细想想,好像没那么振奋人心哦。 在下面的这一部分,我会讲解,如何修改我们前面的程序,使它能够执行一些新的功能。鉴于我只是阐述如何处理消息,所以并不需要看全部的代码,您就能明白我的意思。这也是我们的目标,好了,集中精力 :P 对于初学者,确保前面的程序能够正常的编译、运行就足够了。然后,在前面程序的原有代码的基础上直接修改或者将程序的代码拷贝到

34、一个新的工程中再修改就可以了。 我们想要添加到程序中的功能是,当用户在窗口内单击( 译者注:这里的单击是指鼠标左键单击,以下均如此 )鼠标以后,程序会以对话框的形式显示出程序的名字。听上去好像并不那么令人激动啊,但这是处理消息的最基本的内容。让我们先来看一下窗口过程中的代码。 LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) switch( msg ) case WM_CLOSE: DestroyWindow( hwnd ); break; case WM_DESTROY: PostQui

35、tMessage( 0 ); break; default: return DefWindowProc( hwnd, msg, wParam, lParam ); return 0; 若要处理鼠标单击产生的消息,我们需要在窗口过程中添加 WM_LBUTTONDOWN 消息( 或者WM_RBUTTONDOWN,WM_MBUTTONDOWN,它们分别对应右键和中键 )。如果我或者其他人提到处理消息,就是指将消息处理代码添加到 WndProc( )函数中。 LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPa

36、ram ) switch( msg ) case WM_LBUTTONDOWN: / - / - we just added this stuff break; / - 这里就是我们添加的 case WM_CLOSE: DestroyWindow( hwnd ); break; case WM_DESTROY: PostQuitMessage( 0 ); break; default: return DefWindowProc( hwnd, msg, wParam, lParam ); return 0; 在窗口过程中,消息处理的顺序是不分先后的,只要确保在每条消息处理结束的时候都添加一个 b

37、reak语句就可以了。很明显,我们在 switch( )中添加了一个 case 语句。现在,我们想要程序接收到这个消息的时候执行我们想要的操作。 我先把要添加到原程序中的代码写出来( 这段代码会显示出程序的名字 ), 然后把它添加到我们先前的程序中。稍后,我会告诉您如何将它添加到您自己的程序中。这对我、对您都是一件好事。因为我不用再敲全部的代码了,而您也可以将它应用于任何程序,不必局限于我举例的这个。如果您还不清楚怎么做,请仔细阅读压缩包内的这部分内容的源代码。 GetModuleFileName( hInstance, szFileName, MAX_PATH ); MessageBox(

38、hwnd, szFileName, This program is:, MB_OK | MB_ICONINFORMATION ); 这段代码并不是独立存在的, 不能够随意的添加到程序的任何部分。 我们想要的是只有当用户单击鼠标左键的时候它才被执行,所以要这样将它添加到基本程序中。 LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) switch( msg ) case WM_LBUTTONDOWN: / BEGIN NEW CODE / 新代码开始 char szFileNameMAX_PA

39、TH; HINSTANCE hInstance = GetModuleHandle( NULL ); GetModuleFileName( hInstance, szFileName, MAX_PATH ); MessageBox( hwnd, szFileName, This program is: , MB_OK | MB_ICONINFORMATION ); / END NEW CODE / 新代码结束 break; case WM_CLOSE: DestroyWindow( hwnd ); break; case WM_DESTROY: PostQuitMessage( 0 ); br

40、eak; default: return DefWindowProc( hwnd, msg, wParam, lParam ); return 0; 请注意内的部分。 当在 switch( )语句中声明变量的时候, 必须要加上。 虽然这是 C 语言的基础知识,但我认为还是有必要提一下。 代码添加结束, 重新编译。 如果一切正常, 在窗口内单击一下鼠标, 就会弹出一个对话框显示程序的名字。 您可能注意到我们添加了两个变量, hInstance 和 szFileName。 查看 GetModuleFileName( )函数会发现,它的第一个参数是个 HINSTANCE 型变量,一个可执行模块的指针

41、。( 指向我们的程序,.exe 文件 )。为什么我们要得到它呢?GetModuleHandle( )函数就是答案。GetModuleHandle( )函数的说明中指出,传递一个 NULL参数给它, 它就会返回一个指向调用它的过程的句柄( 实际上就是返回这个程序的句柄 ), 这正是我们所期望的,将这些语句结合起来,我们可以这样声明: HINSTANCE hInstance = GetModuleHandle( NULL ); 再看第二个参数, 这是我们都熟悉的,这是一个指向 保存程序的路径和名字的字符串的指针。 数据类型是LPTSTR( 或者 LPSTR )。既然 LPSTR 和 char* 是

42、等价的,我们也可以这样声明一个数组: char szFileNameMAX_PATH; MAX_PATH 是由定义的一个宏,很方便。它定义了在 Win32 系统下存储一个文件名所需要的最大的缓冲区。我们将 MAX_PATH 传递给 GetModuleFileName( )函数,这样它就能知道缓冲区的大小了。 调用 GetModuleFileName( )函数以后,szFileName 中就存储了包含.exe 文件的名字的字符串,字符串以NULL 结束,我们简单的将它传递给 MessageBox( )函数然后显示给用户。 好了, 添加完了这段代码, 现在就可以编译了。 如果一切正常, 在窗口内单

43、击鼠标, 就会弹出一个对话框,包含.exe 程序的名字。 如果不能正常运行,下面给出这个程序的全部的源代码,与您的代码对比一下,看看有什么不同。 #include const char g_szClassName = myWindowClass; LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) switch( msg ) case WM_LBUTTONDOWN: char szFileNameMAX_PATH; HINSTANCE hInstance = GetModuleHandle(

44、 NULL ); GetModuleFileName( hInstance, szFileName, MAX_PATH ); MessageBox( hwnd, szFileName, This program is:, MB_OK | MB_ICONINFORMATION ); break; case WM_CLOSE: DestroyWindow( hwnd ); break; case WM_DESTROY: PostQuitMessage( 0 ); break; default: return DefWindowProc( hwnd, msg, wParam, lParam ); r

45、eturn 0; int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) WNDCLASSEX wc; HWND hwnd; MSG Msg; wc.cbSize = sizeof( WNDCLASSEX ); wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(

46、 NULL, IDI_APPLICATION ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hbrBackground = ( HBRUSH )( COLOR_WINDOW+1 ); wc.lpszMenuName = NULL; wc.lpszClassName = g_szClassName; wc.hIconSm = LoadIcon( NULL, IDI_APPLICATION ); if ( !RegisterClassEx( &wc ) ) MessageBox( NULL, Window Registration Failed

47、!, Error!, MB_ICONEXCLAMATION | MB_OK ); return 0; hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, g_szClassName, The title of my window, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, hInstance, NULL ); if ( hwnd = NULL ) MessageBox( NULL, Window Creation Failed!, Error!, MB_ICON

48、EXCLAMATION | MB_OK ); return 0; ShowWindow( hwnd, nCmdShow ); UpdateWindow( hwnd ); while( GetMessage( &Msg, NULL, 0, 0 ) 0 ) TranslateMessage( &Msg ); DispatchMessage( &Msg ); return Msg.wParam; Win32 APIWin32 API 编程指南编程指南( ( 4 4 ) ) 理解消息循环理解消息循环 理解消息循环和 windows 程序的消息收发体系不仅是编写小程序, 也是其它应用程序开发的基础。 前

49、面我们已经学习了一些消息处理的方法,接下来要深入到整个消息处理过程中去,如果不弄清楚这些,以后就可能会越学越糊涂。 消息是什么?消息是什么? 消息的本质就是一个整数。如果您查看过头文件( 这是一种很通用、很好的研究 API 运行原理的方法 ),可能会发现类似这样的定义: #define WM_INITDIALOG 0x0110 #define WM_COMMAND 0x0111 #define WM_LBUTTONDOWN 0x0201 等等。在 Windows 系统中,一切都是靠消息紧密地联系在一起的,至少在基础层是这样。如果您想让一个窗口或者一个控件( 实际上它也是一个特殊的窗口 )执行一

50、定的功能, 就要给它发送一个消息。 如果另一个窗口想要您的程序执行一定的操作,它也会给您发送一个消息。如果某一事件被触发,例如用户的键盘输入,鼠标移动,点击按钮等,系统就会将这个事件对应的消息发送给相应的窗口。如果这些消息中有属于您的窗口的,您就得接收并处理这些消息。 每个 Windows 消息结构都有两个成员,wParam 和 lParam。最初的 wParam 是 16bit,lParam 是 32bit,但是在 Win32 中,它们都是 32bit。并不是所有的消息都会用到这两个成员,每个消息应用它们的方法也不完全相同。例如 WM_CLOSE 消息,这两个成员都不用,您完全可以忽略它们。

51、而对 WM_COMMAND 消息,这两个成员都使用,wParam 包含两个值,HIWORD( wParam )是通知码( 如果可用 ),LOWORD( wParam )是控制码或者是发送消息的菜单的 ID 值。 lParam 是发送消息的控件的 HWND( 窗口句柄 ), 如果为空, 说明消息不是来自于控件。 HIWORD( )和 LOWORD( )是 windows 定义的两个宏,用来分离出 32bit 数据的高位字( 0xFFFFFFFF0000 )和低位字( 0xFFFF00000000 )。在 Win32 系统中,字( WORD )是 16bit 的数据,双字( DWORD )是 32

52、bit。 您可以调用 PostMessag( )函数或者 SendMessage( )函数发送一个消息。PostMessage( )函数将消息放入消息队列后立即返回,也就是说,调用 PostMessage( )函数返回后,发送的消息有可能已经被处理了,也有可能尚未被处理。而 SendMessage( )函数直接将消息发送给窗口,并且直到窗口处理完这个消息以后,函数才会返回。若想要关闭一个窗口,我们可以调用 PostMessage( )函数,发送一个 WM_CLOSE 消息,PostMessage( hwnd, WM_CLOSE, 0, 0 );这样做同点击右上角的关闭按钮效果相同。注意此时 w

53、Param 和 lParam都是 0。前面已经提到了,WM_CLOSE 不用这两个成员。 对话框对话框 如果您要使用对话框, 就要通过向它发送消息, 同它通信。 您可以先利用对话框的 ID, 调用 GetDlgItem( )函数得到这个对话框的句柄,然后调用 SendMessage( )函数发送消息;或者直接调用 SendDlgItemMessage( )函数,它将这些操作合并起来执行。您传递给它一个窗口的句柄和一个子窗口的 ID,它就会返回子窗口的句柄,然后发送消息。SendDlgItemMessage( )函数以及和它相似的 GetDlgItemText( )函数不仅仅可以应用于对话框,还

54、可以应用于任何的 windows 程序。 什么是消息循环?什么是消息循环? while( GetMessage( &Msg, NULL, 0, 0 ) 0 ) TranslateMessage( &Msg ); DispatchMessage( &Msg ); 1. 消息循环调用 GetMessage( )函数,从消息队列中获取消息。如果消息队列为空,程序基本上会处于等待状态。( 也就是所说的阻塞 ) 2. 当某一事件发生,就触发了一个消息,这个消息被添加到消息队列( 例如系统检测到鼠标点击 )。GetMessage( )函数就会返回一个正值返回一个正值,通知 有一个消息请求被处理。这个消息也

55、会被添加到传递过来的 MSG结构中。如果接收到的是 WM_QUIT 消息,就返回 0。如果发生错误,就返回一个负值。 3. 我们接收这个消息( 在 MSG 变量中 ),并将它传递给 TranslateMessage( )函数,在这里会做一点额外的处理。将虚拟键盘码转化成字符信息。这一步骤是可选的,但如果不加上这行代码,大部分的程序都不能正常运行。 4. 如果上面的步骤一切正常,接下来,我们就将这个消息传递给 DispatchMessage( )函数。DispatchMessage( )函数的任务是检查这个消息属于哪一个窗口,然后将它发送给窗口对应的窗口过程函数。然后调用窗口过程函数,将窗口的句

56、柄,消息,wParam 和 lParam 作为参数传递给窗口过程函数。 5. 在您的窗口过程中,需要检查消息和它的参数,并且还可以随心所欲的处理这些消息。如果处理的消息不是什么特殊的消息,还可以直接调用 DefWindowsProc( )函数,用系统默认的操作来处理这些消息( 多数时候都是什么也不做 )。 6. 一旦消息处理完毕,窗口过程就会自动返回,DispatchMessage( )函数也会返回,程序重新回到循环开始处继续执行。 这是 windows 编程的一个非常重要的概念。您的窗口过程函数并不是莫名其妙的被系统调用的,实际上,是您自己通过 DispatchMessage( )函数间接的

57、调用了它。如果您喜欢,尽可以在窗口句柄上调用GetWindowLong( )函数将消息直接发送给窗口过程函数! while( GetMessage( &Msg, NULL, 0, 0 ) 0 ) WNDPROC fWndProc = ( WNDPROC )GetWindowLong( Msg.hwnd, GWL_WNDPROC ); fWndProc( Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam ); 在我早期的代码中曾经试验过,这样做程序虽然也可以正常运行,但是却总有各种各样的不足,例如Unicode/ANSI 的转换,调用计时器的回调等等,这

58、种方法就不灵验了,并且很可能会影响到整个程序。所以,可以尝试这样做,但不要在实际的代码中真的这样写。 您可能注意到了,我们用 WindowLong( )函数将窗口过程和窗口联系起来。为什么我们不直接调用窗口过程函数 WndProc( )呢?原因在于消息循环负责维护程序中所有窗口的消息,按钮和列表框也有它们自己的窗口过程, 所以我们要确保调用的窗口过程函数是消息相对应的那个窗口的。 因为多个窗口可以调用相同的窗口过程函数,所以第一个参数( 窗口的句柄 )就被用于告知窗口过程函数这个消息是发送给哪个窗口的。 如您所见,编写应用程序的大部分时间都花费在处理消息循环中的消息上。您尽可以将消息发送过去,

59、Windows会为您处理。 但是, 当您想结束程序时, 该怎么做呢?因为我们用的是 while( )循环, 如果GetMessage( )函数返回了一个 FALSE 值,循环就停止了,程序执行到了 WinMain( )的末尾,自然也就结束了。这也正是PostQuitMeaasge( )函数完成的工作。它将一个 WM_QUIT 消息添加到消息队列中,然后 GetMessage( )函数填充消息结构,并且返回 0。这时,Msg 结构的 wParam 成员包含着您传递给 PostQuitMessage( )函数的值,您完全可以忽略它,或者将它返回给即将结束的 WinMain( )函数。 注意:如果遇

60、到错误,GetMessage( )函数会返回-1。切记这一点,否则很可能在某个地方就会遇到麻烦尽管 GetMessage( )函数被定义为返回一个 BOOL 型数据,而 BOOL 型被定义为 UINT( unsigned int ),所以它的返回值也可能不是 TRUE 或 FALSE。下面是一些似乎可以正常运行的代码,但是在某些情况下会出问题: while( GetMessage( &Msg, NULL, 0, 0 ) ) while( GetMessage( &Msg, NULL, 0, 0 ) != 0 ) while( GetMessage( &Msg, NULL, 0, 0 ) = T

61、RUE ) 以上都是错误的!值得一提的是,我过去在文档中一直用第一种写法,像我刚才说的,这样写也可以正常运行,而且 GetMessage( )也从来没失败过,而有的时候,您的代码没问题,程序却有问题了。然而,我没有考虑到您是否能读到这里,也有可能您的代码既有错误,GetMessage( )函数又失败: ) 我已经指出并且纠正了这个错误,如果还有什么遗漏,敬请见谅。 while( GetMessage( &Msg, NULL, 0, 0 ) 0 ) 这句代码总是执行同样的作用。( 译者注:也就是说这是正确的写法,不会出现不确定行为 ) 我希望现在您对 Windows 消息循环能有一个更清晰的理解

62、了,如果还没有,不用担心,当您实际应用了一段时间后,情况自然就会改变了。 Win32 APIWin32 API 编程指南编程指南( ( 5 5 ) ) 应用资源应用资源 您可能总想看看本书附录部分关于如何使用 VC+和 BC+资源的内容。 在我们继续深入学习之前,我会简单介绍一下资源,避免在后面的章节中重复写这一主题相关的代码。您不需要实际编译这一节的代码,这些仅仅是范例。 资源是预先定义的二进制数据, 并以二进制的格式存储在可执行程序中。 我们通过资源脚本文件创建一个资源,脚本文件扩展名为.rc。商业化的编译器通常都带有一个可视化的资源编辑器,能够自动生成资源脚本文件而不需要您自己创建。 但

63、有时, 您必须要手动编写资源脚本文件, 尤其是当您的编译器没有可视化编辑器,或者,你需要的一些特定功能编译器不能提供支持。 不幸的是,不同的编译器对应的资源文件也不完全相同。我会尽力阐述它们的处理资源文件的公共特性。 包含在 MSVC+中的资源编辑器使得手工编辑资源文件非常困难,因为它规定了固定的格式。您自己编写的资源文件,在保存的时候会被完全的破坏。因此,通常您不需要为创建资源文件费心。但了解如何手动修改他们还是很有好处的。另一个让人郁闷的是,MSVC+会默认将资源文件的头文件命名为resource.h,尽管您可能想取一个别的名字。为了使这份文档尽可能简单,我会遵守这些规定,但是在结尾的附录

64、中,我会介绍如何更改。 首先,让我们看一个非常简单的资源脚本文件,一个图标。 #include resource.h IDI_MYICON ICON my_icon.ico 这就是全部的内容。IDI_MYICON 是资源的标识符。ICON 是类型,my_icon.ico包含资源的外部文件的名字( 译者注:也就是资源的名字 )。这个资源脚本文件可以在任何编译器上正常运行。 那么,这个#include resource.h又是做什么的呢?我们的程序需要标识那个图标,而最好的方法就是赋给它一个唯一的 ID( IDI_MYICON )。我们可以通过创建resources.h文件来为图标指定一个 ID,

65、在这个文件和我们的资源脚本文件中,都包含这个 ID。 #define IDI_MYICON 101 您看到了,我将 IDI_MYICON 指定为 101。我们甚至可以忽略这个标识符的存在,在用到这个图标的地方直接用 101 来代替,但用 IDI_MYICON 会使我们的程序清晰很多,当有大量资源的时候,也容易记忆。 接下来再说说怎么添加一个菜单资源: #include resource.h IDI_MYICON ICON my_icon.ico IDR_MYMENU MENU BEGIN POPUP &File BEGIN MENUITEM E&xit, ID_FILE_EXIT END EN

66、D IDR_MYMENU 是资源的名字,MENU 是资源的类型。这里有一个新的看点,您注意到上面的 BEGIN 和END了吗?有些资源编辑器或者编译器用 代替BEGIN, 用 代替END。 如果您的编译器支持这两种写法,您尽可以随便选一种来用。如果不同时支持,您就需要选用一种适合的写法了。 我们已经添加了一个新的标识符,ID_FILE_EXIT,为了在我们的程序中能够使用它,我们还需要将它添加到资源文件的头文件 resource.h 中。 #define IDI_MYICON 101 #define ID_FILE_EXIT 4001 在大项目中创建并维护这些资源文件实在是一件很费神的事, 这

67、也是为什么大多数人都喜欢用可视化的资源编辑器来管理资源的原因。 它们能够很好地为您处理好一切。 并且您还可以在多个不同项目中使用相同或相似的 ID,而不会有什么问题。 下面是一个演示如何使用资源文件的范例: HICON hMyIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_MYICON ) ); LoadIcon( )和许多其它使用资源的函数的第一个参数都是当前程序的实例句柄( 在 WinMain( )函数中指定或者像前面的范例程序一样,通过 GetModuleHandle( )函数获得 )。第二个参数是资源的标识符。 您一定很想知道MAKEI

68、NTRESOURCE( )有什么作用以及为什么LoadIcon( )函数接受一个LPCTSTR类型的参数而不是我们传递给它的 UINT 类型的 ID。MAKEINTRESOURCE( )将一个整数( 资源的 ID 就是一个整数 )转化为它需要的 LPCTSTR 类型。这带给我们另一种标识资源的方法,就是用字符串。基本上没有人这样做了,我也不想深入地讲了,但是,最基本的是,如果你不通过#define 语句将一个整型值赋给你的资源,那么资源的名字就会被当作一个字符串使用,在您的程序中可以这样: HICON hMyIcon = LoadIcon( hInstance, MYICON ); LoadI

69、con( )和其它装载资源的 API 函数能够通过检查高位的值来判断传递给它的参数是一个整数, 还是一个指向字符串的指针。如果高位是 0( 针对整数的值小于等于 65535 ),就把它当作一个资源的 ID 值,这种方式限制了资源的 ID 值只能小于 65535, 除非您有很多很多资源文件, 否则应该没什么问题的。 如果高位不是 0,那就把它当作一个指向资源名称的指针。千万别依靠 API 来做这种检查,除非在说明文档中已经详细说明。 例如,对菜单命令,像 ID_FILE_EXIT 就无效,因为它只能为整型值。 Win32 APIWin32 API 编程指南编程指南( ( 6 6 ) ) 菜单和图

70、标菜单和图标 范例:menu_one 本文主要讲述如何向窗口中添加基本的菜单。通常我们都使用预先创建好的菜单资源,一个.rc 文件,它会被编译、链接到.exe 程序中。这通常是与编译器密切相关的(译者注:指资源编辑器),商业编译器大多会提供可以用来创建菜单的资源编辑器。但在本例中,我会直接讲述.rc 文件的内容,使您可以自己来添加。我通常还会创建一个.h 文件,包含控件和菜单选项的标识符等等,在.rc 文件和.c 文件中需要包含它。 在本例中,您可以在 simple_window 代码的基础上,按照说明一步步的添加代码。 首先,.h 文件通常命名为resource.h #define IDR_

71、MYMENU 101 #define IDI_MYICON 201 #define ID_FILE_EXIT 9001 #define ID_STUFF_GO 9002 这里没有太多的内容,我们的菜单也很简单。这里的标识符和数值您完全可以自己设定。接下来,我们来创建.rc 文件。 #include resource.h IDR_MYMENU MENU BEGIN POPUP &File BEGIN MENUITEM E&xit, ID_FILE_EXIT END POPUP &Stuff BEGIN MENUITEM &Go, ID_STUFF_GO MENUITEM G&o somewher

72、e else, 0, GRAYED END END IDI_MYICON ICON menu_one.ico 根据您所使用的开发环境的不同,将.rc 文件添加到您的工程中去或添加到 makefile 中去。 您还要将 #include resource.h 添加到源文件 (.c) 中, 这样, 菜单命令标识符和菜单资源 ID 就有定义了。 将菜单和图标关联到窗口上的最简单的方法就是在注册窗口类的时候指定它们,如下所示: wc.lpszMenuName = MAKEINTRESOURCE( IDR_MYMENU ); wc.hIcon = LoadIcon( GetModuleHandle( N

73、ULL ), MAKEINTRESOURCE( IDI_MYICON ) ); wc.hIconSm=( HICON )LoadImage( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDI_MYICON ), IMAGE_ICON, 16, 16, 0 ); 试试看有什么变化。您的窗口现在包含一个 File 和一个 Stuff 菜单了,并且在它们的下面还有各自的子菜单。当然,这里假定您的.rc 文件已经正确的编译、链接到程序中了。 此时,窗口左上角和任务栏上的小图标显示的应该是我们自己指定的图标了。如果我们按下 Alt+Tab 组合键,在应用程序列表

74、窗口中就会显示我们指定的图标的大图标。 我已经展示了用 LoadIcon( )函数来加载图标,这样做也很简单,然而它只能加载默认的分辨率是 32*32的图标,如果想要加载小图标,我们就需要用 LoadImage( )函数了。需要说明一点,图标和资源能够包含多个图像,既然这样,我就可以加载两个大小不同的图标了。 范例:menu_two 使用菜单资源的一种可行的方法是在程序运行之前创建( 或当程序运行时创建 )。这需要多做一些程序设计的工作,但这却增加了灵活性而且有的时候也很有必要这样做。 您也可以使用不是以资源形式存储的图标,将图标作为一个单独的文件来存储,在运行的时候加载。这也赋予了您 允许用

75、户自己定制图标和后面将要讨论的对话框的权利。 以 simple_window 为基础,不要添加.h 和.rc 文件。我会讲述如何处理 WM_CREATE 消息,并且向我们的窗口中添加一个菜单。 #define ID_FILE_EXIT 9001 #define ID_STUFF_GO 9002 将这两个 ID 添加到.c 文件的开头,#include 语句的后面。然后,将下面的代码添加到 WM_HANDLE 消息的处理语句中, case WM_CREATE: HMENU hMenu, hSubMenu; HICON hIcon, hIconSm; hMenu = CreateMenu( );

76、hSubMenu = CreatePopupMenu( ); AppendMenu( hSubMenu, MF_STRING, ID_FILE_EXIT, E&xit ); AppendMenu( hMenu, MF_STRING | MF_POPUP, ( UINT )hSubMenu, &File ); hSubMenu = CreatePopupMenu( ); AppendMenu( hSubMenu, MF_STRING, ID_STUFF_GO, &Go ); AppendMenu( hMenu, MF_STRING | MF_POPUP, ( UINT )hSubMenu, &S

77、tuff ); SetMenu( hwnd, hMenu ); hIcon=LoadImage( NULL,menu_two.ico,IMAGE_ICON,32,32,LR_LOADFROMFILE ); if ( hIcon ) SendMessage( hwnd, WM_SETICON, ICON_BIG, ( LPARAM )hIcon ); else MessageBox( hwnd,Could not load large icon!,Error,MB_OK|MB_ICONERROR ); hIconSm=LoadImage( NULL,menu_two.ico,IMAGE_ICON

78、,16,16,LR_LOADFROMFILE ); if ( hIconSm ) SendMessage( hwnd, WM_SETICON, ICON_SMALL, ( LPARAM )hIconSm ); else MessageBox( hwnd,Could not load small icon!,Error,MB_OK|MB_ICONERROR ); break; 用这种方法创建的菜单和我们直接创建资源文件并加载到窗口中的效果基本相同。 同窗口相关联的菜单在窗口关闭时会自动的移除,所以我们不用担心无法处理。如果想直接操作,可以用 GetMenu( )函数和DestroyMenu( )

79、函数。 处理图标的代码相当的简单,我们调用了两次 LoadImage( )函数,分别加载 16*16 和 32*32 尺寸的图标。但不能用 LoadIcon( )函数,因为它只能加载资源,不能加载文件。此处的实例句柄参数为 NULL,因为我们并没有从程序模块加载资源,而是用一个我们想要加载的图标文件的名字代替了资源 ID。最后,我们传递了一个 LR_LOADFROMFILE 标志指明我们传递给函数的字符串是文件的名字而不是资源的名字。 如果函数调用成功,我们就在窗口的 WM_SETICON 消息中处理图标了;如果失败,就会弹出一个对话框提示我们有错误了。 注意注意:如果图标文件不在当前程序的工

80、作目录,LoadImage( )函数将会失败。如果你用的是 VC+并且是用 IDE,当前程序的工作目录就是工程文件保存的位置。如果您是从资源管理器或者命令提示符的 Debug 或Release 目录运行程序,您就要将图标文件复制到那个目录下面,这样程序才能找到图标文件。如果这一切您都照做了,仍然还有问题,那就显式指明图标文件的绝对路径,如:C:PathToIcon.ico。 哈!我们已经创建了菜单,现在该让它做点什么了。这很简单,我们要做的仅仅是处理 WM_COMMAND 消息而已。当然,我们也许要检验一下接收到的并且要处理的命令( 译者注:就是指根据菜单中的不同选项执行不同的操作 )。现在的

81、窗口过程可能像这样: LRESULT CALLBACK WndProc( HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam ) switch( Message ) case WM_CREATE: HMENU hMenu, hSubMenu; hMenu = CreateMenu( ); hSubMenu = CreatePopupMenu( ); AppendMenu( hSubMenu, MF_STRING, ID_FILE_EXIT, E&xit ); AppendMenu( hMenu, MF_STRING | MF_POPUP,

82、 ( UINT )hSubMenu, &File ); hSubMenu = CreatePopupMenu( ); AppendMenu( hSubMenu, MF_STRING, ID_STUFF_GO, &Go ); AppendMenu( hMenu, MF_STRING | MF_POPUP, ( UINT )hSubMenu, &Stuff ); SetMenu( hwnd, hMenu ); hIcon = LoadImage( NULL, menu_two.ico, IMAGE_ICON, 32, 32, LR_LOADFROMFILE ); if ( hIcon ) Send

83、Message( hwnd, WM_SETICON, ICON_BIG, ( LPARAM )hIcon ); else MessageBox( hwnd, Could not load large icon!, Error, MB_OK | MB_ICONERROR ); hIconSm = LoadImage( NULL, menu_two.ico, IMAGE_ICON, 16, 16, LR_LOADFROMFILE ); if ( hIconSm ) SendMessage( hwnd, WM_SETICON, ICON_SMALL, ( LPARAM )hIconSm ); els

84、e MessageBox( hwnd, Could not load small icon!, Error, MB_OK | MB_ICONERROR ); break; case WM_COMMAND: switch( LOWORD( wParam ) ) case ID_FILE_EXIT: break; case ID_STUFF_GO: break; break; case WM_CLOSE: DestroyWindow( hwnd ); break; case WM_DESTROY: PostQuitMessage( 0 ); break; default: return DefWi

85、ndowProc( hwnd, Message, wParam, lParam ); return 0; 如您所见, 我已经完全处理了 WM_COMMAND 消息, 并且它还包含另一个 switch( )语句。 这个 switch( )是针对 wParam 的低字的,WM_COMMAND 消息的 wParam 域的低字节部分包含了发送消息的控件或者菜单的 ID。 很显然,我们想让 Exit 菜单选项来关闭程序。所以在 WM_COMMAND 中,ID_FILE_EXIT 可以用下面的代码来处理: PostMessage( hwnd, WM_CLOSE, 0, 0 ); 现在的 WM_COMMAN

86、D 消息处理代码可能会是这样: case WM_COMMAND: switch( LOWORD( wParam ) ) case ID_FILE_EXIT: PostMessage( hwnd, WM_CLOSE, 0, 0 ); break; case ID_STUFF_GO: break; break; 我将另一个菜单命令 ID_STUFF_GO 留给您自己来处理。 程序文件图标程序文件图标 您可能注意到了,menu_one.exe 的图标和我们创建并添加到程序中的资源文件相同,而 menu_two.exe 的图标却不是这样的,这是因为我们在第二个程序中加载的是外部文件。Windows 资

87、源管理器在程序文件中只是简单的显示第一个图标( 数字 ID 指定的 ),而我们只有一个图标,结果就是这种显示了。如果您想要程序显示特定的程序文件图标, 只要将它做为一个资源加载, 并给它一个较小的 ID 就可以了, 像第一个例子一样。您也可以选择为窗口加载不同的图标,而不需要在程序中引用这些文件。 Win32 APIWin32 API 编程指南编程指南( ( 7 7 ) ) 对话框,图形界面的有力工具对话框,图形界面的有力工具 范例:dlg_one 几乎没有哪个 Windows 程序不用到对话框。 随便找一个文本编辑器或者其他类型的编辑器, 点击 “文件”-“打开”,都会打开一个对话框,这个对

88、话框提示您选择要打开的文件。 对话框并不仅仅局限于标准的打开文件操作中, 它的外观和功能可以根据您的喜好随意设置。 它的另一个魅力所在就是,您可以基于对话框快速的创建布置一个图形界面( Graphic User Interface )的程序,减少了您手工编写的代码量。 有一点需要牢记,对话框也是窗口。它与“普通”窗口的区别在于系统针对对话框默认的做了许多额外的工作,例如创建并初始化控件,处理 tab 键的切换顺序等。几乎所有在“普通”窗口上能够正常运行的 API函数,在对话框中也都能运行,而且运行得很好! 第一步是创建对话框资源。和创建其它资源一样,要如何创建,取决于您所使用的编译器/IDE。

89、下面,我演示给您.rc 文件中对话框的基本内容,稍后您可以将它添加到您自己的工程中。 IDD_ABOUT DIALOG DISCARDABLE 0, 0, 239, 66 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION My About Box FONT 8, MS Sans Serif BEGIN DEFPUSHBUTTON &OK,IDOK,174,18,50,14 PUSHBUTTON &Cancel,IDCANCEL,174,35,50,14 GROUPBOX About this program.,ID

90、C_STATIC,7,7,225,52 CTEXT An example program showing how to use Dialog Boxesrnrnby theForger, IDC_STATIC,16,18,144,33 END 在第一行的 IDD_ABOUTDLG 是资源的 ID。DIALOG 是资源的类型,四个数字分别是左,上,宽和高的坐标( 译者注:左,上两个数字确定了对话框的左上顶点,通过宽和高,即可确定整个对话框的位置 )。这里的度量单位不是像素, 而是基于系统( 或用户选择的 )字体大小的对话框单位。 如果您选择了一个大的字体,对话框就会大一些,如果您用的是小字体,对

91、话框也相应的会小一些。这一点很重要,因为这样能够保证所有的控件都能以当前字体大小正常的显示控件的文本内容。您可以用 MapDialogRect( ) 在运行时将对话框单位转化成像素单位。 DISCARDABLE 标识通知系统, 当资源未被使用的时候, 可以将它从内存中转存到磁盘上,以节省系统资源( 实际上没什么作用 )。 第二行开始的 STYLE 以及后面的窗口类型指明了创建的对话框的类型。这些在帮助文档的 CreateWindow( ) 函数中有详细的说明。为了能够使用这些预定义常量,需要在 .rc 文件中添加 #include windows.h 语句,或者在 VC+的环境中包含 winr

92、es.h 或 afxres.h 。如果您用的是资源编辑器,这些文件在需要的时候,会被自动的包含进来。 CAPTION 这一行不需多讲。 字体一行指定了对话框所使用的字体的尺寸,名称。这个根据计算机的不同,以及个人的爱好可能会有不同的选择,通常不需要过多考虑。 接下来,我们指定了对话框上创建的控件的列表。 DEFPUSHBUTTON &OK,IDOK,174,18,50,14 上面这一行创建了 OK 按钮。此处的 & 符号类似于菜单中的,会在紧接着的字母“O”下面加一条下划线,用户可以通过 Alt+O 组合键来激活这个按钮( 这就是刚才所说的操作系统默认处理的一部分 )。IDOK 是控件的标识符

93、。IDOK 是预先定义的,不需要我们自己再重新定义。最后的四个数字分别是左,上,宽和高,单位都是对话框单位。 以上这些都是纯的理论。通常,您可能用资源编辑器来创建对话框,但了解一些如何从文本创建也是很必要的,特别是当您没有一个可视化的资源编辑器的时候。 有两个控件的 ID 值都是 IDC_STATIC ( 其值为-1 ),这个值指明我们永远不会操作这两个控件,所以它们也不需要标识符。但这并不影响给它们赋一个 ID 值,资源编辑器会自动的完成了这一工作。 在静态控件文本中 “rn” 是一个回车换行符,作用是切换到一个新行。 好的! 将这些添加到您的.rc 文件中, 我们还需要再写一个对话框过程来

94、处理针对对话框的消息。 别担心,这里没什么新的东西,几乎和我们的窗口过程差不多( 但不完全相同 )。 BOOL CALLBACK AboutDlgProc( HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam ) switch( Message ) case WM_INITDIALOG: return TRUE; case WM_COMMAND: switch( LOWORD( wParam ) ) case IDOK: EndDialog( hwnd, IDOK ); break; case IDCANCEL: EndDialog( h

95、wnd, IDCANCEL ); break; break; default: return FALSE; return TRUE; 对话框过程和窗口过程只有很少的不同点。 首先, 我们未处理的消息, 并不需要调用 DefWindowProc( ) 函数来处理。对话框会自动地为您处理(如果您自己处理了,很可能会弄巧成拙)。 其次,通常情况下,我们不处理的消息,返回值为 FALSE,处理的消息,返回值为 TRUE,除非消息指明了其它特定的返回值。注意我们上面的代码,默认的消息处理是什么也不做并且返回 FALSE,而处理的消息会跳出 switch( ) 语句,返回值为 TRUE。 再次,我们并不调

96、用 DestroyWindow( ) 函数来关闭对话框,而是调用 EndDialog( ) 来执行这个操作。它的第二个参数是返回给所有调用 DialogBox( ) 函数的值。 最后,我们不是在 WM_CREATE 消息中,而是在处理 WM_INITDIALOG 消息中,执行一些对话框显示之前的初始化工作,然后返回值为 TRUE ,并将键盘的输入焦点设置为默认的控件。( 实际上也可以处理 WM_CREATE 消息,但是它是在任何控件都没有创建之前执行的,所以不能对控件进行操作。在 WM_INITDIALOG 消息中,控件都已经创建完毕了 ) 以上这些已经足够了,让我们一起创建一个对话框吧。 c

97、ase WM_COMMAND: switch( LOWORD( wParam ) ) case ID_HELP_ABOUT: int ret = DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_ABOUT ), hwnd, AboutDlgProc ); if ( ret = IDOK ) MessageBox( hwnd, Dialog exited with IDOK., Notice, MB_OK | MB_ICONINFORMATION ); else if ( ret = IDCANCEL ) MessageBox(

98、hwnd, Dialog exited with IDCANCEL., Notice, MB_OK | MB_ICONINFORMATION ); else if ( ret = -1 ) MessageBox( hwnd, Dialog failed!, Error, MB_OK | MB_ICONINFORMATION ); break; / Other menu commands. break; 上面就是我创建“关于”对话框的代码,或许您已经猜到了,应该将这段代码添加到您自己的程序的 WM_COMMAND 消息中去, 如果您对这么做还有什么疑问, 请返回菜单那一节看一看。 ID_HELP

99、_ABOUT 是 帮助-关于 菜单选项的标识符。 既然我们想要主窗口的菜单选项创建一个对话框,当然要将这段代码放在主窗口的窗口过程函数 WndProc( ) 中,而不是放在对话框过程函数中。 然后,我将 DialogBox( ) 的返回值赋值给 ret,可以通过这个值判断按下的按钮是对话框内的 Esc 还是 Enter 。这也演示了如何应用对话框的返回值,并通过这个值判断(对话框运行的)成功、失败、用户的选择以及其它您希望返回给对话框过程函数的信息。 DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_ABOUT ), hwnd,

100、AboutDlgProc ); 这是唯一应该特别关注的内容。您可以将这条语句放在任何您想显示对话框的位置。IDD_ABOUT 是对话框资源的 ID,hwnd 是对话框父窗口( 也有的书中成为上层窗口 )的句柄。AboutDlgProc( ) 显然是控制对话框的对话框过程函数。 大概就这些吧! 细心的读者到这里可能会有疑问了, 如果对话框函数 DialogBox( ) 直到对话框关闭才返回, 那当它运行的时候,我们就不能处理其它消息了啊,这怎么办啊?解决的方法很巧妙,对话框有自己的消息循环,当对话框运行的时候,( 主窗口的 )消息循环是独立的,默认的循环是由 Windows 来处理的。这个默认的

101、消息循环仍然处理一些常见的事件,例如,当 Tab 键按下的时候,在控件之间切换键盘焦点。 应用 DialogBox( ) 函数的另一个效果是,当对话框显示的时候,主窗体就一直不可用。这种效果有时候是我们想要的,而有时候不是,例如当我们想把对话框当作一个浮动的工具栏的时候。既然这样,我们就希望对话框和我们的主窗体能够互相协调运行,这也是我们下一节要讨论的焦点。 Win32 APIWin32 API 编程指南编程指南( ( 8 8 ) ) 非模态对话框非模态对话框 范例:dlg_two 首先, 让我们来看一下 DialogBox( )函数的姊妹函数 CreateDialog( )。 这两个函数的区

102、别在于, DialogBox( )函数自己处理消息循环并且直到对话框关闭函数才会返回,而 CreateDialog( )函数的行为更像一个用CreateWindowEx( )函数创建的窗口,即从消息循环中取出消息,处理结束后立即返回。这也就是非模态的含义,而 DialogBox( )函数创建的是模态对话框。 您可以像前面的对话框范例一样创建对话框资源,或许您也想为“工具窗口”设置扩展风格,使它在工具栏上有一个小的标题栏。我创建的对话框资源文件如下所示: IDD_TOOLBAR DIALOGEX 0, 0, 98, 52 STYLE DS_MODALFRAME | WS_POPUP | WS_C

103、APTION EXSTYLE WS_EX_TOOLWINDOW CAPTION My Dialog Toolbar FONT 8, MS Sans Serif BEGIN PUSHBUTTON &Press This Button,IDC_PRESS,7,7,84,14 PUSHBUTTON &Or This One,IDC_OTHER,7,31,84,14 END 您可能注意到了,资源编辑器用 DIALOGEX 代替了 DIALOG,表明在我们的对话框中要使用扩展风格( EXSTYLE )。 接下来, 我们想要在程序运行的时候创建对话框, 并且想让它立即显示出来, 所以我们要在 WM_CRE

104、ATE消息中添加代码。我们声明了一个全局变量的窗口句柄用来保存 CreateDialog( )函数的返回值,稍后我们会用到这个句柄。DialogBox( )不能返回给我们一个句柄,因为当它返回的时候,窗口已经销毁了。 HWND g_hToolbar = NULL; case WM_CREATE: g_hToolbar=CreateDialog( GetModuleHandle( NULL ),MAKEINTRESOURCE( IDD_TOOLBAR ), hwnd, ToolDlgProc ); if ( g_hToolbar != NULL ) ShowWindow( g_hToolbar,

105、 SW_SHOW ) else MessageBox( hwnd, CreateDialog returned NULL, Warning!, MB_OK | MB_ICONINFORMATION ); break; 我们检验了返回值,这是一个好习惯,如果返回值有效( 非 NULL ),我们调用 ShowWindow( )函数来显示窗口,对于 DialogBox( )函数,这步是不必要的,系统会为我们自动调用 ShowWindow( )函数。 现在,为我们的工具栏创建一个对话框过程。 BOOL CALLBACK ToolDlgProc( HWND hwnd, UINT Message, WPA

106、RAM wParam, LPARAM lParam ) switch( Message ) case WM_COMMAND: switch( LOWORD( wParam ) ) case IDC_PRESS: MessageBox( hwnd, Hi!, This is a message, MB_OK | MB_ICONEXCLAMATION ); break; case IDC_OTHER: MessageBox( hwnd, Bye!, This is also a message, MB_OK | MB_ICONEXCLAMATION ); break; break; default

107、: return FALSE; return TRUE; 绝大多数对话框的消息处理规则对 CreateDialog( )函数和 DialogBox( )函数创建的对话框都同样适用, 例如不用调用 DefWindowProc( )函数,不处理的消息返回 FALSE,处理的消息返回 TRUE。 有一点小小的改变是,在非模态对话框中,我们不调用 EndDialog( )函数,而是像窗口程序一样调用DestroyWindow( )。既然这样,当主窗口销毁的时候,我们也销毁对话框。在主窗口的窗口过程中. case WM_DESTROY: DestroyWindow( g_hToolbar ); Post

108、QuitMessage( 0 ); break; 最后一点,我们想要通过选择来显示和隐藏 toolbar 对话框。所以需要在菜单中添加了两个标签来实现,代码如下: case WM_COMMAND: switch( LOWORD( wParam ) ) case ID_DIALOG_SHOW: ShowWindow( g_hToolbar, SW_SHOW break; case ID_DIALOG_HIDE: ShowWindow( g_hToolbar, SW_HIDE ); break; /. other command handlers break; 您可以通过资源编辑器或者手工来创建菜

109、单,如果不能( 通常是这样 ),参照一下这份指南中提供的dlg_two 范例程序的代码。 现在运行程序,您就能够同时操作对话框窗口和主窗口了。 如果您想在主窗口和新创建的窗口之间用 tab 键来切换,会发现这根本不可能。而且连 Alt+P 和 Alt+O 都不可用(译者注:此处是指,当焦点在主窗口的时候,不能用 tab 切换到新建的窗口或者用 Alt+P 和 Alt+O 切换到新创建的窗口上的按钮)。为什么会这样呢?原因在于 DialogBox( )函数自己处理消息循环并且会默认的处理这些消息, 而 CreateDdialog( )函数不会。 但我们可以自己来处理, 在我们的消息循环中调用 I

110、sDialogMessage( )函数,它会帮我们处理这些消息。 while( GetMessage( &Msg, NULL, 0, 0 ) ) if ( !IsDialogMessage( g_hToolbar, &Msg ) ) TranslateMessage( &Msg ); DispatchMessage( &Msg ); 在上面的程序中,我们首先将消息传递给 IsDialogMessage( )函数,如果这个消息是针对工具栏(就是我所说的那个新创建的窗口)的,系统就会默认处理这个消息然后返回 TRUE。正是在这种情况下,消息已经被处理了,我们就不用再调用 TranslateMess

111、age( )函数或者 DispatchMessage( )函数了。即使消息是针对其它的窗口的,我们也这样处理。 有时候为了给窗口添加类似对话框的行为,IsDialogMessage( )函数也用在没有对话框的窗口中,而且并不会有什么不良的后果。牢记,对话框也是一个窗口,大多数(并不是全部)的对话框 API 函数都可以在窗口上正常运行。 对于非模态对话框,了解这些也差不多了。但您可能还会有一个疑问,如果有多个工具栏.,那要怎么办呢?一个可行的解决办法就是用一个列表(可以是数组,一个 STL 的 std:list 或者类似的结构),然后在消息循环中将列表中的元素分别传递给 IsDialogMessage( )函数,直到找到匹配的那一个,如果没有匹配的,就正常的处理消息。这是一个很普遍的编程问题,不是 Win32 特有的,留给读者自己思考。

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 建筑/环境 > 施工组织

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号