Windows编程系列——第四讲:GDI
Windows编程系列——第四讲:GDI
GDI是Graphics Devices Interface 的缩写,含义是图形设备接口。主要任务是负责系统与绘图程序之间的信息交换,处理所有的图形输出。利用GDI提供的大量函数可以方便地在屏幕、打印机以及其他输出设备上完成输出图形、文本等操作。GDI的出现使程序员无需关系硬件设备与驱动,实现了程序开发与硬件的隔离,大大简化了开发工作。
4.1 图形设备上下文
图形设备上下文(Device Context,这个词有很多翻译,直译应该是设备上下文)是GDI内部保存数据的一种数据结构,它定义了GDI函数在不同设备特定区域的工作方式。以视频显示器为例,图形设备上下文代表了屏幕的一块区域。要想在屏幕上这个区域输出图形或文字,就必须先获得代表此区域的图形设备上下文句柄(HDC),以该HDC作为参数调用的GDI函数都是针对该区域的操作!(这句话很重要,可以拿小本本记下来(っ╹◡╹)ノ)
一旦获得HDC,系统就是使用默认的属性值填充图形设备上下文。如果有必要,我们还可以使用一些GDI函数获取和改变图形设备上下文中的属性值。
4.2 WM_PAINT消息
当客户区一部分或全部变为“无效”或者必须“刷新”时,窗口函数将接收到WM_PAINT消息,并且完成各种计算结果的输出工作。
客户区为什么会无效呢?当用户改变窗口大小时、当窗口由最小化状态恢复到以前大小时,都会使客户区无效。事实上,在最初创建窗口的时候,整个客户区都是无效的,所以通常在系统空闲的时候向窗口发送一个WM_PAINT消息。为了保证在系统比较忙的时候能够正确显示窗口内容,通常会在显示窗口后调用UpdateWindow强迫窗口函数进行绘制。
窗口过程接收到WM_PAINT消息后,并不代表整个客户区都要被刷新,有可能客户区无效的区域只有一小块(称为无效区域),程序只需要更新这个区域。所以,Windows为每个窗口维护了一个绘图信息结构体(PAINTSTRUCT)类型的变量,无效区域的坐标就在其中。WM_PAINT消息也是一个低级别的消息,Windows总是在消息队列为空时才把WM_PAINT放入其中。
typedef struct tagPAINTSTRUCT {
HDC hdc;//图形设备上下文的句柄
BOOL fErase;//是否用背景画刷擦除
RECT rcPaint;//无效区域的矩形位置
BOOL fRestore;//以下三个参数保留
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;
注意,如果在窗口函数处理WM_PAINT消息之前,客户区的另一块区域变为无效,则Windows计算出一个包围两个无效矩形的新的矩形区域,并将这种变化后的信息存放到PAINTSTRUCT中。Windows不会将多个WM_PAINT放到消息队列中。
4.3 GDI版本的 Hello World
正如前面所说,应该在处理WM_PAINT消息的时候完成各种输出工作。或许你也注意到了,在前面我们创建的项目代码中,WndProc函数里面就有处理WM_PAINT消息,见下图:
当时之所以没讲解这段代码就是因为HDC还没有讲。不过,WndProc只是提供了一个处理WM_PAINT消息的模板,如果想要输出Hello World ,还要自己添加代码。在这之前,我们先来看一下模板~
BeginPaint和EndPaint的第一个参数都是窗口句柄,也就是通过窗口函数第一个参数传过来的;第二个参数是指向PAINTSTRUCT结构体的指针,这个结构体如上所述包含了一些可以在重绘客户区时使用的信息。BeginPaint会把ps中的无效区域清空。如果不调用BeginPaint,无效区域始终不会被清空,那么根据前面讲的,每当系统发现消息队列为空,而又总是存在不被清空的无效区域,操作系统就会不断向消息队列发送WM_PAINT消息,导致CPU占用率提高。
现在我们开始显示Hello World喽,修改case语句如下——
case WM_PAINT:
{
const TCHAR * str = L"Hello World !";
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
RECT r;//矩形框结构体
GetClientRect(hWnd, &r);
DrawText(hdc, str, -1, &r, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hWnd, &ps);
}
break;
运行效果图:
首先,RECT是一个结构体,用来描述一个矩形框,在MSDN中可以找到它的详细信息。
然后,使用GetClientRect(hWnd, &r)获得客户区的矩形框。
再然后,就是调用DrawText输出文本了。它的函数原型见下面:
int DrawText(
HDC hdc,//图形设备上下文句柄
LPCTSTR lpchText,//要输出的字符串
int cchText,//输出字符串的个数;
//-1则代表输出整个字符串(NULL-teminated)
LPRECT lprc,//输出的矩形区域
UINT format//字符绘制的各种选项
);
第五个参数用来设置格式,例如DT_SINGLELINE 表示单行, DT_CENTER表示水平居中, DT_VCENTER表示垂直居中。如果函数成功, 则返回值是逻辑单元中文本的高度。如果指定了 DT_VCENTER 或 DT_BOTTOM, 则返回值是从矩形框顶部到文本底部的偏移量。如果函数失败, 返回值为零。
需要知道的是,DrawText是一个用户级别的函数,它的实现包含在user32.dll中。DrawText最终还是要调用GDI函数中的TextOutA函数(旧版本好像是TextOut,不过我在MSDN中没找到)。TextOutA函数原型如下:
BOOL TextOutA(
//输出成功,返回非零值;否则返回0
HDC hdc,
int x,//起始位置的x轴坐标
int y,//起始位置的y轴坐标
LPCSTR lpString,//要输出的字符串
int c//要输出的字符串的长度
);
x、y 指定客户区内字符串的开始位置,也就是第一个字符的左上角位置。采用的是逻辑坐标(Logical Coordinates)。Windows有多种映射方式可以把逻辑坐标转换成显示器的物理像素坐标。图形设备上下文中记录了采用什么映射方式,它的默认值是MM_TEXT。在该模式的映射下,逻辑单位与物理单位相同,都是以客户区左上角为坐标原点,横坐标的值按像素数从左到右递增,纵坐标的值从上到下递增。
可以使用SetMapMode函数设置映射方式,具体参考MSDN
好了,GDI先讲这么多,以后还会讲到。博主现在忙于OpenCV,忙完再更。