GDI+ for VCL基础 -- GDI+ 与 VCL

陆续写了十几篇关于《GDI+Delphi程序的应用》的文章后,应几个小友来信要求,将我所使用的GDI+for VCL,包括DelphiC++Builder版发布在了****的资源下载区。

GDI+ C语言版本同时GDI+ for VCL版本2010.7.10修改版下载地址:http://download.****.net/source/2737743

其中的Delphi版与目前网上流通的版本不完全兼容;而C++Builder本来自带有C++版的Gdiplus,但由于与VCL有些冲突,使用起来较麻烦,所以本人参照Delphi版完全重新写了一个供C++Builder使用的VCL版(开源的),本BLOG中的有关GDI+的Delphi例子很容易就移植到C++Builder。

下面就GDI+ for VCL的一些特点作简单介绍:

GDI+是伴随Windows XP系统出现的增强性图形设备接口子系统,除了一整套API外,还提供了几十个C++类和大量的数据类型,同传统的Win32 GDI相比,GDI+不仅优化和扩展了GDI,而且使得使用C/C++开发Windows程序图形界面更容易操作。但是,对于Delphi和C++Builder所共用的VCL来说,由于VCL通过TCanvas、TBitmap和TImage等及其相关的类,把传统的GDI封装的几乎无可挑剔,类似C++的GDI+类,在易操作上就没什么优势可言了。但是,要想把GDI+封装成完全的VCL风格也存在几个问题:
1、正是由于VCL对GDI良好的封装,VCL所有与界面和图形有关的类对GDI存在严重的依赖性,即使GDI+封装成完全的VCL风格,也无法“插足”已有的界面和图形类,充其量只能起到“敲敲边鼓”的辅助作用,所以把GDI+封装成完全的VCL风格意义不大,除非GDI+为主,完全重构原有的界面和图形类;
2、现有大量的C++、.NET有关GDI+的代码移植到Delphi(C++Builser)时,结构修改量太大,而且对于已经熟悉C++或者.NET GDI+的人来说,重新掌握新的VCL风格无疑是痛苦的;
3、GDI+的坐标计量类型有整数和实数2套,如果按VCL风格写,势必要舍弃一套,保留整数型当然是最好的,但这2套类型又都不那么完整,整数型更是个半吊子系统。
鉴于以上原因,无论是目前网上流通的GDI+ for Delphi版本,还是本人改写的GDI+ for VCL,都基本采用了原C++类风格,甚至于.net的GDI+版本也与.net其它类风格不一样,更多的保留了原C++风格(就我个人的看法,不知什么原因,GDI+ for C++版本无论是架构设计,还是代码水平,其实都很差,同C++的STL没法比)。
当然,同网上流通的GDI+ for Delphi版本比,我改写的GDI+ for VCL版本并没有完全照搬原C++代码,兼顾了部分VCL和.NET风格,比如加入了VCL异常、布尔类型和绝大部分枚举和集合类型都采用了VCL风格、增加了.NET风格的Pens和Brushs等全局变量(C++Builder)或者全局函数(Delphi)。
GDI+ for VCL的所有类(不包括TPens和TBrushs)都派生于TGdiplusBase:
TCloneAPI=function(Native:GpNative;varclone:GpNative):TStatus;stdcall;

TGdiplusBase
=class(TObject)
private
FNative:GpNative;
protected
constructorCreateClone(SrcNative:GpNative;clonefunc:TCloneAPI
=nil);
propertyNative:GpNativereadFNativewriteFNative;
public
constructorCreate;
classfunctionNewInstance:TObject;override;
procedureFreeInstance;
override;
end;

原C++的GdiplusBase只是重载了new和delete操作符,分别以GDI+的GdipAlloc和GdipFree替换了原系统默认的内存分配和释放方法,而TGdiplusBase也相应的重载了TObject的NewInstance方法和FreeInstance方法(其实在不重载也能正常运行);

