1.3 GDI+的 MFC 编程
本节介绍利用 MFC 进行 GDI+编程的必要的准备,并通过例子说明 GDI+编程的具体步 骤,最后给出如何解决存在的 new 操作符问题的方法。
C++封装的 GDI+的(英文)帮助内容,位于 VS08 的“目录/Win32 和 COM 开发/Graphics and Multimedia/GDI+”,主要的参考资料位于其子目录“GDI+ Reference”中。
1.3.1 设置与初始化
封装了 GDI+ API 的各种 C++类、函数、常量、枚举和结构,都被定义在 Gdiplus.h 头 文件所包含的一系列头文件中。所以,采用 MFC 进行 GDI+编程,必须包含 Gdiplus.h 头文 件。
从 14.1.2 的有关 GDI+平面 API 的讨论可知,封装在 GDI+类中方法,最后都需要调用GDI+平面 API 中的相关底层函数,才能完成实际的操作。所以,为了运行 GDI+应用程序,在操作系统平台中,必须安装动态链接库 Gdiplus.dll。对 Windows XP 及以上版本,该 DLL已经自动被操作系统包含。
该动态链接库所对应的静态库文件为 GdiPlus.lib,而且它在 VC08 及之前的早期版本中 不是 C++和 MFC 的默认链接库。所以,对早期的 VC 版本必须在项目设置,添加该库作为 链接器输入的附加依赖项。但是对 VC08 SP1 及 VC10,该库已经成为标准链接库之一,不 必再为链接器输入的附加依赖项添加此库。
因为在 Gdiplus.h 头文件中,将所有的 GDI+的类、函数、常量、枚举和结构等都定义在 了命名空间 Gdiplus 中。所以,一般在 GDI+程序中,都应该使用如下的命名空间声明:
using namespace Gdiplus;
(1)VC 中的设置
为了在 MFC 应用程序中能使用 GDI+,必须包含 GDI+头文件、使用 GDI+命名空间。 对 VC08 及之前的版本,还要为项目添加 GDI+链接库。
1) 包含头文件、使用命名空间——在要使用 GDI+的文件(如视图类的头文件或代码 文件)头部包含 GDI+的头文件:
#include <gdiplus.h>
并加上使用 GDI+命名空间的 using 指令(区分大小写,注意首字母大写):
using namespace Gdiplus;
2) 添加链接库(对 VC08 SP1 及 VC10 不必添加)——在 VS08 及其早期版本中,选 “项目/*属性”菜单项,打开项目的属性页窗口,先选“所有配置”,再选“配置 属性/链接器/输入”项,在右边上部的“附加依赖项”栏的右边,键入 GdiPlus.lib(参见图 14-9)后按“应用”钮,最后按“确定”钮关闭对话框。
(2)GDI+的初始化与清除
为了在 MFC 应用程序中使用采用 C++封装的 GDI+ API,必须在 MFC 项目的应用程序 类 中 , 调 用 GDI+ 命 名 空 间 中 的 GDI+ 启 动 函 数 GdiplusStartup 和 GDI+ 关 闭 函 数 GdiplusShutdown,来对 GDI+进行初始化(装入动态链接库 Gdiplus.dll,或锁定标志+1)和 清除(卸载动态链接库 Gdiplus.dll,或锁定标志-1)工作。它们一般分别在应用程序类的InitInstance 和 ExitInstance 重载方法中调用。
图 14-9 在项目属性对话框中添加静态链接库
函数 GdiplusStartup 和 GdiplusShutdown,都被定义在 GdiplusInit.h 头文件中:
Status WINAPI GdiplusStartup( OUT ULONG_PTR *token,
const GdiplusStartupInput *input, OUT GdiplusStartupOutput *output);
void GdiplusShutdown(ULONG_PTR token);
其中:
类型 ULONG_PTR,是用无符号长整数表示的指针,被定义在 basetsd.h 头文件中:
typedef _W64 unsigned long ULONG_PTR;
输出参数 token(权标),供关闭 GDI+的函数使用,所以必须设置为应用程序类的 成员变量(或全局变量,不提倡)。
结构 GdiplusStartupInput 和 GdiplusStartupOutput,都被定义在 GdiplusInit.h 头文件中。
GDI+启动输入结构指针参数 input,一般取默认构造值即可,即(设:无调 试事件回调过程、不抑制背景线程、不抑制外部编解码):
input = GdiplusStartupInput(NULL, FALSE, FALSE);
GDI+启动输出结构指针参数 output,一般不需要,取为 NULL 即可。 注意,采用 MFC 进行 GDI+ API 编程时,在使用任何 GDI+的功能调用之前,必须先调用 GDI+启动函数 GdiplusStartup 来进行初始化 GDI+的工作;在完成所有的 GDI+功能调用 之后,必须调用 GDI+关闭函数 GdiplusShutdown 来进行清除 GDI+的工作。
(3)过程框图
图 14-10 是使用 MFC 进行 GDI+编程的设置、准备与初始化过程的逻辑框图。
图 14-10 GDI+的设置、准备与初始化
1.3.2 编程例子
下面通过一个简单的例子,来说明如何使用 GDI+进行应用程序开发。
(1)创建和设置
创建一个名为 Gdip 的传统界面 MFC 单文档应用程序项目,在应用程序类和视图类的CPP 代码文件中,包含头文件并使用命名空间:
#include <gdiplus.h>
using namespace Gdiplus;
对 VC08 及之前的版本还需在项目属性中添加链接库 GdiPlus.lib。
(2)初始化与清除
然后再进行 GDI+系统的初始化,这需要在应用程序类 CGdipApp 中声明一个成员变量:
ULONG_PTR m_gdiplusToken; // ULONG PTR 为 int64 类型 并在该类的初始化函数 CGdipApp::InitInstance()中加入以下代码来对 GDI+进行初始化:
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
注意:这两个语句必须加在应用程序类的 InitInstance 函数中的
CWinApp::InitInstance();
语句之前,不然以后会造成视图窗口不能自动重画、程序中不能使用字体等等一系列问题。
还要在 CGdipApp::ExitInstance()中加入以下代码来关闭 GDI+:
GdiplusShutdown(m_gdiplusToken);
上面的 InitInstance 和 ExitInstance 都是应用程序类的重写型方法。而且,默认时 VC08 SP1 及其以前版本是不会自动生成 ExitInstance 方法代码的(不过 VC10 会自动生成此方法),需 要自己利用属性窗口来添加(不要手工添加)。
(3)绘图
接下来就可以利用 GDI+进行绘图了。下面的代码段是在 OnDraw 函数中画一个带网格 的透明度连续变化的图:
CGdipView::OnDraw(CDC* pDC) {
……
Graphics graph(pDC->m_hDC); // 创建图形对象
Pen bluePen(Color(0, 0, 255)); // 创建蓝色笔
Pen redPen(Color(255, 0, 0)); // 创建红色笔
int y = 255; // y 的初值
for (int x = 0; x < 256; x += 5) { // 绘制红蓝网线
graph.DrawLine(&bluePen, 0, y, x, 0);
graph.DrawLine(&redPen, 255, x, y, 255);
y -= 5;
}
// 画一组绿色透明度垂直渐变的水平线(填满正方形)
for (y = 0; y < 256; y++) {
Pen pen(Color(y, 0, 255, 0)); // α 随 y 变的绿色笔
graph.DrawLine(&pen, 0, y, 255, y);
}
// 画一组品红色透明度水平渐变的垂直线(填满扁矩形)
for (int x = 0; x < 256; x++) {
Pen pen(Color(x, 255, 0, 255)); // α 随 x 变的品红色笔
graph.DrawLine(&pen, x, 100, x, 200);
}
}
运行的结果如图 14-11 所示。其中,左图为第一个循环所绘制的结果、中图为前两个循 环所绘制的结果、右图为全部三个循环所绘制的结果。
图 14-11 透明度的连续变化
1.3.3 new 问题
在 VC08(包括 SP1)中使用 GDI+时,不能用 new 来动态创建 GDI+对象。解决办法有 如下两种:
(1)修改 GdiplusBase 类
打开(默认)位于“C:\Program Files\Microsoft SDKs\Windows\v6.0A\ Include\”目录中 的 Gdiplus Base.h 头文件,并注释掉里面 Gdiplus Base 类的内容(该类其实只含 new、new[]、 delete 和 delete[]这四个运算符的重载),使其成为一个空类(但不要删除整个类)。
为了不修改原始安装目录中的 Gdiplus Base.h 头文件,可以:
将该头文件复制到你的项目目录中。
注释掉该头文件里面 Gdiplus Base 类的内容(保留类定义)。
在项目中所有的#include 语句之前,包含"Gdiplus Base.h"头文件,形如:
#include "gdiplusBase.h" #include <gdiplus.h>
则编译系统会优先包含项目目录中的 gdiplus Base.h 头文件,从而屏蔽掉原来位于 平台 SDK 的 Include 目录中的同名头文件。
(2)用&代替 new
也可以在有些使用 new 的地方改用&,例如将代码 Pen pPen = new Pen(Color::Red); 改 为 Pen pPen = &Pen(Color::Red);。