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;
class
functionNewInstance: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:
string
readGetGdipErrorString;
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;
(略)
class
Color
{
private
:
union
{
ARGBFARGB;
struct
{
BYTEFBlue;
BYTEFGreen;
BYTEFRed;
BYTEFAlpha;
};
};
static
TIdentMapEntryKnownColors[];
void
MakeARGB(BYTEa,BYTEr,BYTEg,BYTEb);
void
MakeARGB(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
);
static
ColorFromTColor(BYTEalpha,Graphics::TColorcolor);
static
ColorFromTColor(Graphics::TColorcolor);
static
ColorFromArgb(ARGBargb);
static
ColorFromArgb(BYTEalpha,ARGBargb);
static
ColorFromArgb(BYTEr,BYTEg,BYTEb);
static
ColorFromArgb(BYTEa,BYTEr,BYTEg,BYTEb);
static
ColorFromName(AnsiStringName,BYTEAlpha
=
255
);
static
ColorFromCOLORREF(BYTEalpha,COLORREFrgb);
static
ColorFromCOLORREF(COLORREFrgb);
bool
IsEmpty();
Color
&
operator
=
(Colorc);
Color
&
operator
=
(ARGBc);
bool
operator
==
(Color
&
c);
bool
operator
!=
(Color
&
c);
static
ARGBStringToARGB(AnsiStringName,BYTEAlpha
=
255
);
static
AnsiStringARGBToString(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(
const
S:
string
;Alpha:BYTE
=
255
):TARGB;
procedureGetARGBValues(Proc:TGetStrProc);
functionARGBToIdent(Argb:Longint;varIdent:
string
):Boolean;
functionIdentToARGB(
const
Ident:
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(
const
s:
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
]of
string
=
(
'
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
)
=
$
808080
thenbc:
=
kcBlack
else
bc:
=
kcAliceBlue;
//
以下使用内建的Pens和Brushs作图,也可分别使用TGpPen和TGpBrush建立
g.DrawLine(Pens.Brown,
120
,
30
,
659
,
30
);
g.FillRectangle(Brushs[bc],
120
,
38
,
540
,
200
);
//
显示纵标题
for
i:
=
0
to
4
do
g.DrawString(QualityStr[i],font,Brushs.Black,
4.0
,i
*
40
+
48
);
//
显示横标题
for
i:
=
0
to
3
do
g.DrawString(
'
Alpha:
'
+
IntToStr(Alphas[i]),font,Brushs.Black,
130.0
+
i
*
140
,
8
);
g.DrawString(
'
选择显示颜色
'
,font,Brushs.Black,
4.0
,
260.0
);
//
根据所选颜色和Alpha,用不同的合成品质画色块
for
i:
=
0
to
3
do
begin
for
j:
=
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(
const
s:
string
);
begin
CbColor.Items.Add(s);
end;
end.
C++ Builder代码:
//
---------------------------------------------------------------------------
#ifndefmainH
#define
mainH
//
---------------------------------------------------------------------------
#include
<
Classes.hpp
>
#include
<
Controls.hpp
>
#include
<
StdCtrls.hpp
>
#include
<
Forms.hpp
>
#include
<
Buttons.hpp
>
//
---------------------------------------------------------------------------
class
TMainForm:
public
TForm
{
__published:
//
IDE-managedComponents
TBitBtn
*
BitBtn1;
TComboBox
*
CbColor;
void
__fastcallCbColorDrawItem(TWinControl
*
Control,
int
Index,TRect
&
Rect,
TOwnerDrawStateState);
void
__fastcallCbColorChange(TObject
*
Sender);
void
__fastcallFormPaint(TObject
*
Sender);
private
:
//
Userdeclarations
void
__fastcallGetKnownColorStr(
const
Strings);
public
:
//
Userdeclarations
__fastcallTMainForm(TComponent
*
Owner);
};
//
---------------------------------------------------------------------------
extern
PACKAGETMainForm
*
MainForm;
//
---------------------------------------------------------------------------
#endif
//
---------------------------------------------------------------------------
#include
<
vcl.h
>
#pragma
hdrstop
#include
"
main.h
"
#include
"
Gdiplus.hpp
"
//
---------------------------------------------------------------------------
#pragma
package(smart_init)
#pragma
resource"*.dfm"
TMainForm
*
MainForm;
//
---------------------------------------------------------------------------
__fastcallTMainForm::TMainForm(TComponent
*
Owner)
:TForm(Owner)
{
GetARGBValues(GetKnownColorStr);
CbColor
->
ItemIndex
=
0
;
}
//
---------------------------------------------------------------------------
void
__fastcallTMainForm::GetKnownColorStr(
const
Strings)
{
CbColor
->
Items
->
Add(s);
}
//
---------------------------------------------------------------------------
void
__fastcallTMainForm::CbColorDrawItem(TWinControl
*
Control,
int
Index,
TRect
&
Rect,TOwnerDrawStateState)
{
TGpGraphics
*
g
=
new
TGpGraphics(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)
{
const
static
StringQualityStr[
5
]
=
{
"
Default
"
,
"
HighSpeed
"
,
"
HighQuality
"
,
"
GammaCorrected
"
,
"
AssumeLinear
"
};
const
static
ByteAlphas[
4
]
=
{
255
,
128
,
64
,
32
};
//
TGpGraphics*g=newTGpGraphics(Handle,false);
TGpGraphics
*
g
=
new
TGpGraphics(Canvas
->
Handle);
//
建立与本窗口字体关联的Gdiplus字体对象,以下3句都可建立,
//
但是第三句显示有点不一样,可能没包括字符集的信息
TGpFont
*
font
=
new
TGpFont(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
(
int
i
=
0
;i
<
5
;i
++
)
g
->
DrawString(QualityStr[i],font,Brushs.Black,
4.0
,i
*
40
+
48
);
for
(
int
i
=
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
(
int
i
=
0
;i
<
4
;i
++
)
{
for
(
int
j
=
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;
}
}
//
---------------------------------------------------------------------------
运行结果:
通过这个例子,可以进一步了解前面介绍的颜色转换函数的应用、Pens和Brushs的应用;同时也增加对GDI+颜色类型TARGB不同于TColor的感性认识,即对Alpha的了解以及不同的Alphi值在不同的合成品质下的差异;还可掌握TCanvas与GDI+混合使用自绘TComboBox选项的技巧。