在TGdiplusBase中有个保护的GpNative(指针)类型的成员Native(Delphi中说明为属性,C++builder直接说明为数据成员),供所用派生类使用(原C++类将这个成员分散说明在各个类中),这个数据成员就是Gdiplus.dll内部使用的类指针,GDI+类都是通过对应的内部类指针对Dll Exports函数的调用实现的(假如你讨厌GDI+的类,你完全可以抛开它们而直接使用指针操作原始的Dll Exports函数);

至于TGdiplusBase构造方法CreateClone则是我为了简化派生类的Clone方法所提供的基类保护方法。

GDI+ for VCL定义了一个异常类EGdiplusException:

EGdiplusException=class(Exception)
private
FGdipError:TStatus;
functionGetGdipErrorString:
string;
public
constructorCreateStatus(Status:TStatus);
propertyGdipError:TStatusreadFGdipError;
propertyGdipErrorString:
stringreadGetGdipErrorString;
end;

除各类的析构方法外,其它类方法都使用了异常检查,这使得GDI+ for VCL代码同原C++代码和目前流通的GDI+ for Delphi比,更加方便和健壮,通过使用EGdiplusException.GdipError或者EGdiplusException.GdipErrorString,可以得到GDI+最后一次异常代码或信息。

GDI+ for VCL重新定义了绝大多数数据类型,如将C++风格的常量和枚举类型改为了VCL风格的枚举和集合类型,重构某些数据结构,以提供对VCL数据类型的支持或转换。以Color类为例,改写后的TGpColor:

// Known Color

#defineKnownColorCount141
static const ARGB kcAliceBlue = 0xfff0f8ff;
static const ARGB kcAntiqueWhite = 0xfffaebd7;
(略)

classColor
{
private:

union
{
ARGBFARGB;
struct
{
BYTEFBlue;
BYTEFGreen;
BYTEFRed;
BYTEFAlpha;
};
};
staticTIdentMapEntryKnownColors[];

voidMakeARGB(BYTEa,BYTEr,BYTEg,BYTEb);
voidMakeARGB(BYTEa,Graphics::TColorcolor);
COLORREFGetCOLORREF();
AnsiStringGetKnownName(
void);

public:

Color();
Color(Color
&color);
Color(ARGBargb);
Color(BYTEalpha,ARGBargb);
Color(BYTEr,BYTEg,BYTEb);
Color(BYTEa,BYTEr,BYTEg,BYTEb);
Color(BYTEalpha,Graphics::TColorcolor);
Color(Graphics::TColorcolor);
Color(AnsiStringName,BYTEAlpha
=255);

staticColorFromTColor(BYTEalpha,Graphics::TColorcolor);
staticColorFromTColor(Graphics::TColorcolor);
staticColorFromArgb(ARGBargb);
staticColorFromArgb(BYTEalpha,ARGBargb);
staticColorFromArgb(BYTEr,BYTEg,BYTEb);
staticColorFromArgb(BYTEa,BYTEr,BYTEg,BYTEb);
staticColorFromName(AnsiStringName,BYTEAlpha=255);
staticColorFromCOLORREF(BYTEalpha,COLORREFrgb);
staticColorFromCOLORREF(COLORREFrgb);

boolIsEmpty();
Color
&operator=(Colorc);
Color
&operator=(ARGBc);
booloperator==(Color&c);
booloperator!=(Color&c);

staticARGBStringToARGB(AnsiStringName,BYTEAlpha=255);
staticAnsiStringARGBToString(ARGBargb);

__propertyARGBArgb
={read=FARGB};
__propertyBYTEAlpha
={read=FAlpha};
__propertyBYTEA
={read=FAlpha};
__propertyBYTERed
={read=FRed};
__propertyBYTER
={read=FRed};
__propertyBYTEGreen
={read=FGreen};
__propertyBYTEG
={read=FGreen};
__propertyBYTEBlue
={read=FBlue};
__propertyBYTEB
={read=FBlue};
__propertyCOLORREFRgb
={read=GetCOLORREF};
__propertyAnsiStringName
={read=GetKnownName};

};

typedefColorTGpColor,
*PGpColor;
typedefARGBTARGB,
*PARGB;

