跟我一起学图形编程
作者:姚明 联系方式:alanvincentmail@gmail.com 2011年1月22日 18:46:14
欢迎使用我的图形学教程。我是计算机专业的学生,对图形图像技术有浓厚的兴趣,就读期间广泛的涉及相关知识,但始终没有深入研究。原因很简单,我认为广度可以决定深度,大学期间应博学,不宜专于细节。现在毕业了,我选择了图形学作为自己深入研究的方向。
关于图形学,我也算是初学者,也许,与大家不同的是,在深入研究之前,我做了充分的知识准备,我计划从点的绘制开始,实现一套完整的三维渲染流水线。朋友们,跟我一起学吧,让我们披荆斩棘,一起攀上这座美丽的高峰!
下面,开始我们的第一步,创建一个windows下的窗口程序。
理论:
Windows 程序中,在写图形用户界面时需要调用大量的标准 Windows Gui 函数。其实这对用户和程序员来说都有好处,对于用户,面对的是同一套标准的窗口,对这些窗口的操作都是一样的,所以使用不同的应用程序时无须重新学习操作。对程序员来说,这些 Gui 源代码都是经过了微软的严格测试,随时拿来就可以用的。当然至于具体地写程序对于程序员来说还是有难度的。为了创建基于窗口的应用程序,必须严格遵守规范。作到这一点并不难,只要用模块化或面向对象的编程方法即可。
下面我就列出在桌面显示一个窗口的几个步骤:
- 得到您应用程序的句柄(必需);
- 得到命令行参数(如果您想从命令行得到参数,可选);
- 注册窗口类(必需,除非您使用 Windows 预定义的窗口类,如 MessageBox 或 dialog box;
- 产生窗口(必需);
- 在桌面显示窗口(必需,除非您不想立即显示它);
- 刷新窗口客户区;
- 进入无限的获取窗口消息的循环;
- 如果有消息到达,由负责该窗口的窗口回调函数处理;
- 如果用户关闭窗口,进行退出处理。
相对于单用户的 DOS 下的编程来说,Windows 下的程序框架结构是相当复杂的。但是 Windows 和 DOS 在系统架构上是截然不同的。Windows 是一个多任务的操作系统,故系统中同时有多个应用程序彼此协同运行。这就要求 Windows 程序员必须严格遵守编程规范,并养成良好的编程风格。
Windows 中的文本是一个GUI(图形用户界面)对象。每一个字符实际上是由许多的像素点组成,这些点在有笔画的地方显示出来,这样就会出现字符。这也是为什么我说“绘制”字符,而不是写字符。通常您都是在您应用程序的客户区“绘制”字符串(尽管您也可以在客户区外“绘制”)。Windows 下的“绘制”字符串方法和 Dos 下的截然不同,在 Dos 下,您可以把屏幕想象成 85 x 25 的一个平面,而 Windows 下由于屏幕上同时有几个应用程序的画面,所以您必须严格遵从规范。Windows 通过把每一个应用程序限制在他的客户区来做到这一点。当然客户区的大小是可变的,您随时可以调整。
在您在客户区“绘制”字符串前,您必须从 Windows 那里得到您客户区的大小,确实您无法像在 DOS 下那样随心所欲地在屏幕上任何地方“绘制”,绘制前您必须得到 Windows 的允许,然后 Windows 会告诉您客户区的大小,字体,颜色和其它 GUI 对象的属性。您可以用这些来在客户区“绘制”。
什么是“设备环境”(DC)呢? 它其实是由 Windows 内部维护的一个数据结构。一个“设备环境”和一个特定的设备相连。像打印机和显示器。对于显示器来说,“设备环境”和一个个特定的窗口相连。
“设备环境”中的有些属性和绘图有关,像:颜色,字体等。您可以随时改动那些缺省值,之所以保存缺省值是为了方便。您可以把“设备环境”想象成是Windows 为您准备的一个绘图环境,而您可以随时根据需要改变某些缺省属性。
当应用程序需要绘制时,您必须得到一个“设备环境”的句柄。通常有几种方法。
- 在 WM_PAINT 消息中使用 call BeginPaint
- 在其他消息中使用 call GetDC
- call CreateDC 建立你自己的 DC
您必须牢记的是,在处理单个消息后你必须释放“设备环境”句柄。不要在一个消息处理中获得 “设备环境”句柄,而在另一个消息处理中在释放它。
我们在Windows 发送 WM_PAINT 消息时处理绘制客户区,Windows 不会保存客户区的内容,它用的是方法是“重绘”机制(譬如当客户区刚被另一个应用程序的客户区覆盖),Windows 会把 WM_PAINT 消息放入该应用程序的消息队列。重绘窗口的客户区是各个窗口自己的责任,您要做的是在窗口过程处理 WM_PAINT 的部分知道绘制什么和何如绘制。
您必须了解的另一个概念是“无效区域”。Windows 把一个最小的需要重绘的正方形区域叫做“无效区域”。当 Windows 发现了一个”无效区域“后,它就会向该应用程序发送一个 WM_PAINT 消息,在 WM_PAINT 的处理过程中,窗口首先得到一个有关绘图的结构体,里面包括无效区的坐标位置等。您可以通过调用BeginPaint 让“无效区”有效,如果您不处理 WM_PAINT 消息,至少要调用缺省的窗口处理函数 DefWindowProc ,或者调用 ValidateRect 让“无效区”有效。否则您的应用程序将会收到无穷无尽的 WM_PAINT 消息。
下面是响应该消息的步骤:
- 取得“设备环境”句柄
- 绘制客户区
- 释放“设备环境”句柄
注意,您无须显式地让“无效区”有效,这个动作由 BeginPaint 自动完成。您可以在 BeginPaint 和 Endpaint 之间,调用所有的绘制函数。几乎所有的GDI 函数都需要“设备环境”的句柄作为参数。
内容:
下面是我们简单的窗口程序的源代码。在进入复杂的细节前,我将提纲挈领地指出几点要点:
- 您应当把程序中要用到的所有常量和结构体的声明放到一个头文件中,并且在源程序的开始处包含这个头文件。这么做将会节省您大量的时间,也免得一次又一次的敲键盘。您也可以定义您自己的常量和结构体,但最好把它们放到独立的头文件中。
- 在其它地方运用头文件中定义函数原型,常数和结构体时,要严格保持和头文件中的定义一致,包括大小写。在查询函数定义时,这将节约您大量的时间;
- 如果想详细系统的学习Windows编程,可以参考《windows程序设计》,点击下载。
- 如果你的系统中没有安装VC, 或不会使用VC,我准备了精简版本,只有10MB左右,无需复杂操作,也能编译本教程中的代码,生成程序,点击下载。
- 如果你对下面的代码视如天书,那么请先看《谭浩强C语言教程》,务必熟读,点击下载。
1/**//*------------------------------------------------------------------------
2 HELLOWIN.CPP -- Displays "你好, 欢迎使用YM的图形学教程!" in client area
3 (c) Charles Petzold, 1998
4-----------------------------------------------------------------------*/
5#include <windows.h>
6
7LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
8
9int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
10 PSTR szCmdLine, int iCmdShow)
11{
12static TCHAR szAppName[] = TEXT ("HelloWin") ;
13 HWND hwnd ;
14 MSG msg ;
15 WNDCLASS wndclass ;
16 wndclass.style = CS_HREDRAW | CS_VREDRAW ;
17 wndclass.lpfnWndProc = WndProc ;
18 wndclass.cbClsExtra = 0 ;
19 wndclass.cbWndExtra = 0 ;
20 wndclass.hInstance = hInstance ;
21 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
22 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
23 wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
24 wndclass.lpszMenuName = NULL ;
25 wndclass.lpszClassName= szAppName ;
26 RegisterClass (&wndclass);
27 hwnd = CreateWindow( szAppName, // window class name
28 TEXT ("The Hello Program"), // window caption
29 WS_OVERLAPPEDWINDOW, // window style
30 CW_USEDEFAULT,// initial x position
31 CW_USEDEFAULT,// initial y position
32 CW_USEDEFAULT,// initial x size
33 CW_USEDEFAULT,// initial y size
34 NULL, // parent window handle
35 NULL, // window menu handle
36 hInstance, // program instance handle
37 NULL) ; // creation parameters
38 ShowWindow (hwnd, iCmdShow) ;
39 UpdateWindow (hwnd) ;
40 while (GetMessage (&msg, NULL, 0, 0))
41 {
42 TranslateMessage (&msg) ;
43 DispatchMessage (&msg) ;
44 }
45 return msg.wParam ;
46}
47LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
48{
49 HDC hdc ;
50 PAINTSTRUCT ps ;
51 RECT rect ;
52 switch (message)
53 {
54 case WM_PAINT:
55 hdc = BeginPaint (hwnd, &ps) ;
56 GetClientRect (hwnd, &rect) ;
57 DrawText (hdc, TEXT ("你好, 欢迎使用YM的图形学教程! "), -1, &rect,
58 DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
59 EndPaint (hwnd, &ps) ;
60 return 0 ;
61 case WM_DESTROY:
62 PostQuitMessage (0) ;
63 return 0 ;
64 }
65 return DefWindowProc (hwnd, message, wParam, lParam) ;
66}
67
分析:
看到一个简单的 Windows 程序有这么多行,您是不是有点想撤? 但是您必须要知道的是上面的大多数代码都是模板而已,模板的意思即是指这些代码对差不多所有标准 Windows 程序来说都是相同的。在写 Windows 程序时您可以把这些代码拷来拷去,当然把这些重复的代码写到一个库中也挺好。其实真正要写的代码集中在 WinMain 中。这和一些 C 编译器一样,无须要关心其它杂务,集中精力于 WinMain 函数。唯一不同的是 C 编译器要求您的源代码有必须有一个函数叫 WinMain。否则 C 无法知道将哪个函数和有关的前后代码链接。相对C,汇编语言提供了较大的灵活性,它不强行要求一个叫 WinMain 的函数。
下面我们开始分析,您可得做好思想准备,这可不是一件太轻松的活。
HELLOWIN至少呼叫了18个Windows函数。下面以它们在HELLOWIN中出现的次序列出这些函数以及各自的简明描述:
- LoadIcon 加载图标供程序使用。
- LoadCursor 加载鼠标光标供程序使用。
- GetStockObject 取得一个图形对象(在这个例子中,是取得绘制窗口背景的画刷对象)。
- RegisterClass 为程序窗口注册窗口类别。
- MessageBox 显示消息框。
- CreateWindow 根据窗口类别建立一个窗口。
- ShowWindow 在屏幕上显示窗口。
- UpdateWindow 指示窗口自我更新。
- GetMessage 从消息队列中取得消息。
- TranslateMessage 转译某些键盘消息。
- DispatchMessage 将消息发送给窗口消息处理程序。
- PlaySound 播放一个声音文件。
- BeginPaint 开始绘制窗口。
- GetClientRect 取得窗口显示区域的大小。
- DrawText 显示字符串。
- EndPaint 结束绘制窗口。
- PostQuitMessage 在消息队列中插入一个「退出程序」消息。
- DefWindowProc 执行内定的消息处理。
这些函数均在Platform SDK文件中说明,并在不同的表头文件中声明,其中绝大多数声明在WINUSER.H中。
WinMain函数共有4个参数:应用程序的实例句柄,该应用程序的前一实例句柄,命令行参数串指针和窗口如何显示。Win32 没有前一实例句柄的概念,所以第二个参数总为0。之所以保留它是为了和 Win16 兼容的考虑,在 Win16下,如果 hPrevInst 是 NULL,则该函数是第一次运行。特别注意:您不用必须申明一个名为 WinMain 函数,事实上在这方面您可以完全作主,您甚至无须有一个和 WinMain 等同的函数。您只要把 WinMain 中的代码拷到GetCommandLine 之后,其所实现的功能完全相同。在 WinMain 返回时,产生一个返回值。然后在应用程序结束时通过 ExitProcess 函数把该返回码传递给 Windows 。
1 wndclass.style = CS_HREDRAW | CS_VREDRAW ;
2 wndclass.lpfnWndProc = WndProc ;
3 wndclass.cbClsExtra = 0 ;
4 wndclass.cbWndExtra = 0 ;
5 wndclass.hInstance = hInstance ;
6 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
7 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
8 wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
9 wndclass.lpszMenuName = NULL ;
10 wndclass.lpszClassName= szAppName ;
11 RegisterClass (&wndclass);
12
上面几行从概念上说确实是非常地简单。只要几行指令就可以实现。其中的主要概念就是窗口类(window class),一个窗口类就是一个有关窗口的规范,这个规范定义了几个主要的窗口的元素,如:图标、光标、背景色、和负责处理该窗口的函数。您产生一个窗口时就必须要有这样的一个窗口类。如果您要产生不止一个同种类型的窗口时,最好的方法就是把这个窗口类存储起来,这种方法可以节约许多的内存空间。也许今天您不会太感觉到,可是想想以前 PC 大多数只有 1M 内存时,这么做是非常有必要的。如果您要定义自己的创建窗口类就必须:在一个 WINDCLASS 或 WINDOWCLASSEX 结构体中指明您窗口的组成元素,然后调用 RegisterClass 或 RegisterClass ,再根据该窗口类产生窗口。对不同特色的窗口必须定义不同的窗口类。 WINDOWS有几个预定义的窗口类,譬如:按钮、编辑框等。要产生该种风格的窗口无须预先再定义窗口类了,只要包预定义类的类名作为参数调用 CreateWindow 即可。
WNDCLASS 中最重要的成员莫过于lpfnWndProc了。前缀 lpfn 表示该成员是一个指向函数的长指针。在 Win32中由于内存模式是 FLAT 型,所以没有 near 或 far 的区别。每一个窗口类必须有一个窗口过程,当 Windows 把属于特定窗口的消息发送给该窗口时,该窗口的窗口类负责处理所有的消息,如键盘消息或鼠标消息。由于窗口过程差不多智能地处理了所有的窗口消息循环,所以您只要在其中加入消息处理过程即可。下面我将要讲解 WNDCLASSEX 的每一个成员
1typedef struct tagWNDCLASS {
2 UINT style;
3 WNDPROC lpfnWndProc;
4 int cbClsExtra;
5 int cbWndExtra;
6 HINSTANCE hInstance;
7 HICON hIcon;
8 HCURSOR hCursor;
9 HBRUSH hbrBackground;
10 LPCTSTR lpszMenuName;
11 LPCTSTR lpszClassName;
12} WNDCLASS, *PWNDCLASS;
13
14CreateWindow( szAppName, // window class name
15 TEXT ("The Hello Program"), // window caption
16 WS_OVERLAPPEDWINDOW, // window style
17 CW_USEDEFAULT,// initial x position
18 CW_USEDEFAULT,// initial y position
19 CW_USEDEFAULT,// initial x size
20 CW_USEDEFAULT,// initial y size
21 NULL, // parent window handle
22 NULL, // window menu handle
23 hInstance, // program instance handle
24 NULL) ; // creation parameters
25
注册窗口类后,我们将调用CreateWindow来产生实际的窗口。请注意该函数有11个参数。
1HWND WINAPI CreateWindow(
2 __in_opt LPCTSTR lpClassName,
3 __in_opt LPCTSTR lpWindowName,
4 __in DWORD dwStyle,
5 __in int x,
6 __in int y,
7 __in int nWidth,
8 __in int nHeight,
9 __in_opt HWND hWndParent,
10 __in_opt HMENU hMenu,
11 __in_opt HINSTANCE hInstance,
12 __in_opt LPVOID lpParam
13);
14
我们来仔细看一看这些的参数:
- lpClassName:(必须)。ASCIIZ形式的窗口类名称的地址。可以是您自定义的类,也可以是预定义的类名。像上面所说,每一个应用程序必须有一个窗口类。
- lpWindowName:ASCIIZ形式的窗口名称的地址。该名称会显示在标题条上。如果该参数空白,则标题条上什么都没有。
- dwStyle:窗口的风格。在此您可以指定窗口的外观。可以指定该参数为零,但那样该窗口就没有系统菜单,也没有最大化和最小化按钮,也没有关闭按钮,那样您不得不按Alt+F4 来关闭它。最为普遍的窗口类风格是 WS_OVERLAPPEDWINDOW。 一种窗口风格是一种按位的掩码,这样您可以用“or”把您希望的窗口风格或起来。像 WS_OVERLAPPEDWINDOW 就是由几种最为不便普遍的风格或起来的。
- X,Y: 指定窗口左上角的以像素为单位的屏幕坐标位置。缺省地可指定为 CW_USEDEFAULT,这样 Windows 会自动为窗口指定最合适的位置。
- nWidth, nHeight: 以像素为单位的窗口大小。缺省地可指定为 CW_USEDEFAULT,这样 Windows 会自动为窗口指定最合适的大小。
- hWndParent: 父窗口的句柄(如果有的话)。这个参数告诉 Windows 这是一个子窗口和他的父窗口是谁。这和 MDI(多文档结构)不同,此处的子窗口并不会局限在父窗口的客户区内。他只是用来告诉 Windows 各个窗口之间的父子关系,以便在父窗口销毁是一同把其子窗口销毁。在我们的例子程序中因为只有一个窗口,故把该参数设为 NULL。
- hMenu: WINDOWS菜单的句柄。如果只用系统菜单则指定该参数为NULL。回头看一看WNDCLASSEX 结构中的 lpszMenuName 参数,它也指定一个菜单,这是一个缺省菜单,任何从该窗口类派生的窗口若想用其他的菜单需在该参数中重新指定。其实该参数有双重意义:一方面若这是一个自定义窗口时该参数代表菜单句柄,另一方面,若这是一个预定义窗口时,该参数代表是该窗口的 ID 号。Windows 是根据lpClassName 参数来区分是自定义窗口还是预定义窗口的。
- hInstance: 产生该窗口的应用程序的实例句柄。
- lpParam: (可选)指向欲传给窗口的结构体数据类型参数的指针。如在MDI中在产生窗口时传递 CLIENTCREATESTRUCT 结构的参数。一般情况下,该值总为零,这表示没有参数传递给窗口。可以通过GetWindowLong 函数检索该值。
1ShowWindow (hwnd, iCmdShow) ;
2UpdateWindow (hwnd) ;
调用CreateWindow成功后,会返回窗口句柄。我们必须保存该值以备后用。我们刚刚产生的窗口不会自动显示,所以必须调用 ShowWindow 来按照我们希望的方式来显示该窗口。接下来调用 UpdateWindow 来更新客户区。
1while (GetMessage (&msg, NULL, 0, 0))
2{
3 TranslateMessage (&msg) ;
4 DispatchMessage (&msg) ;
5}
这时候我们的窗口已显示在屏幕上了。但是它还不能从外界接收消息。所以我们必须给它提供相关的消息。我们是通过一个消息循环来完成该项工作的。每一个模块仅有一个消息循环,我们不断地调用 GetMessage 从 Windows 中获得消息。GetMessage 传递一个 MSG 结构体给 Windows ,然后 Windows 在该函数中填充有关的消息,一直到 Windows 找到并填充好消息后 GetMessage 才会返回。在这段时间内系统控制权可能会转移给其他的应用程序。这样就构成了Win16 下的多任务结构。如果 GetMessage 接收到 WM_QUIT 消息后就会返回 FALSE,使循环结束并退出应用程序。TranslateMessage 函数是一个是实用函数,它从键盘接受原始按键消息,然后解释成 WM_CHAR,在把 WM_CHAR 放入消息队列,由于经过解释后的消息中含有按键的 ASCII 码,这比原始的扫描码好理解得多。如果您的应用程序不处理按键消息的话,可以不调用该函数。DispatchMessage 会把消息发送给负责该窗口过程的函数。
1return msg.wParam
如果消息循环结束了,退出码存放在 MSG 中的 wParam中,您可以通过把它放到 eax 寄存器中传给 Windows目前 Windows 没有利用到这个结束码,但我们最好还是遵从 Windows 规范已防意外。
1LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
是我们的窗口处理函数。您可以随便给该函数命名。其中第一个参数 hWnd 是接收消息的窗口的句柄。message是接收的消息。注意message不是一个 MSG 结构,其实上只是一个 DWORD 类型数。Windows 定义了成百上千个消息,大多数您的应用程序不会处理到。当有该窗口的消息发生时,Windows 会发送一个相关消息给该窗口。其窗口过程处理函数会智能的处理这些消息。wParam 和 lParam 只是附加参数,以方便传递更多的和该消息有关的数据。
1switch (message)
2{
3 case WM_PAINT:
4 hdc = BeginPaint (hwnd, &ps) ;
5 GetClientRect (hwnd, &rect) ;
6 DrawText (hdc, TEXT ("你好, 欢迎使用YM的图形学教程!"), -1, &rect,
7 DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
8 EndPaint (hwnd, &ps) ;
9 return 0 ;
10 case WM_DESTROY:
11 PostQuitMessage (0) ;
12 return 0 ;
13}
14 return DefWindowProc (hwnd, message, wParam, lParam) ;
15
上面可以说是关键部分。这也是我们写 Windows 程序时需要改写的主要部分。此处您的程序检查 Windows 传递过来的消息,如果是我们感兴趣的消息则加以处理,处理完后,在 eax 寄存器中传递 0,否则必须调用 DefWindowProc,把该窗口过程接收到的参数传递给缺省的窗口处理函数。所有消息中您必须处理的是 WM_DESTROY,当您的应用程序结束时 Windows 把这个消息传递进来,当您的应用程序解说到该消息时它已经在屏幕上消失了,这仅是通知您的应用程序窗口已销毁,您必须自己准备返回 Windows 。在此消息中您可以做一些清理工作,但无法阻止退出应用程序。如果您要那样做的话,可以处理 WM_CLOSE 消息。在处理完清理工作后,您必须调用 PostQuitMessage,该函数会把 WM_QUIT 消息传回您的应用程序,而该消息会使得 GetMessage 返回,并在将返回值设置0,然后会结束消息循环并退回 WINDOWS。您可以在您的程序中调用 DestroyWindow 函数,它会发送一个 WM_DESTROY 消息给您自己的应用程序,从而迫使它退出。
特别注意:下面讨论的是我们以后的课程的核心部分
1 HDC hdc ;
2 PAINTSTRUCT ps ;
3 RECT rect ;
4
这些局部变量由处理 WM_PAINT 消息中的 GDI 函数调用。hdc 用来存放调用 BeginPaint 返回的“设备环境”句柄。ps 是一个 PAINTSTRUCT 数据类型的变量。通常您不会用到其中的许多值,它由 Windows 传递给 BeginPaint,在结束绘制后再原封不动的传递给 EndPaint。rect 是一个 RECT 结构体类型参数,它的定义如下:
1typedef struct _RECT {
2 LONG left;
3 LONG top;
4 LONG right;
5 LONG bottom;
6} RECT, *PRECT;
7
left 和 top 是正方形左上角的坐标。right 和 bottom 是正方形右下角的坐标。客户区的左上角的坐标是 x=0,y=0,这样对于 x=0,y=10 的坐标点就在它的下面。
1 hdc = BeginPaint (hwnd, &ps) ;
2 GetClientRect (hwnd, &rect) ;
3 DrawText (hdc, TEXT ("你好, 欢迎使用YM的图形学教程!"), -1, &rect,
4 DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
5 EndPaint (hwnd, &ps) ;
6
在处理 WM_PAINT 消息时,您调用BeginPaint函数,传给它一个窗口句柄和未初始化的 PAINTSTRUCT 型参数。调用成功后在 eax 中返回“设备环境”的句柄。下一次,调用 GetClientRect 以得到客户区的大小,大小放在 rect 中,然后把它传给 DrawText。DrawText 的语法如下:
1int DrawText(
2 HDC hDC,
3 LPCTSTR lpString,
4 int nCount,
5 LPRECT lpRect,
6 UNIT uFormat);
7
DrawText是一个高层的调用函数。它能自动处理像换行、把文本放到客户区中间等这些杂事。所以您只管集中精力“绘制”字符串就可以了。我们会在下一课中讲解低一层的函数 TextOut,该函数在一个正方形区域中格式化一个文本串。它用当前选择的字体、颜色和背景色。它处理换行以适应正方形区域。它会返回以设备逻辑单位度量的文本的高度,我们这里的度量单位是像素点。让我们来看一看该函数的参数:
- hdc: “设备环境”的句柄。
- lpString:要显示的文本串,该文本串要么以NULL结尾,要么在nCount中指出它的长短。
- nCount:要输出的文本的长度。若以NULL结尾,该参数必须是-1。
- lpRect: 指向要输出文本串的正方形区域的指针,该方形必须是一个裁剪区,也就是说超过该区域的字符将不能显示。
- uFormat:指定如何显示。我们可以用 or 把以下标志或到一块:
- DT_SINGLELINE:是否单行显示。
- DT_CENTER:是否水平居中。
- DT_VCENTER :是否垂直居中。
结束绘制后,必须调用 EndPaint 释放“设备环境”的句柄。好了,现在我们把“绘制”文本串的要点总结如下:
- 必须在开始和结束处分别调用 BeginPaint 和 EndPaint;
- 在 BeginPaint 和 EndPaint 之间调用所有的绘制函数;
- 如果在其它的消息处理中重新绘制客户区,您可以有两种选择:
(1)用GetDC和ReleaseDC代替BeginPaint和EndPaint;
(2)调用InvalidateRect或UpdateWindow让客户区无效,这将迫使WINDOWS把WM_PAINT放入应用程序消息队列,从而使得客户区重绘。
posted on 2011-01-22 14:49
姚明 阅读(1166)
评论(0) 编辑 收藏 引用 所属分类:
原创教程