win32关键点(一)

分7部分:

      窗口和消息

      输出文本

      图形基础

      键盘

      鼠标

      计时器

      子窗口控制

一 窗口和消息

1 前缀

前缀
 全称
 释义
 
CS
 class style
 类风格选项
 
CW
 create windows
 创建窗口选项
 
DT
 draw text
 绘制文本选项
 
IDI
 Icon ID
 图标ID号
 
IDC
 Cursor ID
 光标ID号
 
MB
 Message Box
 消息框选项
 
SND
 Sound
 声音选项
 
WM
 Windows Message
 窗口消息
 
WS
 Windows Style
 窗口风格
 

 

2 WPARAM和LPARAM的意义

  在Windows是一种16位系统时,WndProc的第三个参数被定义为WORD,是一个16位的无符号整数,而第四个参数被定义为一个LONG,是一个32位有符号整数,所以导致对单词PARAM(参数)加前缀W和L。

但在32位Windows中,WPARAM被定义为一个UINT,而LPARAM被定义为一个LONG,因此窗口过程的这两个参数都是32位的值。

 

3 新的函数类型

WndProc函数返回一个类型为LRESULT的值,该值是一个LONG型,32位有符号。

   

WndProc函数被指定为CALLBACK类型,WinMain函数被指定为WINAPI类型。这些类型指在Windows本身和用户的应用程序之间发生的函数调用的特殊调用序列。

 

4 窗口类结构WNDCLASS

有10个域。分别是:

① style:类风格,用于在什么时候发出窗口变化消息

② cbClsExtra:在类结构保存的窗口结构中预留一些额外空间

      ③ cbWndExtra:在Windows内部保存的窗口结构中预留一些额外空间

      ④ hbrBackground:指定基于这个类创建的窗口背景颜色

      ⑤ hCursor:读取光标

      ⑥ hIcon:读取图标

      ⑦ hInstance:程序的实例句柄

      ⑧ lpfnWndProc:指定处理基于这个窗口类创建的所有窗口的窗口过程

      ⑨ lpszClassName:指定类名

      ⑩ lpszMenuName:指定窗口类菜单

 

5 注册窗口类RegisterClass

一般在Windows XP及以后都可以很顺利的注册成功。

所以可以只写RegisterClass(&wndclass);

 

6 创建窗口CreateWindow

   窗口类定义了窗口的一般特征,调用CreateWindow可以指定有关窗口的更详细的信息。

hwnd = CreateWindow (szAppName,         // 指定一个窗口类,基于该窗口类创建窗口

                  TEXT ("Hello Win"),   // 这个字符串会出现在标题栏中

                  WS_OVERLAPPEDWINDOW, // 本窗口风格

                  CW_USEDEFAULT,        //窗口左上角的X坐标

                  CW_USEDEFAULT,        //窗口左上角的Y坐标

                  CW_USEDEFAULT,        //窗口的宽度

                  CW_USEDEFAULT,        //窗口的高度

                  NULL,                   //窗口对象的父窗口句柄

                  NULL,                   //窗口对象的菜单句柄或者子窗口编号

                  hInstance,                 //当前进程的实例句柄

                  NULL) ;                  //窗口对象的参数指针句柄

 

创建窗口返回的是窗口句柄。

 

7 显示窗口

窗口创建成功后,系统将在内存中为其分配一块内存,但是此时窗口并未显示在显示器上,所以需要使用两个调用。

① ShowWindow(窗口句柄,iCmdShow);

其中的第二个参数用于确定如何儿子屏幕上显示窗口,是最小化还是常规还是最大化。

 

② UpdateWindow(窗口句柄)

调用上句将导致客户区被绘制。它通过给窗口过程发送一个WM_PAINT消息来做到这一点。

 

8 消息循环

调用UpdateWindow之后,窗口就出现在显示器上。

Windows为当前运行的每个Windows程序维护一个“消息队列”。在发生事件的时候,Windows将事件转换为一个“消息”,并将消息放入程序的消息队列中。

 

程序通过执行一个叫做“消息循环”的代码从消息队列中取出消息。

 

while (GetMessage(&msg, NULL, 0, 0))

          {

                 TranslateMessage(&msg);

                 DispatchMessage(&msg);

          }

   

其中msg是一个类型为MSG的结构,MSG结构在WINUSER.H中定义如下:

typedef struct tagMSG {

   HWND      hwnd;

   UINT        message;

   WPARAM    wParam;

   LPARAM     lParam;

   DWORD     time;

   POINT       pt;

} MSG, *PMSG

 

其中,hwnd 是消息发向的窗口句柄。

     message 是消息标识符,以WM_开头。

     wParam 一个32位的消息参数。

     lParam 一个32位的消息参数。

     time 消息放入消息队列的时间。

     pt 消息放入消息队列时鼠标的坐标。

 

消息循环以GetMessage调用开始,它从消息队列中取出一个消息,GetMessage(&msg, NULL, 0, 0)这一调用传给Windows一个指向名为msg的MSG结构的指针。其余三个参数设置为NULL或0,表示程序接收自己创建的所有窗口的所有消息。

 

只要从消息队列中取出的消息的message域不为WM_QUIT,GetMessage就返回一个非0值,while循环就可以继续。

TranslateMessage(&msg);将msg结构传给Windows,进行一些键盘转换。

DispatchMessage(&msg);将msg结构传给Windows,然后Windows将里面的消息发给相应的窗口过程进行处理。处理后,WndProc返回到Windows,Windows返回到程序,程序继续下一个while循环。

 

9 窗口过程WndProc

实际的动作发生在窗口过程中。窗口过程确定了在窗口的客户区显示什么,以及怎么处理用户输入。

窗口过程是命名为WndProc的函数。(也可以其他不冲突的名字)

一个Windows程序可以包含多个窗口过程。

一个窗口过程总是与调用RegisterClass注册的特定窗口类相关联。

CreateWindow函数根据特定的窗口类创建一个窗口,返回该窗口的句柄。

但是基于一个窗口类可以创建多个窗口。

 

1程序    包括  n个窗口过程

1窗口过程关联  1窗口类

1窗口类  创建  n个窗口

CreateWindows根据窗口类创建一个窗口。

 