不仅提供了对VCL的TColor类型的支持(定义为TGpColor类型的参数可直接传递TColor类型),也提供了对GDI+标准颜色的支持与转换(按标准颜色名称得到标准颜色或者按标准颜色取得名称)。在Delphi中,对应TGpColor的地方一律采用TARGB类型,同时提供了与TGpColor函数成员类似的转换方法:

functionARGBToString(Argb:TARGB):string;
functionStringToARGB(
constS:string;Alpha:BYTE=255):TARGB;
procedureGetARGBValues(Proc:TGetStrProc);
functionARGBToIdent(Argb:Longint;varIdent:
string):Boolean;
functionIdentToARGB(
constIdent:string;varArgb:Longint):Boolean;

functionARGB(r,g,b:BYTE):TARGB;overload;
functionARGB(a,r,g,b:BYTE):TARGB;overload;
functionARGB(a:Byte;Argb:TARGB):TARGB;overload;

functionARGBToCOLORREF(Argb:TARGB):Longint;
functionARGBToColor(Argb:TARGB):Graphics.TColor;
functionARGBFromCOLORREF(Rgb:Longint):TARGB;overload;
functionARGBFromCOLORREF(Alpha:Byte;Rgb:Longint):TARGB;overload;
functionARGBFromTColor(Color:Graphics.TColor):TARGB;overload;
functionARGBFromTColor(Alpha:Byte;Color:Graphics.TColor):TARGB;overload;

GDI+ for VCL还增加了.NET风格的Pens和Brushs等全局变量(C++Builder)或者全局函数(Delphi),不仅提供了141种标准颜色的画笔和画刷,也可使用自定义颜色调用Pens和Brushs的缺省数组(Delphi)或者重载的操作符(C++Builder)形成新的画笔和画刷,大大简化了一般的GDI+编程代码,C++Builder的定义为:

static TGpPens Pens;
static TGpBrushs Brushs;

而Delphi则定义为:

function Pens: TPens;
function Brushs: TBrushs;

你可能注意到上面的类型说明中Delphi和C++Builder类的名称不一样,前者为TGpPens和TGpBrushs,而后者直接写为TPens和TBrushs,这是本人写代码时的一点疏忽,不过对写代码没任何影响。

有关GDI+与VCL的话题就说到这里,下面用Delphi和C++Builder以一个简单相同的例子程序作为本文的结尾。

Delphi代码:

unitmain;

interface

uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms,
Dialogs,StdCtrls,Buttons,ExtCtrls;

type
TMainForm
=class(TForm)
BitBtn1:TBitBtn;
CbColor:TComboBox;
procedureFormPaint(Sender:TObject);
procedureCbColorDrawItem(Control:TWinControl;Index:Integer;
Rect:TRect;State:TOwnerDrawState);
procedureFormCreate(Sender:TObject);
procedureCbColorChange(Sender:TObject);
private
{Privatedeclarations}
procedureGetKnownColorStr(
consts:string);
public
{Publicdeclarations}
end;

var
MainForm:TMainForm;

implementation

usesGdiplus;

{$R
*.dfm}

procedureTMainForm.CbColorChange(Sender:TObject);
begin
Invalidate;
end;

procedureTMainForm.CbColorDrawItem(Control:TWinControl;Index:Integer;
Rect:TRect;State:TOwnerDrawState);
var
g:TGpGraphics;
r:TGpRect;
begin
g:
=TGpGraphics.Create(CbColor.Canvas.Handle);
try
CbColor.Canvas.FillRect(Windows.TRect(Rect));
r:
=GpRect(Rect.Left,Rect.Top,CbColor.ItemHeight,CbColor.ItemHeight-4);
OffSet(r,
2,2);
g.FillRectangle(Brushs[StringToARGB(CbColor.Items[Index])],r);
g.DrawRectangle(Pens.Black,r);
CbColor.Canvas.TextOut(r.X
+r.Width+5,r.Y,CbColor.Items[Index]);
finally
g.Free;
end;
end;

procedureTMainForm.FormCreate(Sender:TObject);
begin
GetARGBValues(GetKnownColorStr);
CbColor.ItemIndex:
=0;
end;

