欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Windows编程系列——第四讲:GDI

程序员文章站 2024-03-22 13:18:22
...

上一讲:Windows编程系列——第三讲:创建窗口(下)


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消息,见下图:
Windows编程系列——第四讲:GDI
        当时之所以没讲解这段代码就是因为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;

运行效果图:
Windows编程系列——第四讲:GDI

        首先,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,忙完再更。