第5章 图形设备接口与图形绘制

特殊说明:版权归个人所有,请勿转载,谢谢合作。

Windows图形设备接口GDI(Graphics Device Interface),它的主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形输出。本章节讨论设备环境和基本的GDI函数。了解这些知识,对于Windows编程来说是非常重要的。

5.1 图形设备接口

GDI是Windows系统的重要组成部分,负责系统与用户之间以更直观的方式进行交互――图形方式交互。控制在输出设备上显示图形或文字。GDI的出现使程序员无需要关心硬件设备及设备驱动,就可以将应用程序的输出转化为硬件设备上的输出,实现了程序开发者与硬件设备的隔离,大大方便了开发工作。GDI具有如下特点:

(1)不需要程序直接访问物理显示硬件,通过称为“设备环境”的抽象接口,间接访问显示硬件;

(2)程序需要与显示硬件(显示器、打印机等)进行通讯时,必须首先获得与特定窗口相关联的设备环境;

(3)用户无需关心具体的物理设备类型;

(4)Windows参考设备环境的数据结构完成数据的输出。

 

5.1.1    设备描述表

设备描述表是它与显示设备具有一定的对应关系,在Windows GDI界面下,它总是与某个窗口或这窗口上的某个显示区域相关。通常意义上窗口的设备描述表,一般指的是窗口的客户区,不包括标题栏、菜单栏所占用的区域,而对于整个窗口来说,其设备描述表严格意义上来讲应该称为窗口设备描述表,它包含窗口的全部显示区域。二者的操作方法完全一致,所不同的仅仅是可操作的范围不同而已。目前设备描述表有四种类型,它们分别是:

显示类型,主要支持画图操作以及视频显示等;

打印类型,支持打印机、绘图仪等输入出设备的绘图操作;

存储类型,主要支持绘制位图操作;

消息类型,主要支持设备数据的恢复。

设备描述表所描述图形对象及其属性如表5.1所示。

 

  • 表5.1 设备描述对象
图形对象 描述
画刷 颜色、样式等
画笔 颜色、样式等
位图 位图的颜色、像素、缩放模式等
字体 字体内容、大小、磅数、字符集等
区域 位置、尺寸等
调色板 颜色等

 

设备环境代表屏幕上的一块区域,要想某个区域绘制图形或输出文字,就必须先获得此区域的设备环境句柄(HDC)。HDC是Windows提供的描述设备环境句柄的数据类型,它代表了程序当前的显示设备。在绘图时,必须要指定一个设备环境(DC),用来将某个窗口或设备与设备环境类的句柄指针关联起来,所有的绘图操作都与该句柄有关。HDC的获得要在WM_PATIN消息中,通过BeginPaint函数来获得,通过EndPaint函数来释放。因为所有的绘图、文字输出等,都需要这个HDC,所以今后这类的代码,通常会写在BeginPaint函数与EndPaint函数之间。

 

5.1.2    系统刷新请求

在Windows系统中,WM_PAINT消息非常重要,当窗口的部分区域或全部,变为无效,需要更新窗口时,系统将执行此消息。窗口之所以无效,是因为在最初创建窗口时,整个窗口区域都是处于无效状态,当在创建窗口时,使用的UpdateWindow函数时(创建窗口的第四步),它会执行第一个WM_PAINT消息,执行结果后,它需要将显示的内容在窗口区域显示,此时不允许再进行更改,所以窗口区域无效。如果想再次更改窗口区域显示的内容(即重新响应WM_PAINT消息),只有如下几种请求能办到:

(1)窗口发生变化,窗口的尺寸发生变化、客户区域移动/显示或程序通过滚动条滚动窗口等;

(2)窗口覆盖,窗口被其他窗口覆盖、窗口切换焦点或有菜单操作等;

(3)使用系统API,使用系统屏幕刷新函数,如,InvalidateRect和InvalidateRgn等。

在Windows窗口刷新时,内存中存放着一个显示输出的副本,当需要重新绘制窗口时,将内存中的副本,复制到相应的窗口中去。在应用程序中,通常将图形绘制(包括文本输出)处理,放在WM_PAINT消息响应模块中去(即在过程处理函数中的,消息响应的位置),当程序接收到刷新请求后,即可重新绘制图形。

 