procedureTMainForm.FormPaint(Sender:TObject);
const
QualityStr:array[
0..4]ofstring=
(
'Default','HighSpeed','HighQuality','GammaCorrected','AssumeLinear');
Alphas:array[
0..3]ofByte=(255,128,64,32);
var
g:TGpGraphics;
font:TGpFont;
kc,bc:TARGB;
i,j:Integer;
begin
//建立与窗口关联的Graphics对象,使用Handle建立在D7中效果很好,可2007不停闪烁
//g:=TGpGraphics.Create(Handle,False);
g:=TGpGraphics.Create(Canvas.Handle);
//建立与本窗口字体关联的Gdiplus字体对象,以下3句都可建立,
//但是第三句显示有点不一样,可能没包括字符集的信息
font:=TGpFont.Create(Canvas.Handle);
//font:=TGpFont.Create(Canvas.Handle,Self.Font.Handle);
//font:=TGpFont.Create(Self.Font.Name,Self.Font.Size,Self.Font.Style);
kc:=StringToARGB(CbColor.Items[CbColor.ItemIndex]);
if(kcand$808080)=$808080thenbc:=kcBlack
elsebc:=kcAliceBlue;
//以下使用内建的Pens和Brushs作图,也可分别使用TGpPen和TGpBrush建立
g.DrawLine(Pens.Brown,120,30,659,30);
g.FillRectangle(Brushs[bc],
120,38,540,200);
//显示纵标题
fori:=0to4do
g.DrawString(QualityStr[i],font,Brushs.Black,
4.0,i*40+48);
//显示横标题
fori:=0to3do
g.DrawString(
'Alpha:'+IntToStr(Alphas[i]),font,Brushs.Black,130.0+i*140,8);
g.DrawString(
'选择显示颜色',font,Brushs.Black,4.0,260.0);
//根据所选颜色和Alpha,用不同的合成品质画色块
fori:=0to3do
begin
forj:=Integer(Low(TCompositingQuality))toInteger(High(TCompositingQuality))do
begin
g.CompositingQuality:
=TCompositingQuality(j);
g.DrawLine(Pens[ARGB(Alphas[i],kc),
20],
130+i*140,j*40+58,230+i*140,j*40+58);
end;
end;
font.Free;
g.Free;
end;

procedureTMainForm.GetKnownColorStr(
consts:string);
begin
CbColor.Items.Add(s);
end;

end.

C++ Builder代码:

//---------------------------------------------------------------------------

#ifndefmainH
#definemainH
//---------------------------------------------------------------------------
#include<Classes.hpp>
#include
<Controls.hpp>
#include
<StdCtrls.hpp>
#include
<Forms.hpp>
#include
<Buttons.hpp>
//---------------------------------------------------------------------------
classTMainForm:publicTForm
{
__published:
//IDE-managedComponents
TBitBtn*BitBtn1;
TComboBox
*CbColor;
void__fastcallCbColorDrawItem(TWinControl*Control,intIndex,TRect&Rect,
TOwnerDrawStateState);
void__fastcallCbColorChange(TObject*Sender);
void__fastcallFormPaint(TObject*Sender);
private://Userdeclarations
void__fastcallGetKnownColorStr(constStrings);
public://Userdeclarations
__fastcallTMainForm(TComponent*Owner);
};
//---------------------------------------------------------------------------
externPACKAGETMainForm*MainForm;
//---------------------------------------------------------------------------
#endif


//---------------------------------------------------------------------------

#include
<vcl.h>
#pragmahdrstop

