GDI+初次使用完全介绍和踩坑跳坑说明

6165 人浏览 | 时间: 2016-06-26 23:38:04 | 作者: codexia 会员文章,禁止转载

    之前做的MFC下的Direct UI界面框架是采用GDI做的,虽然基本界面都是可以满足的,但是在绚丽的界面上还是不足的。当然用GDI做也是可以的,只是做起来太费事了。所以选用GDI+做图形支持。
    GDI+在函数和使用方面和GDI还是有所区别的,不过有很多地方使用方式还是差不多,GDI+包装了GDI,而且加入了很多特性,也就是做了很多处理,让图形处理方面更加强悍。GDI+替我们做了很多基础的工作,比如抗锯齿方面。所以用GDI+是很方便的,当然,也是有很多地方需要注意的。

    之前只是看了下GDI+的书,没有实战。然后今天重温了一下GDI+的使用,然后写了一个完整的测试使用代码,然后踩了很多坑。

    那么下面将给大家展示一个完整的GDI+使用流程和说明其中的坑,以免你第一次使用时被这些坑坑的不想用GDI+了。
    基本流程代码如下:
#include <gdiplus.h>
#pragma comment(lib,"gdiplus.lib")
using namespace Gdiplus;

//初始化工作
GdiplusStartupInput gdiplusStartupInput;//初始化结构体
ULONG_PTR gdiplusToken;//当做句柄来理解
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput,NULL);
HDC hdc = ::GetDC(AfxGetMainWnd()->m_hWnd);
//绘画部分
Graphics graphics(hdc);
Pen pen(Color(255,255,0,0));
graphics.DrawRectangle(&pen,0,0,300,300);
//绘画结束
GdiplusShutdown(gdiplusToken);//需要确保GDI+资源都被析构了才能释放GDI+环境资源,否则报内存错误,所以一般放在程序结束前释放

    总的来说就是,分为四个部分。第一部分就是头文件、Lib文件、命名空间的准备;第二部分就是初始化工作;第三部分就是用GDI+绘画部分;第四部分就是结束绘画清理环境。
    第一部分:文件准备和命名空间
    包含头文件,这是我们使用类库最基本的做法。我相信你很容易接受。GDI+的+号英文就是plus,所以头文件就是gdiplus.h。导入GDI+的lib文件我们使用指令导入,比较方便,就不用在项目中设置了。导入了lib之后,就可以使用GDI+的dll了。如果忘记了导入lib,则会提示无法解析的外部符号等链接错误。然后就是命名空间Gdiplus,因为GDI+类中定义了很多符号,所以用了一个专用的命令空间。如果你忘记了使用命名空间,会提示未定义的标识符编译错误。如果你使用了GDI+的命名空间,这样GDI+的标识符就不用每一个都加上Gdiplus::了。然而如果你发现你使用的GDI+的标识符和其他标识符冲突了,两个类库都定义了一个标识符,那么你就要用命名空间明确指示,如Gdiplus::Pen,否则编译器无法确定用哪一个。这里有一个可能出错的地方,你可能会用GDI+类作为前缀来确定标识符范围,如Graphics::Pen。这是错误的。GDI+中的Pen本身就是一个类,怎么能用Graphics类作用域呢!不过这是初次接触GDI+的人容易犯的错,原因是因为对GDI+不熟,所以就在此提醒了一下。
    第二部分:初始化工作
    因为GDI+封装了GDI,并且以类库形式提供使用,所以使用前需要进行初始化。这和GDI使用不一样。在使用GDI+前必然要准备一些环境,比如版本、是否抑制后台线程,是否抑制扩展的图形编码和解码。这些是GDI+内部需要处理的事情,使用GDI+前需要初始化,才能正常使用GDI+。
    初始化的函数为GdiplusStartup。它有三个参数,我们看看前面两个就好,最后一个忽略。第一个参数为ULONG_PTR类型的变量地址,作为GDI+的一个标志Token,对GDI来说就好比是DC句柄。具体有什么用,我们暂时不用管。所以我们创建一个变量,然后传入地址即可。这个变量会在释放GDI+环境时使用。第二个参数为GdiplusStartupInput结构体变量的地址。这个结构体存储了GDI+版本、一个调试回调函数指针、一个布尔值指示是否抑制后台线程、一个布尔值指示是否抑制外部图像编解码器。GDI+版本我们好理解,其他我们也暂时不用了解。我们只要创建这个结构体变量,然后将地址传入GdiplusStartup即可,这样相当于传入了默认的参数。当然我们也可以自己设置参数,但是一定要设置对,否则无法正常初始化。设置结构体的代码如下:
GdiplusStartupInput gdiplusStartupInput;//初始化结构体
gdiplusStartupInput.GdiplusVersion=2;//GDI+版本
gdiplusStartupInput.SuppressBackgroundThread=FALSE;//抑制后台线程,设置为TRUE会导致初始化失败
gdiplusStartupInput.SuppressExternalCodecs=FALSE;//抑制外部图像编/解码
//GDI+版本、一个调试回调函数指针、一个布尔值指示是否抑制后台线程、一个布尔值指示是否抑制外部图像编解码器
ULONG_PTR gdiplusToken;//当做句柄来理解
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput,NULL);

    但是你要注意,SuppressBackgroundThread设置为TRUE后,会导致初始化失败。暂时不要考虑原因,你知道这个事情就行,等熟悉了再深究。一般情况,我们不需要自己设置。其他参数设置影响倒是不是这么大,你可以自己尝试。初始化后,你创建GDI+对象,调试运行,将鼠标放在GDI+对象上,看看提示就知道是否初始化成功,会有提示的。这个就不截图了,自己看看吧。
    第三部分:绘图
    我们就可以开始创建GDI+对象,并调用对象的成员函数绘图了。GDI+类Graphics几乎封装了所有的GDI函数哦。而且对GDI+对象做一些设置后,会增加更多的处理,如抗锯齿。代码如下:
Graphics graphics(hdc);//创建一个GDI+对象graphics
Pen pen(Color(255,255,0,0));//创建一个画笔对象
graphics.DrawRectangle(&pen,0,0,300,300);//画一个矩形

    第四部分:释放GDI+环境
    释放GDI+环境调用GdiplusShutdown函数即可,此函数只有一个参数,也就是GdiplusStartup函数获取的第一个参数。释放GDI+环境就这么简单。但是,这里却有一个坑。前面要是坑比较小,还好说。这个坑对于不熟悉GDI+的人来说,很隐蔽。
    GDI+是用类包装GDI的,在内部会自动管理各种资源。所以我们不用创建一个GDI对象,而是GDI+封装的各种类对象。类对象有生存周期,这样也就让问题出来了。如果过早释放了GDI+环境,就会导致类对象在析构的时候,操作了无法操作的内存,从而报错。像我们示例的流程的代码,就会有问题,也就是内存访问错误。因为在函数中,局部的对象没有析构,所以GDI资源没有释放,而随后GdiplusShutdown函数执行后,GDI+环境就释放了,这样随后再释放内部的GDI资源就要崩溃了。至于内部如何崩溃,我们暂时就不讨论了,暂时知道这个道理就行了,等你熟悉了深入学习了GDI+,你自然就明白了。如果你一开始测试发现有这个问题,暂时注释GdiplusShutdown函数即可。正常使用时,一般在程序结束时才会释放环境,在程序一开始时初始化,就不容易有这样的问题。但是静态全局变量可能有这样的问题,请避免这样的情况出现。
【GDI+在MFC的对话框上画的矩形】
    整个流程和几个你可能会遇到坑就告诉你了,希望你第一次使用GDI+会更加顺利。有了好的开始,后面学习会更加有动力。
当前文章为会员文章,请前往[用户中心]开通会员后继续阅读。