5.1.3    获取设备环境

在WM_PAINT消息处理过程中,通常是从BeginPaint函数调用开始,而以一个EndPaint函数调用结束,【例5-1】是文本绘制的示例,说明了这一点。

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  char szBuff[64] = { "文本绘制" };	// 显示的文本内容
  PAINTSTRUCT ps;				// 记录一些绘制信息
  HDC hdc;				// 设备环境句柄

  // 消息处理
  switch (message) 
  {
    // 绘制消息处理
  case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);

    RECT rt;
    GetClientRect(hWnd, &rt);
    DrawText(hdc, szBuff, strlen(szBuff), &rt, DT_CENTER);

    EndPaint(hWnd, &ps);
    break;

    // 窗口销毁消息,关闭窗口时响应。
  case WM_DESTROY:
    PostQuitMessage(0);
    break;

  default:
    // 调用系统默认消息处理,即交给系统处理。
    return DefWindowProc(hWnd, message, wParam, lParam);
  }//end switch

  return 0;
}

 

程序运行结果如图5.1所示。

 图5.1 文本绘制

 

BeginPaint与EndPaint函数,第一个参数都是程序的窗口句柄,第二个参数是指向PAINTSTRUCT的结构指针。PAINTSTRUCT结构中包含一些窗口消息处理程序,可以用来更新显示区域的内容,结构体原型如下:

 

typedef struct tagPAINTSTRUCT { // ps 
    HDC  hdc; 
    BOOL fErase; 
    RECT rcPaint; 
    BOOL fRestore; 
    BOOL fIncUpdate; 
    BYTE rgbReserved[32]; 
} PAINTSTRUCT;

 

PAINTSTRUCT 结构体包含了用于绘制窗口客户区域的信息。例如,要更新的客户区的矩形区域的大小等等,其各参数说明如下:

参数hdc,设备环境句柄;

参数fErase,一般取值为真,表示擦除无效矩形区域的背景,否则不擦除;

参数rcPaint,无效矩形区域标识。通过制定左上角和右下角的坐标确定一个要绘制的矩形范围,该矩形单位相对于客户区左上角;

参数fRestore,系统预留,一般用不到;

参数fIncUpdate,系统预留,一般用不到;

参数rgbReserved,系统预留,一般用不到。

 

GetClientRect 函数的功能是获取窗口客户区的坐标。客户区坐标指定客户区的左上角和右下角。由于客户区坐标是相对窗口客户区的左上角而言的,因此左上角坐标为(0,0)。第一个参数是程序窗口的句柄。第二个参数是一个指针,指向一个RECT类型。

RECT是个特别的数据结构,它的作用就是定义一个矩形区域对象,用来存储一个矩形框的左上角坐标、宽度和高度。其函数原型如下:

 

typedef struct _RECT { 
    LONG left; 
    LONG top; 
    LONG right; 
    LONG bottom; 
} RECT;

 

参数left,指定矩形框左上角的x坐标。

参数top,指定矩形框左上角的y坐标。

参数right,指定矩形框右下角的x坐标。

参数bottom,指定矩形框右下角的y坐标。

如果根据left、top、right及bottom四个值根据坐标位置难记忆的话,可以根据如图5.2所示进行记忆,这样更直接一些。

 图5.2 RECT参数记忆

 

DrawText函数,在指定的矩形里写入格式化的正文,根据指定的方法对正文格式化(扩展的制表符,字符对齐、换行等)。其函数原型如下:

 

int DrawText(
  HDC hDC,            // handle to device context
  LPCTSTR lpString,   // pointer to string to draw
  int nCount,         // string length, in characters
  LPRECT lpRect,      // pointer to struct with formatting dimensions
  UINT uFormat        // text-drawing flags
);

 

参数hDC,设备环境句柄。