窗口过程总是声明成如下形式:

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

这4个参数跟MSG结构的前4个域是相同的。

第一个参数:接收消息的窗口句柄。

第二个参数:消息类型,标识消息的数字

最后两个参数:32位的消息参数。

 

程序通常不直接调用窗口过程,而是由Windows调用窗口过程。

 

10 窗口过程处理消息

窗口过程接收的每个消息均是用一个数值来标识的,也就是传给窗口过程的message参数。

窗口过程处理消息时,必须返回0.窗口过程不予处理的所有消息应该被传给名为DefWindowsProc的Windows函数。

用switch语句来处理不同消息,消息以WM_开头。

 

11 WM_CREATE消息

当Windows在处理CreateWindow函数时,窗口过程就会接收到WM_CREATE消息。

通常,窗口过程在WM_CREATE处理期间进行一次窗口初始化。

 

12 WM_PAINT消息

当窗口客户区域的一部分或全部变成“无效”时,必须进行刷新,WM_PAINT将通知程序。

在最初创建窗口的时候,整个客户区都是无效的,因为程序还没有在窗口上画任何东西。在调用UpdateWindow时,通常会触发第一个WM_PAINT消息,指示窗口过程在客户区域上画一些东西。

 

在改变程序窗口大小后,客户区也会变得无效,至于怎么变得无效由CS_引导的类风格选项确定。

 

对WM_PAINT的处理几乎总是从一个BeginPaint调用开始,以一个EndPaint结束。

hdc = BeginPaint(hwnd, &pt);

do something;(如GetClientRect(hwnd, &rect);)

EndPaint(hwnd, &pt);

 

hwnd是要刷新的窗口的窗口句柄。

pt是指向类型为PAINTSTRUCT的结构指针。

 

在BeginPaint调用中,如果客户区域的背景还未被擦除,就由Windows来擦除。然后使用注册窗口类的WNDCLASS结构中的hbrBackground域中第一的刷子来删除背景。

BeginPaint调用使整个客户区有效,并返回一个“设备描述表句柄”。设备描述表是指物理输出设备及其驱动程序。可以利用该“设备描述表句柄”在客户区域显示文本和图形。

EndPaint调用释放设备描述表句柄。

 

GetClientRect(hwnd, &rect);

第一个参数:程序的窗口句柄;

第二个参数:指向RECT类型的rectangle结构。该结构有4个LONG域,标识客户区域的尺寸。

当改变窗口大小时,WndProc通过调用GetClientRect来获取变化后的窗口大小,重新绘制客户区。

 

13 WM_DESTROY消息

   当用户点击关闭按钮时发生。

程序可以通过调用PostQuitMessage以标准方式响应WM_DESTROY消息;

PostQuitMessage(0);

该函数在程序的消息队列插入一个WM_QUIT消息。

GetMessage对于除了WM_QUIT消息之外的从消息队列中取出的所有消息都返回非0值。而当GetMessage取到一个WM_QUIT消息时,返回0.

 

14 关闭程序时的消息传递

① 用户点击关闭按钮

② 产生WM_SYSCOMMAND消息;

③ 产生WM_CLOSE消息响应WM_SYSCOMMAND;

④ 产生WM_DESTROY消息响应WM_CLOSE;

⑤ 产生WM_QUIT消息响应WM_DESTROY。

 

15 进队消息和不进队消息

消息能够分为:进队消息和不进队消息。

进队消息是由Windows放入程序消息队列中的。在程序的消息循环中,重新返回并分配给窗口过程。

不进队消息在Windows调用窗口时直接发送给窗口过程。

 

也就是说,进队消息发送给消息队列,不进队消息发送给窗口过程。

在任何情况下,窗口过程都将获得窗口所有的消息。窗口过程是窗口的消息中心。

 

进队消息基本上是用户输入的结果,还包括时钟消息、刷新消息、退出消息。

不进队消息基本上是来自调用特定的Windows函数。

 

 

二 输出文本

16 有效矩形和无效矩形

窗口过程一旦接受到WM_PAINT消息之后,就准备更新整个客户区,但往往只需更新一个较小的区域。这个区域就称为“无效区域”。正是客户区内存在无效区域,才提示Windows将一个WM_PAINT消息放入消息队列。

 

Windows内部为每个窗口保存一个“绘图信息结构”,这个结构包含了包围无效区域的最小矩形的坐标以及其他信息,这个矩形就叫做“无效矩形”。

如果在窗口过程处理WM_PAINT消息之前,客户区又有一个区域变为无效,那么Windows计算出一个包围两个无效区域的新的无效矩形,并将这个变化后的信息放在绘制信息结构中。

一个消息队列在一个时刻只能有一个WM_PAINT消息在队列中。

 

窗口过程可以调用InvalidateRect使客户区变为无效。如果消息队列包含一个WM_PAINT消息,那么Windows将计算出新的无效矩形;否则,就在消息队列中添加一个WM_PAINT消息。

 

在处理WM_PAINT消息期间,窗口过程在调用了BeginPaint之后,整个客户区就会变得有效。

程序也可以显式调用ValidateRect函数使客户区内的任意矩形区域变得有效。如果这条调用使整个客户区都有效,那么将在当前消息队列中删除WM_PAINT消息。

 

17 设备描述表

要在窗口的客户区绘图,可以使用Windows的图形设备接口GDI函数。

设备描述表DC是GDI内部保存的数据结构。

设备描述表与特定的显示设备有关。

设备描述表中的有些值是图形化的“属性”,如指出颜色、背景色、坐标映射方式等。

 

当程序要绘图时,必须先获取设备描述表句柄。在获取了该句柄之后,Windows用默认的属性值填充设备描述表结构的内部各域。

当程序在客户区绘图完毕后,必须释放设备描述表句柄。句柄被释放后不再有效,也不再使用。程序必须在处理单个消息期间获取和释放句柄。

 

18 获取设备描述表句柄的方法之一

在使用WM_PAINT消息时,使用这种方法。它涉及到BeginPaint和EndPaint两个函数。

