第三讲:GDI+编程的基本概念 并完成坐标系的绘制
GDI+是Windows XP中的一个子系统,它主要负责在显示屏幕和打印设备输出有关信息,它是一组通过C++类实现的应用程序编程接口。GDI+对以前的Windows版本中GDI进行了优化,并添加了许多新的功能。作为图形设备接口的GDI+使得应用程序开发人员在输出屏幕和打印机信息的时候无需考虑具体显示设备的细节,他们只需要调用GDI+库输出的类的一些方法既可以完成图形操作。
GDI+的使用方法
在使用GDI+的CPP文件中包含GdiPlus.h文件,并引用命名空间 using namespace Gdiplus; 代码如下:
#include <GdiPlus.h>
using namespace Gdiplus;
除此之前,还需要在项目属性中加入gdiplus.lib以支持GDI+的静态库编译。具体是右键项目属性,选择链接器,再选择输入,在附加依赖项中加入 gdiplus.lib,这样就可以保证程序在编译时能找到GDI+
同时GDI+还需要用到COM的东西,所以还需要包含
#include <comdef.h>
并在链接器的输入附加依赖项中添加 msimg32.lib静态库
以上是使用GDI+之前的配置工作,以准备就绪。在GDI+使用之前还需要对其进行初始化:
//GDI+资源初始化
ULONG_PTR uToKen = 0;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&uToKen,&gdiplusStartupInput,NULL);
uToKen是在用完GDI+绘制完之后,需要对其卸载时需要使用uToKen,在应用程序结束的时候,使用GdiplusShutdown(uToKen);来销毁GDI+资源。
//销毁GDI+资源
GdiplusShutdown(uToKen);
ARGB中 A为Alpha即透明度的意思,当A值为0是代表颜色完全透明,为255时表示完成不透明。颜色透明度是GDI+中引入的新概念,颜色的透明度就是相当于与背景色的混合程度。透明度合成运算:
透明度是像素之间的一种合成运算。它的计算公式是:
输出颜色 =前景色* Alpha /255 +背景色*(255-Alpha)/255
举个例子来说,假设有一个点,其颜色值为RGB(0,0,0),背景色值为RGB(255,255,255),在进行输出时将透明度设置为100,输出的颜色为:
Result.Red = 0*100/255 +255(255-100)/255 = 155
Result.Green =0*100/255 + 255(255-100)/255 = 155
Result.Blue = 0*100/255 + 255(255-100)/255 = 155
GDI+中最常用的几个类包括Bitmap类, Pen类等
Bitmap类
Bitmap类继承于Image类,它的构造如下:
Bitmap(const WCHAR *filename);
//通过图片的文件名来构造一个Bitmap对象。
Bitmap(INT width, INT height, PixelFormat format = PixelFormat32bppARGB);
//通过位图的宽度,位图的高度,像素格式来构造一个空的Bitmap对象,其中format的默认值是PixelFormat32bppARGB,代表32位的颜色信息,颜色组成是A,R,G,B。像素格式还有很多种,比如PixelFormat24bppRGB代表24位的颜色信息,颜色组成为R,G,B。比如PixelFormat16bppRGB代表16位的颜色信息,颜色组成为R,G,B其中R占5位,G占6位,B占5位。
Bitmap类的常用方法
Bitmap* Clone(const Rect& rect, PixelFormat format);
Bitmap* Clone(INT X , INT y, INT width, INT height, PixelFormat format);
Bitmap* Clone(const RectF &rect, PixelFormat format);
Bitmap* Clone(REAl x, REAL y , REAL widht, REAL height, PixelFormat format);
//以上四个方法为Bitmap的拷贝方法,从源位图的指定区域拷贝出新的位图对象出来,并用format像素格式应用在新的Bitmap对象中。
UINT GetWidth();
//获取位图的宽度
UINT GetHeight();
//获取位图的高度
UINT GetPixel(INT X, INT Y , Color *color);
//获取位图指定位置的颜色
SetPixel(INT X, INT Y ,const Color &color);
//设置位图指定位置的颜色
常用的画刷
单色画刷, 类名SolidBrush,用纯色填充图形
影线画刷, 类名HatchBrush,用各种线型图案填充图形。
纹理画刷, 类名TextureBrush,使用图像来填充图形
线性渐变画刷,类名LinearGradientBrush ,使用渐变的色彩填充图形,渐变是指一种颜色沿着指定的方向慢慢变为另一种颜色。
SolidBrush创建单色画刷
SolidBrush(const Color &color);
那么下面的代码创建了一个蓝色的单色画刷
SolidBrush blueBrush(Color(255,0,0,255));
HatchBrush创建影线画刷
HatchBrush(HatchStyle hatchStyle, const Color& foreColor,const Color& backColor);
hatchStyle:影线画刷的类型
foreColor:影线画刷线条的前景色
backColor:影线画刷线条的背景色。
HatchBrush创建影线画刷代码如下:
Color black(255,0,0,0);
Color white(255,255,255,255);
HatchBrush brushA(HatchSytleHorizontal, black, white);//水平横线
HatchBrush brushB(HatchSytleVertical, black, white); //垂直竖线
HatchBrush brushC(HatchStyleCross, black,white); //十字网格线
在利用HatchBrush创建影线画刷时,前景色与背景色的关系,就像国际象棋盘,第一块为背景色,第二块为前景色,第三块为背景色,第....,如图
TextureBrush创建纹理画刷
TextureBrush(Image *image);
image为图像的指针。
如:Image image(L”TEXTURE.bmp”);
TextureBrush textureBrush(&image);
LinearGradientBrush创建渐变画刷
LinearGradientBrush(Point &point1, Point &point2, Color &color1, Color &color2);
point1为渐变的起点坐标。
point2为渐变的终点坐标。
color1为起点的颜色。
color2为终点的颜色。
GDI+的画笔类Pen
Pen创建画笔
Pen(const Color &color, REAL width = 1.0f);
//color为画笔的颜色,width为画笔的宽度。
Pen(const Brush *brush, REAL width = 1.0f);
//brush为画刷的指针,width为画笔的宽度。通过画刷来构造一个画笔,这种画笔绘制出来的线条,边框就犹如在一张带图案的纸张上,剪出的指定宽度的线条,边框的效果。
GDI+绘制图片
采用 Grahpics类里面的一个DrawImage函数来绘制的。
把图片绘制在指定位置的方法如下:
DrawImage(Image* image, const Point& point);
DrawImage(Image* image, INT x , INT y);
DrawImage(Image* image, const PointF& point);
DrawImage(Image* image,REAL x, REAL y);
其中x,y为要绘制的位置,可为INT类型,也可以是REAL类型。
point为要绘制的位置的结构体变量,可为Point类型(内部成员为INT类型),也可以为PointF类型(内部成员为REAL类型)
REAL类型,即为实参类型。
把图片绘制在指定区域的方法如下:
DrawImage(Image* image, const Rect& rect);
DrawImage(Image* image,INT x ,INT y, INT width, INT height);
DrawImage(Image* image,const RectF& rect);
DrawImage(Image* image, REAL x, REAL y , REAL width, REAL height);
其中x,y为指定矩形区域的左上角坐标,可是INT类型,也可以是REAL类型。
width,height为指定矩形的宽度和高度,可以为INT类型,也可以为REAL类型。
rect为指定的矩形区域,可以为Rect类型(内部成员为INT类型),也可以为RectF类型(内部成员为REAL类型)。
把图片的指定区域绘制在指定目标区域的方法如下:
DrawImage(Image* image, const Rect& destRect, INT srcx, INT srcy, INT srcwidth, INT srcheight, Unit srcUnit);
DrawImage(Image* image, const RectF& destRect, REAL srcx, REAL srcy, REAL srcwidth, REAL srcheight, Unit srcUnit);
destRect为指定目标区域,可以为Rect类型(内部成员为INT类型),也可以是RectF类型(内存成员为REAL类型)。
srcx,srcy为图片指定区域的左上角的坐标,可以为INT类型,也可以是REAL类型。
srcUnit为Unit的枚举成员,它指定了所用的度量单位。比如UnitPixel表示以像素为单位;UnitPoint表示以点为单位; UnitInch表示以英寸为单位。一般情况下,我们是选择UnitPixel,以像素为单位。
GDI+如何绘制文本?可以采用DrawString方法来绘制文本:
DrawString(const WCHAR *string, INT length,const Font *font, const PointF &origin, const Brush *brush);
DrawString(const WCHAR *string, INT length,const Font *font, const RectF &layoutRect, const StringFormat *stringFormat, constBrush *brush);
DrawString(const WCHAR *string, INT length, const Font *font, const PointF &origin,const StringFormat *stringFormat,const Brush *brush);
其中:
string为要绘制的字符串,类型为const WCHAR*,宽字符指针。 length为要绘制的字符串的长串,如果该值为-1,表明绘制为NULL结尾的string字符串。
font为要绘制的文本所使用的字体。比如Font myFont(L”宋体”,16); ,宋体,字体大小为16号。layoutRect为文本输出的矩形区域。origin为文本的绘制起点位置,数据类型是PointF.
stringFormat为文本输出的格式,数据类型是StringFormat*.
brush为绘制文本所使用的画刷。
下面是一个简单的绘制文本的示例代码:
{
Gdiplus::Font myFont(L”Arial”, 10);
SolidBrush brushA(Color(255,0,0,255));
graphics.DrawString(L”示例文本”,-1,&myFont,
PointF(200,575),&brushA);
}
下面来一个带文本输出格式的字符串绘制代码:
{
Gdiplus::Font myFont(L”Arial”,16);
RectF layout(0,0,500.0f,200.0f);
//设置为对齐方式(水平居中对齐)
StringFormat format;
format.SetAlignment(StringAlignmentCenter);
SolidBrush brushB(Color(255,0,0,255));
graphics.DrawString(L”示例文本”,-1,&myFont,layout,&format,&brushB);
}
这里是先构建一个字体,然后定义要绘制的是哪一块区域,设置对齐方式和画刷,然后调用DrawString()方法来绘制文本。但是需要注意的是,我们怎么知道将要绘制的文本,是否能够被全部容纳在指定的区域当中?就是说在指定的区域中将这个文本画进去,能全部画得进去吗?那么就要计算一下这个区域是否可以真的容纳得下要绘制的内容,就需要GDI+中的一个字符串测量方法:MeasureString来解决了。此方法可以对指定的区域和输出的文本的字符串进行大小尺寸的计算,以确定是否能够在指定的区域里能否正常显示所要绘制的文本内容。MeasureString测量字符串的常用方法如下:
MeasureString(const WCHAR *string, INT length, const Font *font, const PointF &origin, const StringFormat *stringFormat, RectF *boundingBox);
MeasureString(const WCHAR *string, INT length,const Font *font, const PointF &origin, RectF *boundingBox);
MeasureString(const WCHAR *string, INT length, const Font *font, const RectF &layoutRect, const StringFormat *stringFormat, RectF *boundingBox);
MeasureString(const WCHAR *string , INT length, const Font *font, const RectF &layoutRect, RectF *boundingBox);
boundingBox为测量结果,表示容纳全部文本时所需要的矩形区域。
size为测量结果,表示容纳全部文本时所需要的尺寸。
codepointsFitted为测量结果,表示指定的区域中能够容纳的字符个数。
linesFilled为测量结果,表示指定的区域中能够容纳的字符行数。
origin为文本输出的起点。
在使用MeasureString方法时要特别注意layoutRect参数的设定,因为layoutRect参数影响着boundingBox的计算过程。MeasuringString根据layoutRect的宽度来计算boundingBox的高度。所以可以把layoutRect的高度设置为0,宽度设置为想要的值,通过MeasureString就可以计算出想要的矩形区域了。
如果只能在指定的区域内绘制文本,并且当文本太多的时候,想通过省略号来输出,此时就需要通过字符串的去尾功能。那么StringFormat里就有这么一个功能的方法:SetTrimming
SetTrimming字符串去尾,原型如下:
SetTrimming(StringTrimming trimming);
StringTrimming是一个枚举值,有以下成员:
StringTrimmingNode,代表不使用去尾。
StringTrimmingNodeCharacter,代表以字符为单位去尾。
StringTrimmingWord代表以单词为单位去尾。
StringTrimmingEllipsisCharacter,代表以字符为单位去尾,被去尾的部分用省略号表示。
StringTrimmingEllipsisWord,代表以单词的为单位去尾,被去尾的部分用省略号来表示。
StringTrimmingEllipsisPath,代表省略了字符串的中间部分,保证字符串的首尾都能够显示。这种方式比较不常用。
在GDI中通过MoveToEx()和LineTo()来绘制直线,那么GDI+中是如何绘制直线呢?
GDI+中采用DrawLine函数来绘制直线。
DrawLine(const Pen* pen, INT x1, INT Y1 ,INT x2, INT y2);
DrawLine(const Pen* pen, const Point& pt1, const Point& pt2);
DrawLine(const Pen* pen, REAL x1, REAL y1, REAL x2,REAL y2);
DrawLine(const Pen* pen, const PointF& pt1, const PointF& pt2);
其中:pen为GDI+画笔
x1, y1为直线的起点坐标,可为INT类型,也可是REAL类型。
x2,y2为直线的终点坐标,可以是INT类型,也可为REAL类型。
pt1为直线的起点坐标,可是Point类型,也可为PointF类型。
pt2为直线的终点坐标,可是Point类型,也可为PointF类型。
从上面可以看出,在GDI+中画笔是通过参数来传入的,而在GDI中画笔是通过SelectObject来选择到当前的DC中去的。
在GDI+中采用DrawRectangle()来绘制矩形,它的常用的重载函数如下:
DrawRectangle(const Pen* pen , INT x, INT y ,INT width, INT height);
DrawRectangle(const Pen* pen ,const Rect& rect);
DrawRectangle(const Pen* pen, REAL x ,REAL y , REAL width, REAL height);
DrawRectangle(const Pen* pen, const RectF& rect);
其中:
pen为GDI+的画笔。
x,y为矩形的左上角坐标位置,可为INT类型,也可为REAL类型。
width, height为矩形的宽度与高度,可为INT类型,也可为REAL类型。
rect为矩形的区域,可为Rect类型(内部成员是INT类型),也可为RectF类型(内部成员是REAL类型)。
在GDI+中圆的绘制,使用DrawEllipse()函数
DrawEllipse(const Pen* pen , INT x , INT y, INT width, INT height);
DrawEllipse(const Pen* pen, const Rect& rect);
DrawEllipse(const Pen* pen, REAL x, REAL y , REAL width, REAL height);
DrawEllipse(const Pen* pen , const RectF& rect);
其中:pen为GDI+画笔
x为圆的外切矩形的左上角x坐标,可为INT类型,也可为REAL类型。
y为圆的外切矩形的左上角y坐标,可为INT类型,也可为REAL类型。
width为圆的外切矩形的宽度,可为INT类型,也可为REAL类型。
height为圆的外切矩形的高度,可为INT类型,也可为REAL类型。
rect为圆的外切矩形,可为Rect类型,也可为RectF类型。
那么在GDI+中绘制饼图是使用了DrawPie函数
DrawPie(const Pen* pen , INT x, INT y , INT width, INT height, REAL startAngle, REAL sweepAngle);
DrawPie(const Pen* pen, const Rect& rect, REAL startAngle, REAL sweepAngle);
DrawPie(const Pen* pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle);
DrawPie(const Pen* pen, const RectF& rect, REAL startAngle, REAL sweepAngle);
其中pen为GDI+的画笔
x为饼的限定矩形的左上角坐标x,可以为INT类型,也可是REAL类型。
y为饼的限定矩形的左上角坐标y,可以为INT类型,也可是REAL类型。
上面可以看到GDI中绘制图像用Rectangle , LineTo , Ellipse, Pie 而在GDI+中绘制这些图形的函数大多在前面加上了一个Draw,让这些函数意思更加明显,那么上面绘制了这些图形,它们的内部用什么来填充?怎么去填充?在GDI中是用画刷来填充,在GDI+中也是用画刷来填充,但是画刷是通过参数传入的,而不是通过SelectObject()函数来选入的。
在GDI+中填充矩形。
FillRectangle(const Brush* brush, INT x, INT y, INT width, INT height);
FillRectangle(const Brush* brush, const Rect& rect);
FillRectangle(const Brush* brush, REAL x, REAL y , REAL width, REAL heigth);
FillRectangle(const Brush* brush, const RectF& rect);
其中:
brush为GDI+的画刷。
x,y为矩形的左上角坐标位置,可为INT类型,也可为REAL类型。
width,height为矩形的宽度和高度,可以为INT类型,也可以为REAL类型。
rect为矩形额区域,可以为Rect类型(内部成员是INT类型),也可以为RectF类型(内部成员为REAL类型)。
需要注意的是在GDI+中用来绘制图形的函数都是在Graphics这个类中的函数,所以要使用它们需要先声明一个graphics的实例对象,通过这个实例对象来引用,例如:
Graphics graphics(hdc);
graphics.FillRectangle(&brush, &rect);
上面的代码意思是在hdc这个指定的”画布”上,用这个特定的brush画刷在rect这个指定的矩形区域内进行填充。
下面来谈谈GDI与GDI+的区别
1. GDI的设备描述表,即GDI的DC和GDI+中的Graphics的作用和区别
DC:设备描述表,是windows使用的一个数据结构,用于存储具体设备能力和与如何在设备上重绘一些项目有关的属性信息。首先你必须获得一个设备描述表句柄,然后在图形绘制时,你把这个句柄作为一个参数传递给GDI图形绘制函数。当然你也可以把它传递给获得或设置设备描述表有关属性的函数。在GDI+绘图过冲中,利用GDI+函数,就不必使用句柄或设备描述表。相反,你可以简单地创建一个图形对象(Graphics),然后以你熟悉的面向对象的编程方式调用它的方法即可,比如:
myGraphicsObject.DrawLine(parameters) Graphics对象是GDI+的核心,正如设备描述表是GDI的核心一样,设备描述表(DC)和图形对象(Graphics)在不同的环境下扮演着同样的角色,发挥着类似的作用,但是两者也存在着本质的不同。前者使用基于句柄的编程方法而后者使用面向对象的编程方法。
2GDI和GDI+在绘图对象上的区别
在GDI中,所有与绘图有关的绘图对象必须选入指定设备描述表中(使用SelectObject函数来选入),才能被指定为设备描述表所使用。
而在GDI+中,你只需要这些绘图对象作为一个参数传递给图形对象Graphics方法调用即可,每一个图形对象所使用的绘图工具和它调用的方法使用的参数有关,它可以通过参数使用多种pen和Brush绘图而不是与特定的笔和画刷联系在一起。
下面来谈谈GDI+中新增的功能
1.渐变的画刷:
GDI+充许用户创建一个沿路径或直线渐变的画刷,来填充外形(shapes),路径(paths),区域(regions),渐变画刷同样也可以画直线曲线,路径,当你用一个线形画刷填充一个外形(shapes)时,颜色就能够沿外形逐渐变化。
2.基数样条函数
GDI+支持基数样条函数,而GDI不支持。基数样条是一组单个曲线按照一定的顺序连接而成的一条较大曲线。样条由一系列点指定,并通过每一个指定的点。由于基数样条平滑地穿过组中的每一个点(不出现尖角),因而它比用直线连接创建的路径更精确。
3.持久路径对象
在GDI中,路径属于设备描述表(DC),画完后路径就会被破坏。在GDI+中,绘图工作由Graphics对象来完成,可以创建几个与Graphics分开的路径对象,绘图操作时路径对象不被破坏,这样你就可以多次使用同一个路径对象画路径了。
4.变形和矩阵对象
GDI+提供了矩阵对象,一个非常强大的工具,使得编写图形的旋转、平移、缩放代码变得非常容易。一个矩阵对象总是和一个图形变换对象相联系起来,比如,路径对象(PATH)有一个Transform方法,它的一个参数能够接受矩阵对象的地址,每次路径绘制时,它能够根据变换矩阵绘制。
5.可伸缩区域
GDI+在区域(regions)方面对GDI进行了改进,在GDI中,Regions存储在设备坐标中,对Regions唯一可进行图形变换的操作就是对区域进行平移。而GDI+用世界坐标存储区域(Regions),充许对区域进行任何图形变换,其中图形变换以变换矩阵存储。
6.Alpha混合
GDI+支持Alpha Blending(混合),利用alpha融合,你可以指定填充颜色的透明度,透明颜色与背景色相互融合,填充色越透明,背景色显示越清晰。
7.多种图像格式支持
图像在图形界面程序中占有举足轻重的地位,GDI+除了支持BMP等GDI支持的图形格式外,还支持JPEG(Joint Photographic Experts Group),GIF(Graphics Interchange Format),PNG(Exchangeable Image File),TIFF(Tag Image File Format)等图像格式,你可以直接在程序中使用这些图片文件,无需考虑它们所用的压缩算法。
GDI与GDI+混合编程
GDI+在GDI的设备环境DC上进行图形的绘制
Graphics gs(hDC);
其中参数hDC就是GDI的设备环境DC。
GDI+就会把当前的hDC作为默认的目标画布,之后调用Graphics中的任何函数都会被绘制到hDC上。当然每个函数的调用完成并不会立即反映在hDC中。只有当Graphics类析构的时候才会把所有绘制的内容全部一次性地拷贝到hDC中。
GDI在GDI+的Graphics上面进行图形的绘制
Graphics提供了获得HDC的函数:GetHDC(),这样我们就可以在hDC上进行GDI函数的绘制了。
下面来对第三讲做个总结:
首先简单介绍了一下GDI+,它的特点主要是使得开发者无需考虑具体的设备细节。然后介绍了GDI+的使用方法:在使用GDI+之前要完成以下几步工作:
1.完成GDI+配置工作
2.完成GDI+初始化工作
GDI+配置工作:就是说要在使用GDI+的CPP文件中包含GdiPlus.h文件,并引用命名空间 using namespace Gdiplus; 即在需要使用GDI+文件中写下如下代码:
#include <GdiPlus.h>
using namespace Gdiplus;
上面设置完了之后还需要一个步,那就是要在项目属性中加入gdiplus.lib这个库,以支持GDI+的静态库编程。具体操作是右键项目属性,选择链接器,再选择输入,在附加依赖项中加入gdiplus.lib,这样就可以保证程序在编译时找到GDI+
但是我们一般不把GDiPlus.h文件直接加入到所要使用的CPP中,因为如果要使用的CPP比较多的话,一个个都要加入,则显得比较麻烦,那加到哪里比较好呢?
用vs2010来创建应用程序工程时,它N一般会自带帮我们生成一个StdAfx.h和StdAfx.cpp文件,我们一般是把GdiPlus.h文件和using namespace Gdiplus; 这两句写入到StdAfx.h文件中,那这个自带生成的文件StdAfx.h有啥用呢?微软总不会把没用的文件生成进来吧?不过一般的人是不会知道这些文件有什么用的,下面就来说说:
StdAfx.h和StdAfx.cpp这两个文件是用来建立一个预编译的头文件.PCH和一个预定义的类型文件StdAfx.OBJ.由于windows应用程序体系结构非常大,通常会包含许多头文件,如果每次都编译的话比较费时,因此,我们可以常用的头文件放在StdAfx.h中,如afxwin.h , afxext.h, afxdisp.h, afxcmn.h等,然后让StdAfx.cpp包含这个StdAfx.h文件。由于编译器可以识别哪些(没有修改过的)文件已经编译过,所以StdAfx.cpp就只编译一次,并生成所谓的预编译头文件(因为它存放的是头文件编译后的信息,故有此名)。因此在编程时,那些不需要经常修改和常用的头文件就可以加入到StdAfx.h文件中,从而通过采用预编译头文件就可以加速编译过程。所以也是我们为什么要把GdiPlus.h和using namespace std;放入StdAfx.h中的原因,因为这些GdiPlus.h文件我们只需要调用而不需要修改它,那么它在就只需要编译一次就可以了,从而可以节省时间,加快编译过程。
特别注意: GDI+还需要用到COM的东西,所以还需要包含COM的定义
#include <comdef.h>
并需要在链接器的输入附加依赖项中添加msimg32.lib对它的支持。
上面完成了GDI+的设置工作之后,要用GDI+,还要在需要使用GDI+的CPP文件中的_WinMain函数中(当然这里是指win32的应用程序项目)初始化GDI+才行,具体代码如下:
ULONG_PTR uToKen = 0;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&uToKen, &gdiplusStartupInput,NULL);
那么uToKen是在卸载GDI+时GdiplusShutdown(uToKen); 需要来销毁这个资源所需要的一个标识。
然后讲到颜色透明度,以及透明度的合成运算。
接下来讲到GDI+常用的Bitmap类以及它们的原型和常用的方法,然后是常用的画刷的创建和使用,包括单色画刷SolidBrush,影线画刷HatchBrush,纹理画刷TextureBrush,线性渐变画刷 LinearGradientBrush等接下来是GDI+的画笔类Pen ,绘图函数DrawImage(),绘制文本函数DrawString(),绘制直线函数DrawLine(),绘制矩形的DrawRectangle()函数,绘制圆的函数DrawEllipse(),以及绘制饼图函数DrawPie(),还讲到GDI+中的填充矩形FillRectangle()函数,然后讲到了GDI与GDI+中的区别,以及GDI+的新特性。最后简单说明了一下GDI与GDI+如何混合编程。
在之前的工程中我们已经加载了背景图片,并按指定的文本格式绘制了标题,接下来我们要绘制坐标系,那么坐标系主要有两项元素需要绘制,一项是线,另一项是对应的坐标标尺文字。
绘制直线可以使用 MoveToEx函数和LineTo函数来完成,而绘制文本可以采用TextOut函数来完成。
下面我们首先用TextOut函数来绘制坐标标尺上的文字,TextOut原型如下:
BOOL TextOut(
HDC hdc, //handle to DC
int nXstart, // x-coordinate of starting position
int nYstart, // y-coordinate of starting postion
LPCTSTR lpString , //character string
int cbString //number of characters
);
因为标尺文字是红色的,所以在绘制之前我们应该利用SetTextColor()把绘制文本的颜色设置一下,具体代码如下:
COLORREF clrOldText = ::SetTextColor(hdc, RGB(255,0,0));
之后我们就开始用TextOut()来绘制文本了
TextOut(hdc, 5,100,_T(“7000”),4);
这里是说在hdc指定的这个画布上的(5,100)这个位置上绘制文本“7000”, 这个”7000”的长度是 4
那么接下来的代码一样:
TextOut(hdc,5,130,_T("6000"),4);
TextOut(hdc,5,160,_T("5000"),4);
TextOut(hdc,5,190,_T("4000"),4);
TextOut(hdc,5,220,_T("5000"),4);
TextOut(hdc,5,250,_T("2000"),4);
TextOut(hdc,5,280,_T("1000"),4);
绘制完了竖标尺文字之后,还需要绘制横的标尺文字,同样用TextOut()函数来完成
TextOut(hdc,106,323,_T("1月"),2);
TextOut(hdc,182,323,_T("2月"),2);
TextOut(hdc,250,323,_T("3月"),2);
现在第一个坐标的纵向和横向标尺文字都绘制好了,按理来将这个时候就应该把前面设置的文本绘制颜色的红色换回成系统默认的颜色了,但是我们有两个表需要绘制,第二个表的标尺文字也是用红色,所以我们接着把第二个表的标尺信息绘制完,代码如下:
//第二表的纵向标尺文本
TextOut(hdc,5+380,100,_T("7000"),4);
TextOut(hdc,5+380,130,_T("6000"),4);
TextOut(hdc,5+380,160,_T("5000"),4);
TextOut(hdc,5+380,190,_T("4000"),4);
TextOut(hdc,5+380,220,_T("5000"),4);
TextOut(hdc,5+380,250,_T("2000"),4);
TextOut(hdc,5+380,280,_T("1000"),4);
//第二表的横向标尺文本
TextOut(hdc,106+380,323,_T("1月"),2);
TextOut(hdc,182+380,323,_T("2月"),2);
TextOut(hdc,250+380,323,_T("3月"),2);
TextOut(hdc,700,323,_T("4月"),2);
我们要完成的任务中除了要绘制两个坐标系图之外,还需要绘制一个圆和一个饼图,这两个图都有一个自己的标题,我们在这里也一同绘制好:
TextOut(hdc,125+20,665,_T("访问群体"),4);
TextOut(hdc,525+20,665,_T("学习等级"),4);
好现在所有需要红颜色的文本都已经绘制完毕,那么就要恢复系统之前的默认文本颜色:
SetTextColor(hdc,hOldText);
接下来就是要绘制坐标系中的线:
采用MoveToEx和LineTo来绘制,MoveToEx原型如下:
BOOL MoveToEx(
HDC hdc, //handle to device context
int x, // x-coordinate of new current position
int y, // y-coordinate of new current position
LPPOINT lpPoint //old current position
);
LineTo的函数原型:
BOOL LineTo(
HDC hdc, // device context handle
int nXEnd, //x-coordinate of ending point
int nYEnd, //y-coordinate of ending point
);
即MoveToEx是移动到一个新的点(x,y)并返回之前的那个点的坐标(如果最后一个指针参数不是NULL的话),Lineto是从MoveToEx移动到的这个(x,y)开始绘制线条,直到(nXEnd,nYEnd)这个点结束
下面线绘制左边的横向网格线(注意和纵向标尺的文本对齐)
MoveToEx(hdc,54,104,NULL);
LineTo(hdc,346,104);
MoveToEx(hdc,54,134,NULL);
LineTo(hdc,346,134);
MoveToEx(hdc,54,164,NULL);
LineTo(hdc,346,164);
MoveToEx(hdc,54,194,NULL);
LineTo(hdc,346,194);
MoveToEx(hdc,54,224,NULL);
LineTo(hdc,346,224);
MoveToEx(hdc,54,254,NULL);
LineTo(hdc,346,254);
MoveToEx(hdc,54,284,NULL);
LineTo(hdc,346,284);
MoveToEx(hdc,54,314,NULL);
LineTo(hdc,346,314);
然后绘制第一个坐标的竖线
MoveToEx(hdc,54,73,NULL);
LineTo(hdc,54,315);
MoveToEx(hdc,123,73,NULL);
LineTo(hdc,123,315);
MoveToEx(hdc,192,73,NULL);
LineTo(hdc,192,315);
MoveToEx(hdc,261,73,NULL);
LineTo(hdc,261,315);
这样第一个坐标系就已经绘制完成,然后第一个坐标系中还有一条折线,那么这条折线怎么绘制呢?
绘制折线,当然就要有画笔才行,所以首先要创建一支画笔
HPEN hPen = CreatePen(PS_SOLID,RGB(42,187,203));
因为这里是使用的是GDI的方法来绘制,所以创建了画笔之后,还要将其选入到当前的HDC中才能使用。可以通过SelectObject来做这个事情,但是要记住保存老的画笔,以便在使用完这个新的画笔之后,恢复到系统原来的画笔的模样。
HPEN hPenOld = (HPEN)SelectObject(hdc, hPen);
好现在画笔创建并选入了当前的HDC,那么怎么来绘制这根折线呢?很简单,折线其实每一段都是一根直线嘛,所以还是使用MoveToEx和LineTo来绘制,根据要求代码如下:
MoveToEx(hdc, 78,315,NULL);
LineTo(hdc,150,170);
LineTo(hdc,182,239);
LintTo(hdc,250,140);
那么你看到这个不再是MoveToEx和LineTo的配对组合了,其实LineTo(hdc,182,239)是自动接着上面那个LineTo提供的nXEnd = 150, nYEnd = 170这个点直接绘制的,所以就不需要去使用MoveToEx了。
因为MoveToEx是在当前位置移动到一个新的位置,然后后面LineTo就在这个新位置绘制直线,但是现在是折线它在第一根直线结束时不需要移动到另一个位置,直接在这个结束点接着绘制下一根直线,所以LineTo接着上一个LineTo一起使用就是直接上一个LineTo的nXEnd ,nYEnd这个点往下绘制。
那么在使用之前我们创建了hPen这个句柄,现在折线绘制了不需要了就需要把它销毁,但是在销毁之前呢,先要还原之前的老的画笔:
SelectObject(hdc,hPenOld);
DeleteObject(hPen);
好到现在为止第一个坐标系图绘制完毕。F5运行,效果是有了,但是发现那写坐标标尺文字下面有背景色,而我们不希望这些文字下面有白色的填充背景,那这怎么解决呢?
在设置文本颜色的下面使用 SetBKMode来设置文本背景的填充色,这里使用TRANSPARENT即透明的意思即可以解决这个问题,但是需要注意的是,使用完之后要记得把它还要回来,
int bk = ::SetBkMode(hdc,TRANSPARENT);
那么使用完了之后呢,就需要还有回来
::SetBKMode(hdc,bk);
接下来绘制第二个坐标系,上面的第一个坐标系我们采用的GDI来绘制的,现在我们来采用GDI+来绘制:首先要构建一个Graphics类,然后把hdc传入进来,那么接下来用Graphics提供的绘图函数来所做的操作都直接绘制到了传入的hdc指定的画布上去了。Graphics graphics(hdc)就是构建一个由hdc指定的画布,这个hdc就是当前窗口的DC.
首先构建一个Graphics类
Graphics graphics (hdc);
接下来还绘制坐标系线就需要构造一个画笔,声明一个透明度为不透明的宽度为1的笔
Pen pen(Color(255,0,0,0),1.0f);
然后就开始调用DrawLine函数来绘制,根据坐标值的不同设置来绘制横向和纵向坐标,具体代码如下:
//横线
graphics.DrawLine(&pen,54+380,107,396+380,107);
graphics.DrawLine(&pen,54+380,137,396+380,137);
graphics.DrawLine(&pen,54+380,167,396+380,167);
graphics.DrawLine(&pen,54+380,197,396+380,197);
graphics.DrawLine(&pen,54+380,227,396+380,227);
graphics.DrawLine(&pen,54+380,257,396+380,257);
graphics.DrawLine(&pen,54+380,287,396+380,287);
graphics.DrawLine(&pen,54+380,315,396+380,315);
//竖线
graphics.DrawLine(&pen,54+380,73,54+380,315);
graphics.DrawLine(&pen,123+380,73,123+380,315);
graphics.DrawLine(&pen,192+380,73,192+380,315);
graphics.DrawLine(&pen,261+380,73,261+380,315);
graphics.DrawLine(&pen,330+380,73,330+380,315);
到此就完成了第二个坐标系的绘制,操作起来是不是比用GDI绘制要简单得多呢?
F5运行,唉,怎么那么多错误?仔细一看都是gdiplus这些不是我们编写的文件的错误?这是怎么回事?因为GDI+需要用到COM组件的一些东西,所以在使用GDI+是需要包含COM,因为COM不需要我们修改,所以为加快编译过程,我们把COM包含到StdAfx.h文件中去,
所以在StdAfx.h中添加了
#include<comdef.h>
并且选择项目属性,链接器输入项的附加依赖项加入 msimg32.lib对COM的支持。保存,F5终于运行出来了。
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
//other codes
//Start/GDI绘制第一个坐标系//
COLORREF clroldText = ::SetTextColor(hdc,RGB(255,0,0));
int bk = ::SetBkMode(hdc,TRANSPARENT);
TextOut(hdc,5,100,_T("7000"),4);
TextOut(hdc,5,130,_T("6000"),4);
TextOut(hdc,5,160,_T("5000"),4);
TextOut(hdc,5,190,_T("4000"),4);
TextOut(hdc,5,220,_T("5000"),4);
TextOut(hdc,5,250,_T("2000"),4);
TextOut(hdc,5,280,_T("1000"),4);
TextOut(hdc,106,323,_T("1月"),2);
TextOut(hdc,182,323,_T("2月"),2);
TextOut(hdc,250,323,_T("3月"),2);
//TextOut(hdc,320,323,_T("4月"),2);
TextOut(hdc,5+380,100,_T("7000"),4);
TextOut(hdc,5+380,130,_T("6000"),4);
TextOut(hdc,5+380,160,_T("5000"),4);
TextOut(hdc,5+380,190,_T("4000"),4);
TextOut(hdc,5+380,220,_T("5000"),4);
TextOut(hdc,5+380,250,_T("2000"),4);
TextOut(hdc,5+380,280,_T("1000"),4);
TextOut(hdc,106+380,323,_T("1月"),2);
TextOut(hdc,182+380,323,_T("2月"),2);
TextOut(hdc,250+380,323,_T("3月"),2);
TextOut(hdc,700,323,_T("4月"),2);
TextOut(hdc,125+20,665,_T("访问群体"),4);
TextOut(hdc,525+20,665,_T("学习等级"),4);
::SetTextColor(hdc,clroldText);
::SetBkMode(hdc,bk);
//画左侧的网格线
MoveToEx(hdc,54,107,NULL);
LineTo(hdc,346,107);
MoveToEx(hdc,54,137,NULL);
LineTo(hdc,346,137);
MoveToEx(hdc,54,167,NULL);
LineTo(hdc,346,167);
MoveToEx(hdc,54,197,NULL);
LineTo(hdc,346,197);
MoveToEx(hdc,54,227,NULL);
LineTo(hdc,346,227);
MoveToEx(hdc,54,257,NULL);
LineTo(hdc,346,257);
MoveToEx(hdc,54,287,NULL);
LineTo(hdc,346,287);
MoveToEx(hdc,54,315,NULL);
LineTo(hdc,346,315);
//画左边的竖线
MoveToEx(hdc,54,73,NULL);
LineTo(hdc,54,315);
MoveToEx(hdc,123,73,NULL);
LineTo(hdc,123,315);
MoveToEx(hdc,192,73,NULL);
LineTo(hdc,192,315);
MoveToEx(hdc,261,73,NULL);
LineTo(hdc,261,315);
//折线
HPEN hPen = CreatePen(PS_SOLID,1,RGB(42,187,203));
HPEN hPenOld = (HPEN)SelectObject(hdc,hPen);
MoveToEx(hdc,78,314,NULL);
LineTo(hdc,150,170);
LineTo(hdc,182,239);
LineTo(hdc,250,140);
SelectObject(hdc,hPenOld);
DeleteObject(hPen);
//Start/GDI绘制第一个坐标系///
//Start/GDI+绘制第二个坐标系//
Graphics graphics(hdc);
Pen pen(Color(255,0,0,0),1.0f);
//横线
graphics.DrawLine(&pen,54+380,107,396+380,107);
graphics.DrawLine(&pen,54+380,137,396+380,137);
graphics.DrawLine(&pen,54+380,167,396+380,167);
graphics.DrawLine(&pen,54+380,197,396+380,197);
graphics.DrawLine(&pen,54+380,227,396+380,227);
graphics.DrawLine(&pen,54+380,257,396+380,257);
graphics.DrawLine(&pen,54+380,287,396+380,287);
graphics.DrawLine(&pen,54+380,315,396+380,315);
//竖线
graphics.DrawLine(&pen,54+380,73,54+380,315);
graphics.DrawLine(&pen,123+380,73,123+380,315);
graphics.DrawLine(&pen,192+380,73,192+380,315);
graphics.DrawLine(&pen,261+380,73,261+380,315);
graphics.DrawLine(&pen,330+380,73,330+380,315);
//End/GDI+绘制第二个坐标系//
EndPaint(hWnd, &ps);
break;
}