参数lpString,指向将被写入的字符串的指针,如果参数nCount是-1,则字符串必须是以“\0”结束的。 如果uFormat包含DT_MODIFYSTRING,则函数可为此字符串增加4个字符,存放字符串的缓冲区必须足够大,能容纳附加的字符。

参数nCount,指向字符串中的字符数。如果nCount为-1,则lpString指向的字符串被认为是以“\0”结束的,DrawText会自动计算字符数。

参数lpRect,指向结构RECT的指针,其中包含文本将被置于其中的矩形的信息,所有绘制操作,包括图形,都是以矩形区域作为绘画的基准。

参数uFormat,指定格式化文本的方法。它可以下列值的任意组合,各值描述如表5.2所示。

 

  • 表5.2 设备描述对象
描述
DT_BOTTOM 将正文调整到矩形底部。此值必须和DT_SINGLELINE组合
DT_CALCRECT 决定矩形的宽和高
DT_CENTER 使正文在矩形中水平居中
DT_EDITCONTROL 复制多行编辑控制的正文显示特性
DT_END_ELLIPSIS或DT_PATH_ELLIPSIS 对于显示的文本,如果结束的字符串的范围不在矩形内,它会被截断并以省略号标识。 如果一个字母不是在字符串的末尾处超出了矩形范围,它不会被截断并以省略号标识。 字符串不会被修改,除非指定了DT_MODIFYSTRING标志
DT_EXPANDTABS 扩展制表符,每个制表符的缺省字符数是8
DT_EXTERNALLEADING 在行的高度里包含字体的外部标头,通常,外部标头不被包含在正文行的高度里
DT_INTERNAL 用系统字体来计算正文度量
DT_LEFT 正文左对齐
DT_MODIFYSTRING 修改给定的字符串来匹配显示的正文。

此标志必须和DT_END_ELLIPSIS 或 DT_PATH_ELLIPSIS同时使用

DT_NOCLIP 无裁剪绘制。当DT_NOCLIP使用时DrawText的使用会有所加快
DT_RIGHT 正文右对齐
DT_RTLREADING 当选择进设备环境的字体是希伯来文或阿拉伯文字体时,为双向正文安排从右到左的阅读顺序都是从左到右的
DT_SINGLELINE 单行显示
DT_TABSTOP 设置制表,Tab停止
DT_TOP 正文顶端对齐
DT_VCENTER 使正文在矩形中垂直居中
DT_WORDBREAK 当一行中的字符将会延伸到由lpRect指定的矩形的边框时,此行自动地在字之间断开
DT_WORD_ELLIPSIS 截短不符合矩形的正文,并增加省略号

 

最后,再重新整理一下程序的执行过程。首先,捕获WM_PAINT消息,在WM_PAINT消息中,使用BeginPaint函数来获得HDC(设备环境句柄);其次,通过GetClientRect函数获得当前客户区域的尺寸;再次,使用DrawText函数将文字绘制到屏幕上;最后,使用EndPaint函数来结束绘制过程。

 

5.1.4    其他绘制方法

在前几节已经提及到,通常图形绘制、文字输出,都属于WM_PAINT消息处理范围之内的。而在实际应用中,如果不是在WM_PAINT消息,可以使用GetDC函数来获取窗口客户区域的设备环境句柄,从而实现图形、文字的绘制操作。操作方法如下:

 

hdc = GetDC(hWnd);

// 图形或文字绘制
// ...

ReleaseDC(hWnd, hdc);

 

首先要通过GetDC函数来获得客户区的设备环境句柄,句柄得到后就可以随意绘制,绘制完成后,需要使用ReleaseDC函数,将GetDC获得到的句柄释放。虽然图形、文字的绘制,可以不必居于WM_PAINT消息,更随意一些,但是它有一个问题,它不会永久地保留在屏幕上。例如,绘制代码写在鼠标左键接下消息处(WM_LBUTTONDOWN),代码如下所示:

 

  // 鼠标消息的处理
case WM_LBUTTONDOWN:
  
  hdc = GetDC(hWnd);
  // 图形或文字绘制
  // ...
  ReleaseDC(hWnd, hdc);

 