在处理WM_PAINT消息时,窗口过程首先调用BeginPaint。BeginPaint函数一般在准备绘制时导致无效区域的背景被擦除。BeginPaint返回的值是设备描述表句柄,这一返回值通常被保持在叫做hdc的变量中。

HDC hdc;

HDC数据类型定义为32位的无符号数。

然后,程序就可以使用需要设备描述表句柄的GDI函数了。

调用EndPaint即可释放设备描述表句柄。

 

一般地,处理WM_PAINT消息的形式如下:

case WM_PAINT:

   hdc = BeginPaint(hwnd, &ps);

   使用GDI函数

   EndPaint(hwnd, &ps);

   return 0;

 

处理WM_PAINT消息时,必须成对地调用BeginPaint和EndPaint。

 

19 绘图信息结构

Windows为每一个窗口保存一个绘图信息结构。这就是PAINTSTRUCT,定义如下:

 

typedef struct tagPAINTSTRUCT {

   HDC        hdc;          

   BOOL       fErase;        

   RECT       rcPaint;

   BOOL       fRestore;

   BOOL       fIncUpdate;

   BYTE       rgbReserved[32];

} PAINTSTRUCT

 

在程序调用BeginPaint时,Windows填充该结构的各个字段。用户程序只需要使用前三个字段。

hdc是设备描述表句柄。

 

fErase通常被标识为FLASE,这意味着Windows已经擦除了无效矩形的背景。

如果程序通过调用Windows函数InvalidateRect使客户区中的矩形失效,那么该函数的最后一个参数会指定fErase的值。如果指定0,那么在稍后的PAINTSTRUCT里面的fErase会被设置为TRUE。

 

rcPaint是RECT结构,定义了无效矩形的边界。RECT结构中的left、top、right、bottom以像素点为单位。此时,Windows将绘图操作限制在此RECT结构定义的矩形范围内,如果要在无效矩形外绘图,应该在调用BeginPaint之前,使用如下调用:

InvalidateRect(hwnd, NULL, TRUE);

它将使整个客户区无效,并擦除背景。

 

20获取设备描述表句柄的方法之二

要得到窗口客户区的设备描述表句柄,可以调用GetDC来获取句柄。在使用完后调用ReleaseDC;

hdc = GetDC(hwnd);

使用GDI函数

ReleaseDC(hwnd, hdc);

GetDC和ReleaseDC函数必须成对地使用。

GetDC返回的设备描述表句柄具有一个剪取矩形,等于整个客户区。

GetDC不会使任何无效区域变为有效,要是整个客户区有效,需要调用:

ValidateRect(hwnd, NULL);

 

一般可以调用GetDC和ReleaseDC来对键盘消息、鼠标消息作出反应。

 

21 TextOut细节

TextOut是用于显示文本的最常用的GDI函数。语法是:

TextOut(hdc, x, y, psText, iLength);

 

第一个参数:设备描述表句柄,既可以是GetDC的返回值,也可以是BeginPaint的返回值。

第二个参数:定义客户区内字符串的开始位置的水平坐标。

第三个参数:定义客户区内字符串的开始位置的垂直坐标。

第四个参数:指向要输出的字符串的指针。

第五个参数:字符串中字符的个数。如果psText中的字符是Unicode的,那么串中的字节数就是iLength值的两倍。

 

设备描述表还定义了一个剪取区域。

对于从GetDC获取的设备描述表句柄,默认的剪取区是整个客户区。

对于从BeginPaint获取的设备描述表句柄,默认的剪取区是无效区域。

Windows不会在剪取区域之外的任何位置显示字符串。

 

22 字符大小

要用TextOut显示多行文本,就必须确定字体的字符大小,可以根据字符的高度来定位字符的后续行,以及根据字符的宽度来定位字符的后续列。

系统字体的字符高度和平均宽度取决于视频显示器的像素大小。

 

程序可以调用GetSystemMetrics函数来确定关于用户界面构件大小的信息。

程序可以调用GetTextMetrics函数来确定字体大小。

metric是度量的意思。

 

TEXTMETRIC的结构:

typedef struct tagTEXTMETRIC

{

   LONG       tmHeight;

   LONG       tmAscent;

   LONG       tmDescent;

   LONG       tmInternalLeading;

   LONG       tmExternalLeading;

   LONG       tmAveCharWidth;

   LONG       tmMaxCharWidth;

   其他域

} TEXTMETRIC, *PTEXTMETRIC;

 

要使用GetTextMetrics函数,需要先定义一个通常被称为tm的结构变量:

TEXTMETRIC tm;

在需要确定文本尺寸时,先要获取设备描述表句柄,再调用GetTextMetrics:

hdc = GetDC(hwnd);

GetTextMetrics(hdc, &tm);

操作;

ReleaseDC(hwnd,hdc);

 

23 文本尺寸

字体的纵向大小由5个值确定:

① tmHeight,等于tmAscent加上tmDescent。这两个值表示了基线上下字符的最大纵向高度。

② tmAscent,基线以上的高度

③ tmDescent,基线以下的高度

④ tmInternalLeading,重音号和字符之间的距离,如ü中的u和两点的距离。

⑤ tmExternalLeading,一般用于多行文本间行距的调整。

字符的横向大小由2个值确定:

① tmAveCharWidth,小写字母加权平均宽度。

② tmMaxCharWidth,字体中最宽字符的宽度。

对于等宽字体,tmAveCharWidth和tmMaxCharWidth这两个值相等。

大写字母的平均宽度比较复杂,如果:

① 字体是等宽字体,那么大写字母的平均宽度等于tmAveCharWidth。

② 字体是变宽字体,那么大写字母的平均宽度等于tmAveCharWidth*1.5。

 

判断字体是否是变宽字体,可以通过TEXTMETRIC结构中的tmPitchAndFamily域的低位判断,如果低位是1,那么是变宽字体,如果是0,那么是等宽字体。

大写字母宽度 = (tm.tmPitchAndFamily & 1 ? 3 : 2) / 2 * 小写字母宽度

 

24 格式化文本

在一次Windows对话期间,系统字体的大小不会改变,因此在程序运行过程中,只需要调用一次GetTextMetric。最好是在窗口过程中处理WM_CREATE消息时进行此调用。