#include
"main.h"
#include
"Gdiplus.hpp"
//---------------------------------------------------------------------------
#pragmapackage(smart_init)
#pragmaresource"*.dfm"
TMainForm
*MainForm;
//---------------------------------------------------------------------------
__fastcallTMainForm::TMainForm(TComponent*Owner)
:TForm(Owner)
{
GetARGBValues(GetKnownColorStr);
CbColor
->ItemIndex=0;
}
//---------------------------------------------------------------------------
void__fastcallTMainForm::GetKnownColorStr(constStrings)
{
CbColor
->Items->Add(s);
}
//---------------------------------------------------------------------------
void__fastcallTMainForm::CbColorDrawItem(TWinControl*Control,intIndex,
TRect
&Rect,TOwnerDrawStateState)
{
TGpGraphics
*g=newTGpGraphics(CbColor->Canvas->Handle);
try
{
CbColor
->Canvas->FillRect(Rect);
TGpRectr(Rect.Left,Rect.Top,CbColor
->ItemHeight,CbColor->ItemHeight-4);
r.Offset(
2,2);
TGpColorc
=TGpColor::StringToARGB(CbColor->Items->Strings[Index]);
TGpBrush
*b=Brushs[c];
g
->FillRectangle(b,r);
g
->DrawRectangle(Pens.Black,r);
CbColor
->Canvas->TextOutA(r.X+r.Width+5,r.Y,CbColor->Items->Strings[Index]);
}
__finally
{
deleteg;
}
}
//---------------------------------------------------------------------------
void__fastcallTMainForm::CbColorChange(TObject*Sender)
{
Invalidate();
}
//---------------------------------------------------------------------------
void__fastcallTMainForm::FormPaint(TObject*Sender)
{
conststaticStringQualityStr[5]=
{
"Default","HighSpeed","HighQuality","GammaCorrected","AssumeLinear"};
conststaticByteAlphas[4]={255,128,64,32};

//TGpGraphics*g=newTGpGraphics(Handle,false);
TGpGraphics*g=newTGpGraphics(Canvas->Handle);
//建立与本窗口字体关联的Gdiplus字体对象,以下3句都可建立,
//但是第三句显示有点不一样,可能没包括字符集的信息
TGpFont*font=newTGpFont(Canvas->Handle);
//TGpFont*font=newTGpFont(Canvas->Handle,Font->Handle);
//TGpFont*font=newTGpFont(Font->Name,Font->Size,Font->Style);
try
{
TARGBkc
=TGpColor::StringToARGB(CbColor->Items->Strings[CbColor->ItemIndex]);
TARGBbc
=(kc&0x808080)==0x808080?kcBlack:kcAliceBlue;
g
->DrawLine(Pens.Brown,120,30,659,30);
g
->FillRectangle(Brushs[bc],120,38,540,200);
for(inti=0;i<5;i++)
g
->DrawString(QualityStr[i],font,Brushs.Black,4.0,i*40+48);
for(inti=0;i<4;i++)
g
->DrawString("Alpha:"+IntToStr(Alphas[i]),
font,Brushs.Black,
130.0+i*140,8.0);
g
->DrawString("选择显示颜色",font,Brushs.Black,4.0,260.0);
for(inti=0;i<4;i++)
{
for(intj=0;j<5;j++)
{
g
->CompositingQuality=(TCompositingQuality)j;

g
->DrawLine(Pens(TGpColor(Alphas[i],kc),20),
130+i*140,j*40+58,230+i*140,j*40+58);
}
}
}
__finally
{
deletefont;
deleteg;
}
}
//---------------------------------------------------------------------------

运行结果:

GDI+ for VCL基础 -- GDI+ 与 VCL

通过这个例子,可以进一步了解前面介绍的颜色转换函数的应用、Pens和Brushs的应用;同时也增加对GDI+颜色类型TARGB不同于TColor的感性认识,即对Alpha的了解以及不同的Alphi值在不同的合成品质下的差异;还可掌握TCanvas与GDI+混合使用自绘TComboBox选项的技巧。

2011.6.26:
TGdiplusBase.CreatClone是一个构造方法 由于疏忽,在一些该方法调用语句中,未使用类类型作限定,导致克隆对象错误,应予以修正,以TGpMatrix.Clone为例:

function TGpMatrix.Clone: TGpMatrix;
begin
Result := CreateClone(Native, @GdipCloneMatrix);
end;
改为
Result := TGpMatrix.CreateClone(Native, @GdipCloneMatrix);

已修改过的主单元文件Gdiplus.pas的下载地址:http://download.****.net/source/3395894