程序执行后,客户区不会有任何内容,当点击鼠标左键时,绘制的内容将打印到屏幕上,但此时,如果窗口被其他窗口覆盖、窗口的尺寸发生变化或任何一项(如5.1.2所描述的刷新请求),刚刚绘制的内容将会消失。只有再次点击鼠标左键时,绘制的内容才能再次打印到屏幕上。

 

5.1.5    窗口刷新函数的应用

窗口刷新中,屏幕刷新函数应用更为广泛,因为它使用较为灵活。屏幕刷新函数有两个:InvalidateRect函数与InvalidateRgn函数。

InvalidateRect函数向指定的窗体添加一个矩形,然后窗口客户区域的这一部分将被重新绘制,其函数原型如下:

BOOL InvalidateRect(
  HWND hWnd,  // handle of window with changed update region
  CONST RECT *lpRect,
               // address of rectangle coordinates
  BOOL bErase  // erase-background flag
);

 

参数hWnd,要更新的客户区所在的窗体的句柄。如果为NULL,则系统将在函数返回前重新绘制所有的窗口,然后发送 WM_ERASEBKGND 和 WM_NCPAINT 给窗口过程处理函数。

参数lpRect,无效区域的矩形代表,它是一个结构体指针,存放着矩形的大小。如果为NULL,全部的窗口客户区域将被增加到更新区域中。

参数bErase,指出无效矩形被标记为有效后,是否重绘该区域,重绘时用预先定义好的画刷。当指定TRUE时需要重绘。

 

【例5-2】将5.1.3节示例的功能进行了更改,当鼠标左键按下时,屏幕显示文字,鼠标右键按下时,清空屏幕上的文字,代码如下:

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  char szBuff[64] = { "文本绘制" };	// 显示的文本内容
  PAINTSTRUCT ps;				// 记录一些绘制信息
  HDC hdc;						// 设备环境句柄
  static BOOL bIsDraw = FALSE;		// 静态变量,存放是否绘制的布尔值变量

  // 获得客户区域范围
  RECT rt;
  GetClientRect(hWnd, &rt);

  // 消息处理
  switch (message) 
  {
    // 鼠标消息的处理
  case WM_LBUTTONDOWN:
    bIsDraw = TRUE;
    // 刷新整个客户区
    InvalidateRect(hWnd, &rt, TRUE);
    break;

  case WM_RBUTTONDOWN:
    bIsDraw = FALSE;
    // 刷新整个客户区
    InvalidateRect(hWnd, &rt, TRUE);
    break;

    // 绘制消息处理
  case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);

    if(bIsDraw)
    {
      // 绘制文字到客户区,文字顶端居中
      DrawText(hdc, szBuff, strlen(szBuff), &rt, DT_CENTER);
    }//end if

    EndPaint(hWnd, &ps);
    break;

    // 窗口销毁消息,关闭窗口时响应。
  case WM_DESTROY:
    PostQuitMessage(0);
    break;

  default:
    // 调用系统默认消息处理,即交给系统处理。
    return DefWindowProc(hWnd, message, wParam, lParam);
  }//end switch

  return 0;
}

 

首先,定义布尔型静态变量bIsDraw,用来识别是否绘制,默认不做绘制操作;

其次,通过GetClientRect函数获得客户区的尺寸;

再次,就是消息的捕获过程。在WM_LBUTTONDOWN消息处,bIsDraw赋值为TRUE,目的是鼠标左键按下则执行绘制操作。虽然变量已经设置,但如果不执行InvalidateRect函数,它不会做刷新操作(即不会执行WM_PAINT消息)。在WM_RBUTTONDOWN消息处,bIsDraw赋值为FALSE,目的是鼠标右键按下后,不再执行文字的绘制操作。同样需要执行InvalidateRect函数,刷新一下窗口;

最后,在WM_PAINT消息处,判断bIsDraw的值,如果为真执行绘制文字操作,否则不做任何操作。

 

转载请附上原文出处链接及本声明
李老师的博客 » 第5章 图形设备接口与图形绘制

发表评论

提供最优质的文章集合

立即查看 了解详情