假设要编写一个Windows程序,在客户区显示多行文本,这需要先获取字符宽度和高度。可以在窗口过程内定义两个变量来保存字符宽度和总的字符高度。

case WM_CREATE:

  hdc = BeginPaint(hwnd, &pt);

  GetTextMetric(hdc, &tm);

  cxChar = tm.tmAveCharWidth;                                 小写字母宽度

  cyChar = tm.Height + tm.tmExternalLeading;                     字母高度

  cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) / 2 * tm.tmAveCharWidth; 大写字母宽度

  EndPaint(hwnd, &pt);

  return 0;

 

25 客户区的大小

窗口最大化之后的客户区大小,可以通过以SM_CXFULLSCREEN和SM_CYFULLSCREEN为参数调用GetSystemMetric来获得。

要确定客户区的大小,最好的方法是在窗口过程处理WM_SIZE消息。在窗口大小改变时,就会产生WM_SIZE消息。传给窗口过程的lParam参数的低位字中包含客户区的宽度x,高位字中包含客户区的高度y。要保存这些尺寸,可以定义两个int型变量来保存。

static int cxClient,cyClient;

然后在WM_SIZE消息处理中:

case WM_SIZE:

   cxClient = LOWORD(lParam);

   cyClient = HIWORD(lParam);

   return 0;

 

用cyClient/cyChar可以得到客户区可以显示的文本总行数。

 

客户区大小的另外一种获取方法就是使用:

BOOL GetClientRect(HWND hWnd,      // handle to window
               LPRECT lpRect  // address of structure for client coordinates

获取窗口的大小,使用:

BOOL GetWindowRect(HWND hWnd,      // handle to window
                  LPRECT lpRect  // address of structure for window coordinates);

获取屏幕大小:

方式一:最大化当前窗口后,窗口大小就是屏幕大小。

方式二:使用 GetDeviceCaps(HDC hdc, int nIndex ),参看 34 设备的大小。

 

26 滚动条的范围和位置

每个滚动条都有一个相关的范围和位置。这是一对整数。当滚动框在滚动条的顶部(左部)时,滚动框的位置是范围的最小值;在滚动条的底部(右部)时,滚动框的位置是范围的最大值。

在默认情况下,滚动条的范围是0~100,但将范围改变为更方便于程序的数值也是很容易的:

SetScrollRange(hwnd, iBar, iMin, iMax, bRedraw);

其中hwnd为该窗口的句柄。

   iBar为SB_VERT或SB_HORZ。

   iMin和iMax为范围。

   bRedraw,如果要Windows根据新范围重绘滚动条,则设置为TRUE。

 

滚动框的位置不是连续的,而是离散的整数值。

可以使用SetScrollPos在滚动条范围内设置新的滚动框位置:

SetScrollPos(hwnd, iBar, iPos, bRedraw);

参数iPos是新位置,必须在iMin至iMax的范围内。

 

Windows提供了类似的函数GetScrollRange和GetScrollPos来获取滚动条的当前范围和位置。

 

27 滚动条消息

在用鼠标单击滚动条或者拖动滚动框时,Windows都给窗口过程发生WM_VSCROLL或WM_HSCROLL消息。在滚动条上的每个鼠标动作都至少产生两个消息,一个在按下鼠标键时产生,一个在释放鼠标键时产生。

WM_VSCROLL和WM_HSCROLL也带有wParam和lParam消息参数。

lParam只用于作为子窗口而创建的滚动条(通常在对话框内)。

wParam消息参数被分为一个低位字和一个高位字。

低位字是一个数值,指出了鼠标对滚动条进行的操作。这个数值被看作一个“通知码”。通知码以SB开头。

#define SB_LINEUP          0

#define SB_LINELEFT        0

#define SB_LINEDOWN       1

#define SB_LINERIGHT       1

#define SB_PAGEUP          2

#define SB_PAGELEFT        2

#define SB_PAGEDOWN       3

#define SB_PAGERIGHT       3

#define SB_THUMBPOSITION  4

#define SB_THUMBTRACK    5

#define SB_TOP              6

#define SB_LEFT             6

#define SB_BOTTOM          7

#define SB_RIGHT            7

#define SB_ENDSCROLL       8

 

当把鼠标的光标放在滚动框上并按住鼠标键时,就产生SB_THUMBPOSITION和SB_THUMBTRACK消息。

当wParam的低位字是SB_THUMBTRACK时,wParam的高位字是用户在拖动滚动框时的当前位置。

当wParam的低位字是SB_THUMBPOSITION时,wParam的高位字是用户释放鼠标后滚动框的最终位置。

 

28 滚动条信息函数

滚动条文档指出SetScrollPos、SetScrollRange、GetScrollPos、GetScrollRange函数是过时的。

在Win32 API中,升级了2个滚动条函数,称作SetScrollInfo和GetScrollInfo。这些函数完成上述4个函数的全部功能,并增加了2个新特性。

第一个功能设计滚动框的大小。滚动框的大小称作页面大小。算法是:

滚动框大小 / 滚动长度 ≈ 页面大小 / 范围 ≈ 显示的文档数量 / 文档的总大小

可以使用SetScrollInfo来设置页面大小。

 

第二个功能是GetScrollInfo函数,它可以获取32位的范围值。

 

SetScrollInfo和GetScrollInfo函数的语法是:

SetScrollInfo(hwnd, iBar, &si, bRedraw);

GetScrollInfo(hwnd, iBar, &si);

 

iBar参数是SB_VERT或SB_HORZ。

bRedraw可以是TRUE或FALSE,指出了是否要Windows重新绘制计算了新信息后的滚动条。

两个函数的第三个参数是SCROLLINFO结构,定义为:

typedef struct tagSCROLLINFO

{

   UINT   cbSize;

   UINT   fMask;

   int      nMin;

   int      nMax;

   UINT   nPage;

   int      nPos;

   int      nTrackPos;

}  SCROLLINFO

 

在程序中,可以定义如下的SCROLLINFO结构类型:

SCROLLINFO si;

在调用SetScrollInfo或GetScrollInfo函数之前,必须将cbSize自动设置为结构的大小:

si.cbSize = sizeof(si);或si.cbSize = sizeof(SCROLLINFO);

 

fMask:把fMask字段设置为以SIF前缀开头的一个或多个标识,来获取或设置里面的结构中域的值。

① SIF_RANGE:用于获取或设置滚动条的范围

② SIF_POS:用于获取或设置滚动框的位置

③ SIF_PAGE:用于获取或设置滚动条的页面大小

④ SIF_TRACKPOS:用于获取或设置滚动框移动时的位置

 

 

三 图形基础

29 GDI基础

图形设备接口GDI是Windows的子系统,它负责在视频显示器和打印机上显示图形。

Windows NT中的图形主要由GDI32.DLL动态链接库输出的函数来处理。

GDI的主要目的之一是支持与设备无关的图形。

 

图形输出设备分为两大类:光栅设备和矢量设备。

大多数PC机显示器、打印机都是光栅设备。

绘图仪是矢量设备。

 

组成GDI的函数可以分为这样几类:

① 获取(或重建)和释放(或清除)设备描述表的函数;

② 获取有关设备描述表信息的函数;

③ 绘图函数;

④ 设置和获取设备描述表参数的函数;

⑤ 使用GDI对象的函数。

 

30 GDI图元

在屏幕或打印机上显示的图形类型本身可以被分为几类,通常被称为“图元”。

① 直线和曲线

② 填充区域

③ 位图:位图是位的矩形数组,位对应于显示设备上的像素,它们是光栅图形的基本工具。GDI支持两种类型的位图——老的“设备有关”位图,新的“设备无关”位图。

④ 文本

 

 

 

31 GDI其他方面

① 映射模式和变化;

② 元文件:元文件是以二进制形式存储的GDI命令的集合。元文件主要用于通过剪贴板传输矢量图形表示。

③ 区域:区域是形状任意的复杂区;

④ 路径:路径是GDI内部存储的直线和曲线的集合;

⑤ 剪裁:绘图可以限制在客户区的某一部分中,这就是剪裁。剪裁通常是通过区域或者路径定义的。

⑥ 调色板:定制调色板通常限于显示256色的显示器。Windows仅保留这些色彩之中的20种供系统使用,但可以改变其他236种色彩。

⑦ 打印

 

32 进一步探讨设备描述表

想在一个图形输出设备上绘图时,首先必须获得一个设备描述表的句柄。将句柄返回给程序时,Windows就给了用户使用设备的权限。然后在GDI函数中将该句柄作为一个参数,向Windows标识想在其上进行绘图的设备。

(1)获取设备描述表句柄

如果在处理一条消息时获取了设备描述表句柄,应该在退出窗口函数之前释放它。

获取设备描述表句柄的几种方法:

① 在处理WM_PAINT消息时,使用BeginPaint和EndPaint调用

hdc = BeginPaint(hwnd, &ps);

GDI操作

EndPaint(hwnd, &ps);

注:变量ps是类型为PAINTSTRUCT的结构,该结构的hdc字段是BeginPaint返回的设备描述表句柄。PAINTSTRUCT结构包含了一个名为rcRect的RECT结构,该结构定义了包围窗口无效范围的矩形。使用从BeginPaint获得的设备描述表句柄,只能在这个区域内绘图。BeginPaint调用使这个区域有效。

 

② 可以在处理非WM_PAINT消息时获取设备描述表句柄

hdc = GetDC(hwnd);

GDI操作

ReleaseDC(hwnd, hdc);

注:这个设备描述表适用于窗口句柄为hwnd的客户区。这个调用可以在整个客户区上绘图。

 

③ Windows程序还可以获取适用于整个窗口的设备描述表句柄

hdc = GeWindowstDC(hwnd);

GDI操作

ReleaseDC(hwnd, hdc);

注:这个设备描述表除了客户区之外,还包括窗口的标题栏、菜单、滚动条和框架。如果要使用该函数,必须捕获WM_NCPAINT消息。

 

④ 获取整个屏幕的设备描述表句柄

hdc = CreateDC(TEXT(“DISPLAY”), NULL, NULL, NULL);

原型是:hdc = CreateDC(pszDriver, pszDevice, pszOutput, pData);

       GDI操作

       DeleteDC(hdc);

 

⑤ 如果只需要获取关于某设备描述表的一些信息,而并不进行任何绘画,在这种情况下,可以使用CreateIC来获取一个“信息描述表”的句柄,其参数和CreateDC一样。

 

⑥ 一个设备描述表通常是指一个物理显示设备。通常,需要获取有关该设备的信息,其中包括显示器的显示尺寸和色彩范围。可以通过GetDeviceCaps函数来获取这些信息。

iValue = GetDeviceCaps(hdc, iIndex);

参数iIndex的取值为WINGDI.H头文件中定义的29个标识符之一。

 

33 用TextOut输出整型的方法

设一开始有整型:int i = 100;

要用TextOut函数将i输出,需要用到三个函数:

① wsprintf

② TEXT宏

③ TextOut

 

首先得先说明下wsprintf的原型:

int wsprintf(LPTSTR lpOut, LPCTSTR lpFmt,…);

第一个参数:缓冲区,是一个字符数组,一般定义为TCHAR型。

第二个参数:格式字符串,因为第一个参数是TCHAR类型,一定要和TEXT宏联合使用,这样才能在不同的编译环境下都可以顺利编译。

后续参数:要输出的的整型变量。

 

针对第一个参数:要定义一个TCHAR的字符数组作为缓冲区。

TCHAR szBuffer[10]; //足够大就行了

针对第二个参数,需要使用到TEXT宏。

 

TEXT宏的原型:

TEXT(LPTSTR string //ANSI or Unicode string);

用来处理要转换的整型,具体用法是:

TEXT(“%d”);

 

那么上述的两个调用应该写成:

int iLength = 0; //用来保存字符串中的字符个数;

iLength = wsprintf(szBuffer, TEXT(“%d”), i);

上述语句的作用是:将i存进szBuffer中,返回szBuffer存有的字符个数到iLength中。

 

TextOut的原型是:

TextOut(hdc, x, y, psText, iLength);

第一个参数是设备描述表句柄;

第二个参数是输出的文本的x坐标;

第三个参数是输出的文本的y坐标;

第四个参数是是指向要输出的字符串的指针;

第五个参数是字符串中的字符个数;

那么TextOut函数应该写成:

TextOut(hdc, x, y, szBuffer, iLength);

 

34 设备的大小

使用GetDeviceCaps函数能获取有关输出设备物理大小的信息。

对于打印机,用“每英寸的点数dpi”表示分辨率。

对于显示器,用水平和垂直的总的像素数来表示分辨率。

 

用“像素大小”或“像素尺寸”表示设备水平或垂直显示的总像素数。

用“度量大小”或“度量尺寸”表示以每英寸或毫米为单位的显示区域的大小。

像素大小 / 度量大小 = 分辨率

 

使用SM_CXSCREEN和SM_CYSCREEN参数从GetDeviceCaps得到像素大小;

使用HORZSIZE和VERTSIZE参数从GetDeviceCaps得到度量大小;

两者相除就可以得到水平分辨率和垂直分辨率。

如果设备的水平分辨率和垂直分辨率相等,就称该设备具有“正方形像素”。

   因为整个屏幕的度量大小是固定的,所以可以根据分辨率调整水平或垂直显示的像素数。如果分辨率小,那么“像素大小”也就小,也就是说,总像素数少了,那么每个像素的尺寸也就变得大些。

 

35 字体的大小

现在讨论字体的大小问题,这里不是说字号,而是说字体显示的dpi值。Windows系统默认是每英寸96点,所另外一种选择,就是每英寸120点。

我们在调整分辨率的时候,从小分辨率变化到大分辨率时,会觉得图标的文字变小,那是因为在大分辨率下,每个像素的面积变小,假设一个字需要100个像素来显示,那么从小分辨率变化到大分辨率时,字的总面积就变小了,所以字的大小也就发生变化,而这一变化是字体的大小变化,而不是该字的字号发生变化。

在传统的排版中,字体的字母大小由“磅”表示。1磅≈1/72英寸,在计算机排版中1磅正好为1/72英寸。

理论上,字体的磅值是从字体中最高的字符顶部到字符下部的字符底部的距离,其中不包括重音号。根据TEXTMETRIC结构,字体的磅值等于tmHeight – tmInternalLeading。

 

36 关于色彩

“全色”视频显示器的分辨率是每个像素24位:8位红色、8位绿色、8位蓝色。

“高彩色”显示分辨率是每个像素16为:5位红色、6位绿色、5位蓝色。

 

显示256种颜色的视频适配器每个像素需要8位。然而这些8位的值一般由定义实际颜色的调色板表组织。

 

使用GetDeviceCaps可以使程序员确定视频适配器的存储组织,以及能够表示的色彩数目。

这个调用返回色彩平面的数目:iPlanes = GetDeviceCaps(hdc, PLANES);

这个调用返回每个像素的色彩位数:iBitsPixel = GetDeviceCaps(hdc, BITSPIXEL);

 

大多数彩***形显示设备要么使用多个色彩平面,要么每像素有多个色彩位,但是不能同时二者兼用;即这两个调用必须有一个返回1.(一般都是第一个返回1)。

 

在大多数GDI函数调用中,使用COLORREF值(32位)来表示一种色彩。

31
 …
 24
 23
 …
 16
 15
 …
 8
 7
 …
 0
 
0
 蓝
 绿
 红
 

理论上,COLORREF可以指定2的24次方或1600万种色彩。

这个无符号长整数常常称为一个“RGB色彩”。在使用RGB(r, g, b);宏时注意参数的顺序是红、绿、蓝。而在无符号长整数中,由高位到低位是0、蓝、绿、红。

当三个参数都是0时,表示黑色,当三个参数都是255时,表示白色。

黑色 = RGB(0,0,0) = 0x00000000

白色 = RGB(255, 255, 255) = 0x00FFFFFF

 

37 保存设备描述表

通常,在调用GetDC或BeginPaint时,Windows会用默认值创建一个新的设备描述表,对设备描述表其属性所做的一切修改在调用ReleaseDC或EndPaint被释放掉。

如果需要使用非默认的设备描述表属性,则必须在每次获取设备描述表句柄时初始化设备描述表。

如果需要在释放设备描述表之后,仍然保存程序中对设备描述表所做的改变,以便在下一次调用GetDC和BeginPaint时它们仍起作用。则应该在窗口类那将CS_OWNDC标志包含进窗口类风格中。

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

现在,基于这个窗口类所创建的每个窗口都将拥有自己的设备描述表,它一直存在,直到窗口被删除。

如果使用了CS_OWNDC风格,就只需初始化设备描述表一次,可以在处理WM_CREATE消息期间完成这一操作。

CS_OWNDC风格只影响GetDC和BeginPaint获得的设备描述表,不影响其他函数获得的设备描述表,如GetWindowDC获得的设备描述表。

在某些情况下,可以需要改变某些设备描述表,用改变后的属性进行绘图,然后又要恢复回改变前的属性。这时,可以通过如下调用来保存设备描述表的状态。

保存:int idSaved = 0; idSaved = SaveDC(hdc);

恢复:RestoreDC(hdc, idSaved);

也可以不保存SaveDC的返回值,这时候如果要恢复,就只能恢复到最近保存的状态,RestoreDC(hdc, -1);

 

38 写像素

写像素SetPixel(hdc, x, y, crColor);其中:

hdc是设备描述表句柄;

x, y是像素点的坐标;

crColor是要设置的颜色,一般可以用RGB(r, g, b)设置。

 

39 线条

几种画线函数:

① LineTo:画直线

② Polyline和PolylineTo:画一系列相连的直线

③ PolyPolyline:画多组相连的线

④ Arc和ArcTo和AngleArc:画椭圆线

⑤ PolyBezier和PolyBezierTo:画贝塞尔线条

⑥ PolyDraw:画一系列相连的线以及贝塞尔线条

 

几种填充函数:

① Rectangle:画矩形

② Ellipse:画椭圆

③ RoundRect:画带圆角的矩形

④ Pie:画椭圆的一部分,使其看起来像一个扇形

⑤ Chord:画椭圆的一部分,使其看起来像弓形

⑥ Polygon:画多边形

⑦ PolyPolygon:画多个多边形

 

设备描述表的5个属性影响着用这些函数所画线条的外观:

① 当前画笔的位置;

② 画笔;

③ 背景方式;

④ 背景色;

⑤ 绘图模式。

 

画一条直线,必须调用2个函数,第一个函数指定了线的开始点坐标,第二个函数指出了线的终点坐标:

MoveToEx(hdc, x1, y1, NULL);

LineTo(hdc, x2, y2);

 

MoveToEx不会画线,只是设置了设备描述表的“当前位置”属性。然后LineTo函数从当前的位置到它所指定的点画一直线。在默认的设备描述表中,当前位置最初是在点(0,0)。

MoveToEx最后一个参数是指向POINT结构的指针。从该函数返回后,POINT结构的x和y字段指出了之前的“当前位置”,如果不需要这个信息,直接填NULL。

 

如果需要获取当前位置,先定义一个POINT的结构变量pt,然后通过下面的调用:

GetCurrentPositionEx(hdc, &pt);

 

几个函数的原型:

Rectangle(hdc, xLeft, yTop, xRight, yBottom);

Ellipse(hdc, xLeft, yTop, xRight, yBottom);

RoundRect(hdc, xLeft, yTop, xRight, yBottom, xCorner, yCorner);

Chord(hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd);

      Pie(hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd);

      Arc(hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd);

 

一个二维的贝塞尔线条由4个点定义——两个端点和两个控制点。曲线的控制点固定,将曲线从两个端点间的直线处拉伸构造曲线。

 

40 使用画笔

调用任何画笔函数时,Windows使用设备描述表中当前选中的“画笔”来画线。画笔决定线的色彩、宽度、线型。线型可以是实线、点划线、虚线,默认设备描述表中画笔是BLACK_PEN,一个像素宽,实线。

Windows提供三种现有画笔,分别是:BLACK_PEN, WHITE_PEN和NULL_PEN。

Windows使用句柄来引用画笔。用HPEN的类型定义,即画笔的句柄。

HPEN hPen;

 

调用GetStockObject,可以获得现有画笔的句柄。

hPen = GetStockObject(WHITE_PEN);

 

调用SelectObject将画笔选进设备描述表。

SelectObject(hdc,hPen);

 

SelectObject的返回值是选进前设备描述表的画笔句柄。

 

使用CreatePen或CreatePenIndirect创建一个“逻辑画笔”,这仅仅是对画笔的描述。这些函数返回逻辑画笔的句柄,然后调用SelectObject将画笔选进设备描述表,之后才可以使用新的画笔来画线。

在任何时候,只能有一种画笔选进设备描述表。

在释放设备描述表或在选择了另一种画笔到设备描述表中之后,就可以调用DeleteObject来删除所创建的逻辑画笔。

逻辑画笔是一种“GDI对象”,GDI对象有六种:画笔、刷子、位图、区域、字体、调色板。

CreatePen的原型是:

HPEN CreatePen(iPenStyle, iWidth, crColor);

 

iPenStyle参数确定画笔是实线、虚线还是点线。

iWidth参数确定线宽,如果iPenStyle不是实线,且iWith大于1,那么画笔将变成实线。

crColor是RGB颜色。

 

获取当前画笔句柄:

hPen = GetCurrentObject(hdc, OBJ_PEN);

 

还可以建立一个逻辑画笔LOGPEN结构,调用CreatePenIndirect来创建画笔。

LOGPEN logpen;

此结构有三个成员:UINT lopnStyle 是画笔线型;POINT lopnWidth是按逻辑单位度量的画笔宽度,只用其中的x值;COLORREF lopnColor是画笔颜色

 

41 填充空隙

点式画笔和虚线画笔的空隙的着色取决于设备描述表的两个属性——背景模式和背景颜色。默认的背景模式是OPAQUE,在这种方式下,Windows使用背景色填充空隙,默认的背景色为白色。

下述调用用来改变和获取Windows用来填充空隙的背景色:

改变:SetBkColor(hdc, crColor);

获取:GetBkColor(hdc);

 

下述调用用来改变和获取背景模式:

改变:SetBkMode(hdc, 模式);

     模式:TRANSPARENT,忽略背景色,并且不填充空隙。

           OPAQUE默认。

获取:GetBkMode(hdc);

 

42 绘图方式

设备描述表中定义的绘图方式也影响显示器上所画线的外观。

当Windows使用画笔来画线时,实际上执行画笔像素与目标位置处原来像素之间的某种按位布尔运算。像素间的按位布尔运算叫做“光栅运算”,简称为“ROP”。由于画一条直线只涉及两种像素(画笔和目标),因此这种布尔运算又称为“二元光栅运算”,简称为“ROP2”。

在默认设备描述表中,绘图方式定义为R2_COPYPEN,这意味着Windows只是将画笔像素复制到目标像素代替之。

Windows定义了16种不同的ROP2码,用来设置不同的绘图方式。

设置绘图方式:SetROP2(hdc, iDrawMode);

获取绘图方式:iDrawMode = GetROP2(hdc);

 

43 绘制填充区域

Windows中有7个用来画带边缘的填充图形的函数:

① Rectangle:画矩形

② Ellipse:画椭圆

③ RoundRect:画带圆角的矩形

④ Pie:画椭圆的一部分,使其看起来像一个扇形

⑤ Chord:画椭圆的一部分,使其看起来像弓形

⑥ Polygon:画多边形

⑦ PolyPolygon:画多个多边形

 

Windows用设备描述表中选择的当前画笔来画图形的边界框,边界框还使用当前背景方式、背景色彩和绘图方式,跟画线时一样。

图形以当前设备描述表中选择的刷子来填充。默认情况下,使用现有对象,这意味着图形内部将画成白色。

Windows定义6种现有刷子:WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DKGRAY_BRUSH、BLACK_BRUSH和NULL_BRUSH。

也可以自己定义刷子 HBRUSH hBrush;

 

通过GetStockObject来获取现有刷子:

hBrush = GetStockObject(WHITE_BRUSH);

通过SeletctObject将刷子选进设备描述表:

SelectObject(hdc, bBrush);

 

如果要画一个没有边界框的图形,可以将NULL_PEN选进设备描述表。

SelectObject(hdc, GetStockObject(NULL_PEN));

 

如果要画一个没有填充内部的图像,可以将NULL_BRUSH选进设备描述表。

SelectObject(hdc, GetStockObject(NULL_BRUSH));

 

画多边形函数的原型:

Polygon(hdc, apt, iCount);

apt参数是POINT结构的一个数组,iCount是点的数目。如果该数组中的最后一个点和第一个点不同,则Windows将会再加一条线,将最后一个点与第一个点连起来。

 

画多个多边形函数的原型:

PolyPolygon(hdc, apt, aiCounts, iPolyCount);

apt数组具有全部多边形的所有点。

aiCounts数组给出了多边形的端点数。

iPolyCount给出了所画的多边形的个数。

 

44 用画刷填充内部

Rectangle、RoundRect、Ellipse、Chord、Pie、Polygon和PolyPolygon图形的内部是用选进设备描述表的当前画刷来填充的。画刷是一个8×8的位图,它水平和垂直地重复使用来填充内部区域。

Windows有5个函数,可以自己创建逻辑画刷,然后用SelectObject将画刷选进设备描述表。

① hBrush = CreateSolidBrush(crColor); 纯颜色刷子

② hBrush = CreateHatchBrush(iHatchStyle, crColor); 带影射线的刷子

  crColor是影线的颜色,影线的间隙用设备描述表定义的背景方式和背景色来着色。

③ CreatePatternBrush()

④ CreateDIBPatternBrushPt() 基于位图的刷子

⑤ hBrush = CreateBrushIndirect(&logbrush);

  该函数包含其他4个函数。

变量logbrush是一个类型为LOGBRUSH的结构,该结构有三个字段

UINT lbStyle;

COLORREF lbColor;

LONG lbHatch;

 

45 矩形函数

Windows包含了几种使用RECT结构和“区域”的绘图函数。区域就是屏幕上的一块地方,是矩形,多边形和椭圆的组合。

FillRect(hdc, &rect, hBrush);

用指定画刷来填充矩形。该函数不需要事先将画刷选进设备描述表。

FrameRect(hdc, &rect, hBrush);

使用画刷画矩形框,但不填充矩形。

InvertRect(hdc, &rect);

将矩形中所有像素反转。

 

常用矩形函数:

① SetRect(&rect, xLeft, yTop, xRight, yBottom); 设置矩形的4个字段值。

② OffsetRect(&rect, x, y); 将矩形沿x轴和y轴移动几个单元。

③ InflateRect(&rect, x, y); 增减矩形尺寸

④ SetRectEmpty(&rect); 将矩形各字段设为0

⑤ CopyRect(&DestRect, &SrcRect); 将矩形复制给另一个矩形。

⑥ IntersectRect(&DestRect, &SrcRect1,&ScrRect2);获取两个矩形的交集

⑦ UnionRect(&DestRect, &SrcRect1,&ScrRect2); 获取两个矩形的并集

⑧ bEmpty = IsRectEmpty(&rect); 确定矩形是否为空

⑨ binRect = PtinRect(&rect, point);确定点是否在矩形内

 

46 创建和绘制区域

区域是对显示器上一个范围的描述,这个范围是矩形、多边形和椭圆的组合。

区域可以用于绘制和剪裁,通过将区域选进设备描述表,就可以用区域来进行剪裁。

当创建一个区域时,Windows返回一个该区域的句柄,类型为HRGN。

HRGN hRgn;

① 创建矩形区域:

hRgn = CreateRectRgn(xLeft, yTop, xRight, yBottom);

hRgn = CreateRectRgnIndirect(&rect);

 

② 创建椭圆区域:

hRgn = CreateEllipticRgn(xLeft, yTop, xRight, yBottom);

hRgn = CreateEllipticRgnIndirect(&rect);

 

③ 创建多边形区域:

hRgn = CreatePolygonRgn(&point, iCount, iPolyFillMode);

point参数是个POINT类型的结构数组;

iCount是点的数目;

iPolyFillMode是ALTERNATE或者WINDING

 

④ 区域的融合

iRgnType = CombineRgn(hDestRgn, hSrcRgn1, hSrcRgn2, iCombine);

这一函数将两个源区域组合起来并用句柄hDestRgn指向组合成的目标区域。

iCombine参数说明了hSrcRgn1和hSrcRgn2是怎么组合的。

RGN_AND  公共部分

RGN_OR    全部

RGN_XOR  全部除去公共部分

RGN_DIFF  hSrcRgn1不在hSrcRgn2的部分

RGN_COPY hSrcRgn1的全部,忽略hSrcRgn2

 

区域的句柄可以用到4个绘图函数:

FillRgn(hdc, hRgn, hBrush);

FrameRgn(hdc, hRgn, xFrame, yFrame);

xFrame, yFrame是画在区域周围边框的宽度和高度。

InvertRgn(hdc, hRgn);

PaintRgn(hdc, hRgn);

 

47 矩形与区域的剪裁

区域也在剪裁中扮演了一个角色。

InvalidateRect函数使显示的一个矩形区域失效,并产生一个WM_PAINT消息。

InvalidateRect(hwnd, NULL, TRUE); 清除客户区;

 

可以通过调用GetUpdateRect来获取失效矩形的坐标。

使用ValidateRect函数使客户区的矩形有效。

当接收到一个WM_PAINT消息时,无效矩形的坐标可以从PAINTSTRUCT结构中得到,该结构是用BeginPaint函数填充的。

Windows中有两个作用于区域而不是矩形的函数:

InvalidateRgn(hwnd, hRgn, bErase);

ValidateRgn(hwnd, hRgn);

所以当接收到一个WM_PAINT消息时,可能由无效区域引起的。剪裁区域不一定是矩形。

 

SelectObject(hdc, hRgn);

SelectClipObject(hdc, hRgn);

通过将一个区域选进设备描述表来创建自己的剪裁区域。

 

 

 

 

转自:http://blog.csdn.net/pizi0475/archive/2010/03/19/5395483.aspx

转载于:https://blog.51cto.com/wangtingkui/1551779

Published by

风君子

独自遨游何稽首 揭天掀地慰生平