VFP9.0中通过FFC类库使用GDI+


第一部分
原著: Walter Nicholls
翻译: YAUR
一套新的GDI+ FFC图形类将随着vfp9.0最终版一起发布,不过你可以去VFP的官方网站上(http://msdn.com/vfoxpro)的下载说明中先睹为快。这套GDI+类预计在8月上旬或之后不久就可以下载了,它可以在vfp9.0 beta版中使用,下载网址还是上边提到的那个,在这篇文章中,Walter Nicholls 介绍了本系列(共四部分)中的第一部分:直接在vfp表单上绘图。

在 vfp9中最令人期待的和最让人兴奋的新特点之一就是增强的报表引擎,尽管这个报表引擎有众多令人激动的特点,不过,最让我兴奋的是我们可以通过编写用户插件来修改和扩展报表中类目的外观
图1
有两种方法可以做到这些,其中之一是使用ReportListener类,它提供了一种可以钩住(或译关联监控 hook)报表产生和显示过程的方法。另外一个则是直接访问用来显示报表的GDI+图形介面。
就像一份额外的红利一样,vfp9.0中的对ReportListener类的一些支持改进使得在其他地方也可以使用GDI+,做一个详细的计划,你也可以写出一个单独的可以在表单、报表上绘图、甚至将图片存为档、存入磁片的GDI类。
VFP自带了一套类库的集合,称之为"功能类",就是我们常说的FFC,在9.0中包含了一个类库:_GDIPPLUS.VCX,它给你提供了在vfp应用程式中使用GDI+的捷径。
注:我们期望GDI+类库能够在2004年8月份的早些时候或之后不久就能够在微软的网站中提供下载,大家注意微软的vfp主页中的一些公告(http://msdn.com/vfoxpro)
在这个关于GDI+ FFC类库介绍系列的第一部分,我们提供给你们一个旋风般的旅行,并且也为你们示范如何在vfp的表单上绘图,在第二部分,我将为你们展示如何使用一些技巧来扩展你的报表系统:结合GDI+类库使用ReportListener类。
我将做的让它看起来容易至极
看看前面几页,找找"如何做一个圆形图"这一届,这个图片是一个通过这篇文章而完成的一个最终产品,并且我承诺将会使这个过程看上去很容易,到文章的结尾,你就会发现,这一切是的的确确的非常简单。
紧记这篇文章所描述的是基于 vfp9 beta版的(version 09.00.0000.1720). 所以在写这篇文章的时候,vfp9的一些功能并不是一成不变的,它可能和最终正式版由一些不同,然而,你应该可以成功的用vfp9 BETA版来使用这些发布在这里的范例(文章附带的示例)
FFC类库和GDI+ API的关系
我们所指的"GDI+"实际上为程式师提供了好几种形式。在.net世界中,它们有System.Drawing 以及与之相关联的命名空间。C++程式师使用一个几乎差不多的在gdiplus.h中的API定义,然而,通过GDIPLUS.DLL(vfp开发人员也可以利用这些api来进行介面增强)提供的介面实际上是被称之为"GDI+ Flat API"的非可视介面。
为了使GDI+干起活来更加容易,新的VFP9的FFC类库GDIPLUS.VCX提供了一个非常类似于.net系统中 System.Drawing 命名空间的视觉化介面,这就意味着那些最初为.NET和C++程式师所写的书籍、文章和类似于MSDN的参考资料通过使用FFC类库同样可以应用到VFP中,当然一些小的改动该是要做地。
主要的区别在于:
. 所有的类名都被冠以"GP"字母的首码
. 一些属性的名字是不同的(例如用PenWidth来代替Width)
. 用#define 来定义的常量也会加上"GDIPLUS_" 首码
. 它仅仅是GDI+完整功能集合的一个子集,不过扩展起来是容易地
这些不同中的大部分时决定于开发语言的约束,例如,"Point"和"Width"在vfp中有其他的含义, integers和floats的差别和其他开发语言不同,表1展示了_GDIPLUS.VCX的类定义和.net中相同类的对比关系。
表1
FFC 类 功能描述 .NET 类
GpGraphics 绘图介面(画布)和绘图操作 Graphics
GpColor 由红、蓝、绿及alpha组成的颜色 Color
GpPoint 2D平面座标 Point, PointF
GpSize 尺寸大小 (宽度, 高度). Size, SizeF
GpRectangle 位置和尺寸集合 Rectangle, RectangleF
GpPen 绘制直线和曲线 Pen
GpBrush 数字画笔类的抽象基类 Brush
GpSolidBrush 使用纯色填充区域 SolidBrush
GpHatchBrush 使用图案填充区域 HatchBrush
GpFontFamily 描述一种字体的同类字体 (例如, "Arial"). FontFamily
GpFont 用来修是文本的集合,包括大小,字体等 Font
GpStringFormat 文本排版、排列资讯,例如:居中 StringFormat
GpImage 图片类的基类:包括向量图和点阵图 Image
GpBitmap 由2D图元资讯阵列组成的图形 Bitmap

在表单上使用GDI+
当我开始在vfp中使用GDI+的时候,因为GDI+早已在VFP中使用了,所以我期望我能更合理的使用GDI+功能并且让所做得事情变得更好。看起来似乎我是错的,不过好像不是太错,在使用GDI+功能的时候有些环节你还是要仔细一点,而且你的工作也不得不受到一些制约。
我们将使用GDI+的功能来在表单上直接绘制来取代通常使用的控制项,例如图像和容器控制项,方法就是使用表单事件:Paint(),它是在重绘表单背景和所有表单上的物件的时候被触发的。
保持清洁的表单介面
你应该首先确保你想绘制的表单区域没有被VFP中的其他任何操作或物件来使用,作为开始练习的情况,不要在上面放置其他的控制项(按钮、文本框等),因为它们将导致重绘冲突,我喜欢在表单上放一个隐藏的形状控制项,在绘图即将发生的时候将它显示出来,这不仅提醒我保持其他控制项不来骚扰,也使得我在编写其上绘图的位置和尺寸的代码的时候方便了许多。
如果你想在一个页面或页框中绘图的时候,你必须采用一些机制来确保这些绘图代码只能在相应的页面处于活动的情况下才能执行。
最后,不要忘记了”?”语句或者使用了set talk on导致的输出。你可以用将表单属性AllowOutput 设置为.F.的方法来避免输出。它可以防止程式将你的表单作为活动输出视窗,我经常在我的基类表单中设置这个属性,因为这个设置常常不太容易想起来。
隐屏绘图
为了改善性能,VFP通常使用一个称之为”双缓冲”的技术,在这种模式下,VFP不是直接在视窗中绘制控制项,取代它的是绘制成一个隐屏点阵图,然后在需要的时候复制这个图像来刷新视窗,这种机制的速度远比那种在视窗或视窗中的物件在改变大小、位置等的时候就刷新快的多。
不爽的是,我们却不能使用这种隐屏介面模式,因为在视窗被刷新的时候俄我们对表单介面的所有绘图操作会被删除,而且paint()事件仅仅是在隐屏点阵图被重绘的时候才被触发的。
隐屏绘图
围绕这个工作是属于本篇文章的范围之内的,所以现在应该关闭隐屏点阵图的模式来运行附带的示例,或者已经知道这个限制了。你可以使用SYS(602,0)来禁止使用隐屏点阵图模式,使用SYS(602,0)就可以改回来了。这是一个全局设定,所以我不建议你在你的表单的Init()中或其他的类似全局事件中载入这个设置。
GDI+初始化
在任何GDI+功能被使用的时候,GDI+系统必须被“启动”。VFP可以自动做这些,不过仅仅是在需要的时候,如系统自然也不知道你的想法!所以你可以完全自己来载入GDI+,仅仅一句代码:
createobject( "ReportListener" )
你不用担心GDI+的释放,VFP会在应用程式关闭的时候为你做这个的。
设置一个基础表单
在本文的剩余部分,我们将使用同样的基础表单来干活,这是一个modeless, resizable表单,而且上边还有一个居中隐藏的形状控制项(shpPlaceholder),看图2。我将使用新的锚属性(VFP新增的)来确保当表单尺寸改变的时候这个形状控制项尺寸也能变化。表2展示了一些表单的主要属性和方法,同样在附带的下载档gpintro_blank.scx中这些属性已被搞定。

图2

属性方法 值和表述
Width, Height 350, 250
AllowOutput .F.
BorderStyle 3 (Sizable)
Caption "Blank form - GDI+ Intro"
WindowType 0 (Modeless)
Init() * 确保 GDI+ 已经初始化
createobject("ReportListener")
shpPlaceholder.Left, .Top, .Width, .Height 25, 25, 300, 200
shpPlaceholder.Anchor 15 (保持和表单所有边距相同)
shpPlaceholder.Visible .F.
表2
我假定你已经复制了FFC类库到你的专案根目录中的FFC/目录中,如果不想这样,调整一下相应得路径,现在,我们准备开始使用GDI+。
出发:GpGraphics对象
要想视觉化的来做这些事情,你需要GpGraphics物件,它描绘一个视窗介面、一个报表页或者其他任何可能的物件。GpGraphics物件提供了查找(修改)一个绘图介面的尺寸和解析度,还有绘制介面的方法,表3展示了一些GpGraphics物件非常重要的属性和方法。
说明: 绘制各种形状的轮廓
FillRectangle( oPen, x,y, width,height )
FillRectangle( oPen, oRect )
FillRectangles( oPen, aRects[] )
FillEllipse( oPen, x,y, width,height )
FillEllipse( oPen, oRect)
FillClosedCurve( oPen, aPoints[] )
FillPie( oPen, x,y, width,height, start,sweep )
FillPie( oPen, oRect, start, sweep )
FillPolygon( oPen, aPoints[] )
Description: 绘制各种填充图形
DrawImageAt( oImage, x,y )
DrawImageAt( oImage, oPoint )
DrawImageScaled( oImage, x,y,width, height )
DrawImageScaled( oImage, oRect )
DrawImagePortionAt( oImage, oDestPoint, oSrcRect, nSrcUnit )
DrawImagePortionScaled( oImage, oDestRect, oSrcRect, nSrcUnit )
说明: 绘制图形或半帧像.
DrawStringA( cString, oFont, oRect, [oStringFormat], [oBrush] )
DrawStringW( cUnicodeString, oFont, oRect, [oStringFormat], [oBrush] )
说明:绘制文字.
MeasureStringA( cString, oFont, layoutarea, [oStringFormat], [@nChars], [@nLines]
MeasureStringW( cUnicodeString, oFont, layoutarea, [oStringFormat], [@nChars], [@nLines]
说明: 计算文本的大小
ResetTransform()
TranslateTransform( xOffset, yOffset [,matrixorder] ) RotateTransform( nAngle [,matrixorder] )
ScaleTransform( xScale, yScale [,matrixorder] )
说明: 改变图形坐标系
Save( @graphicsstate )
说明: 保存当前图形物件的状态。
Restore( graphicsstate )
说明: 恢复前一次保存的状态。
表3
在某些情况下,GDI+物件已经存在而且我们需要给它一个简单的VFP介面。例如,VFP9的报表系统内部就使用了GDI+。报表中的每一页都被一个图形物件封装了,通过存储于ReportListener的一个属性里的控制码,在这种情况下,就有一个简单的GpGraphics示例,而且俄I你可以通过这个控制码来访问它(更详细的内容在下篇文章中描述)。
在其他情况下,你必须自己创建一个GDI+图形物件,例如:要绘制一个表单,你必须创建一个基于原始表单视窗的控制码(HWnd)的GpGraphics对象。
local oGr as GpGraphics of ffc/_gdiplus.vcx
oGr = newobject('GpGraphics','ffc/_gdiplus.vcx')
oGr.CreateFromHWND( Thisform.HWnd )
GpGraphics物件已经在这个表单中进行了适当的设置,它的座标空间为(0,0)位置来代替左上角。如果你没有一个预先搞好的GpGraphics基类物件,例如你要产生一个点阵图并且保存到档,那你可能要做很多细微的工作。
绘制线条和形状
为了展示第一个示例,我将持续快速的介绍一系列类,我们将从绘制一个类似于图3的椭圆形来开始。

图3
要在视窗介面中绘制一些图形,我们需要一个工具,在这一步中,我们使用GpPen物件,它可以绘制线条(这里是椭圆的轮廓线)。我们也需要设置颜色工具,使用GpColor物件,表4和表5中列出了一些非常重要的GpPen和GpColor物件的属性和方法。
属性/方法名 说明
PenColor 画笔颜色(ARGB 值)
PenWidth 画笔宽度
PenType 画笔类型 (参考 GDIPLUS_PENTYPE_ 常量).
Alignment 画笔对齐方式(参考 GDIPLUS_PENALIGNMENT_ 常量).
DashStyle 使用的虚线类型
Create( color [, width] [, unit] ) 用给定的颜色画图
CreateFromBrush( brush [, width] [, unit] ) 创建一个基于刷子的画笔
Init( color ) 设定初始颜色
Init() 产生一个空的画笔物件 (使用时必须调用Create())
表4:GpPen的属性/方法描述

属性/方法名 说明
Red Red component (0...255).
Green Green component (0...255).
Blue Blue component (0...255).
Alpha Alpha (透明度). 0 =完全透明,
255 =完全不透明
ARGB GDI+ 颜色 (32位整形参数).
FoxRGB Visual FoxPro 颜色值,可以用 RGB()或 GETCOLOR() 函数返回.注意: 这个值不包括alpha资讯, 并且要将Alpha属性值设置为完全不透明(255).
Set( red,green,blue [,alpha] ) 设置颜色 (如果没有制定Alpha,那默认为 100% 不透明)
Init( red,green,blue [,alpha] ) 创建特定的颜色(如果没有制定Alpha,那默认为 100% 不透明)
Init( argb ) 创建单一的GDI+ color
Init() 创建默认颜色,黑色
表5:GpColor的属性/方法描述

以下代码创建了一个深蓝色的GpColor物件
oLineColor = newobject( ;
'GpColor','ffc/_gdiplus.vcx','', 0,0,100 )
以下代码创建了一个基于以上颜色的GpPen物件,而且制定宽度为3图元
oPen = newobject('GpPen', 'ffc/_gdiplus.vcx' )
oPen.Create( m.oLineColor, 3 )

要在表单介面中绘制一个椭圆形,我们使用GpGraphics物件的DrawEllipse方法,以下代码将使用之前创建的画笔物件来绘制这个椭圆,它的位置是由我们之前隐藏的那个占位元形状物件所决定的。
oGr.DrawEllipse( m.oPen ;
, Thisform.shpPlaceholder.Left ;
, Thisform.shpPlaceholder.Top ;
, Thisform.shpPlaceholder.Width ;
, Thisform.shpPlaceholder.Height ;
)
将这些代码放置到表单的Paint()事件中,当你运行以后,你就能看到如图3的增强图形了。

关于矩形
在接下来的这个例子中,我们将再次使用同样的座标,为了节省代码,我们将创建一个GpRectangle物件并且将之作为一个单独得参数来引用,这里还是绘制椭圆的代码碎片,这时我们使用GpRectangle物件来定义椭圆区域的范围。
oBounds = newobject( ;
'GpRectangle','ffc/_gdiplus.vcx','' ;
, Thisform.shpPlaceholder.Left ;
, Thisform.shpPlaceholder.Top ;
, Thisform.shpPlaceholder.Width ;
, Thisform.shpPlaceholder.Height ;
)
oGr.DrawEllipse( m.oPen, m.oBounds )

填充区域:刷子
现在,让我来用量绿色来填充这个椭圆,为了绘制填充区域,我们需要一个新的工具:刷子。GDI+ FFC类库提供了几个刷子,但是用春色来绘制我们还是要用GpSolidBrush物件。

* 创建一个亮绿色的刷子
local oFillColor as GpColor of ffc/_gdiplus.vcx ;
, oBrush as GpSolidBrush of ffc/_gdiplus.vcx
oFillColor = newobject( ;
'GpColor','ffc/_gdiplus.vcx','' ;
, 0,255,0 ) && green
oBrush = newobject( ;
'GpSolidBrush', 'ffc/_gdiplus.vcx', '' ;
, m.oFillColor )

要在荧幕上填充椭圆,我可以使用FillEllipse()方法。
oGr.FillEllipse( m.oBrush, m.oBounds )
要绘制椭圆的轮廓线,我们必须首先调用FillEllipse()方法,然后使用DrawEllipse()。最后你就能看到如图4所示的结果了。

图4


来个 饼形图 怎么样?
现在你已经有了些基础,来看看怎么画一个 饼形图 吧。下面的代码我用了一个包含2列的阵列作为表单属性,每1列都为饼形图的1个切片提供资料,来看一下伪代码:

aSliceData[ nRow, 1 ] = data value
aSliceData[ nRow, 2 ] = fill color for the slice
(in GDI+ ARGB format)
nSliceTotal = total of all the data values

Paint()事件会调用这个阵列并计算适当的角度来画这个饼形图。这里有个新概念:DrawPie()和FillPie()方法,他们就象DrawEllipse 和 FillEllipse 一样,用一个 起始角 和一个 扫描角(切片的一个范围),来描述 切片角度。

常式我这是故意弄得简单些,其实你可以很容易地扩展她,比如显示标签或标记值(或许你要把其中一个切片拖出来)。尽管用远端的SQL查询获取切片资料可能不是个好主意,但你也别在Paint()事件中放太多的代码。

下面的是Paint()的关键代码,完整的源码在附带的 gpintro_piechart.scx 里,图5是执行结果。

图5

* Create the drawing objects
local oLineColor as GpColor of ffc/_gdiplus.vcx ;
, oPen as GpPen of ffc/_gdiplus.vcx ;
, oBrush as GpSolidBrush of ffc/_gdiplus.vcx
oLineColor = newobject( ;
'GpColor','ffc/_gdiplus.vcx','' ;
, 0,0,0 ) && black
oPen = newobject( ;
'GpPen', 'ffc/_gdiplus.vcx','';
, m.oLineColor ) && 1-pixel-wide pen
oBrush = newobject( ;
'GpSolidBrush', 'ffc/_gdiplus.vcx','' )
oBrush.Create() && don't specify colour yet
* Work out from the slice data what the starting
* angles should be
local nSlices
nSlices = alen(This.aSliceData,1)
local aAngles[m.nSlices+1], iSlice
aAngles[1] = 0 && start at 0 degrees (+ve x axis)
for iSlice = 2 TO m.nSlices
aAngles[m.iSlice] = aAngles[m.iSlice-1] ;
+ 360*This.aSliceData[m.iSlice-1,1]/This.nSliceTotal
endfor
aAngles[m.nSlices+1] = 360 && Stop at full circle
* Draw the pie slices
for iSlice = 1 to m.nSlices
oBrush.BrushColor = This.aSliceData[m.iSlice,2]
oGr.FillPie(m.oBrush, m.oBounds ;
, aAngles[m.iSlice] ;
, aAngles[m.iSlice+1] - aAngles[m.iSlice])
oGr.DrawPie(m.oPen, m.oBounds ;
, aAngles[m.iSlice] ;
, aAngles[m.iSlice+1] - aAngles[m.iSlice])
endfor
写上文本
最后一步是在饼形图上写上一些文本。因为饼形图上包含了很多不同的颜色,一般样式的文字可能难以阅读,因此我给她带上阴影,让我们在图上写一个“VFP9 is cool!”。这里有个新工具:GpFont类,详见 表6。
属性/方法名 说明
FontName 字体名称,比如:"Arial."
Style 比如:粗体、斜体(GDIPLUS_FONTSTYLE_ bits的集合)
Size 字大小
Unit 默认是points(1/72英寸)
Create( fontname, size [,style [,unit]] ) 指定字体字形建立GDI+的字体物件
Create( GpFontFamily, size [,style [,unit]] ) 指定字体系列和字形建立GDI+的字体物件
Init( fontname/family, size [,style [,unit]] ) 初始化一个空的GpFont并调用Create()
GetHeight( GpGraphics ) 获取指定的GpGraphics物件的字体行间距
GetHeightGivenDPI( nDPI ) 用指定的解析度(点/英寸)获取字体行间距
表6:GpFont类

在这个饼形图上写字,我们建立一个GpFont物件,设为 Arial,粗体,32Points高:

oFont = newobject('GpFont','ffc/_gdiplus.vcx')
oFont.Create( "Arial" ; && font name
, 32 ; && size in units below
, GDIPLUS_FONTSTYLE_BOLD; && attributes
, GDIPLUS_UNIT_POINT ; && units
)

我们还需要一个Brush物件,Brush前面我介绍过,我们必须指定这些字写在哪儿--让我们把她放到图的中间。我们已经有了一个GpRectangle物件定义了边界,因此最后一步就是告诉GDI+如何在这个矩形中排版。那么,欢迎来到 GpStringFormat类(请看表7)

* Get a basic string format object, then set properties
oStringFormat = newobject( ;
'GpStringFormat','ffc/_gdiplus.vcx')
oStringFormat.Create( )
oStringFormat.Alignment ;
= GDIPLUS_STRINGALIGNMENT_Center
oStringFormat.LineAlignment ;
= GDIPLUS_STRINGALIGNMENT_Center
属性/方法名 说明
Alignment 文本的水准对齐方式
LineAlignment 垂直对齐
FormatFlags 请看 gdiplus.h 里的 GDIPLUS_STRINGFORMATFLAGS_ 常量
Trimming 如何TRIM一个字串
HotkeyPrefix Windows "hotkey" 首码
Create( [flags] [, languageID] ) 建一个新的字串格式物件
GetGenericDefault( [lMakeClone] ) 复制一个默认字串格式或获取一个默认字串格式的控制码
GetGenericTypographic( [lMakeClone] ) 复制一个默认字串格式式样或获取一个默认字串格式式样的控制码
表7:GpStringFormat类的一些属性和方法
这个字串我们要画2次:上面是纯色,偏移几点再来个半透明的阴影。这里又要解释一个新概念:颜色值的“alpha”元件。代码如下:

* Now draw the text with a drop-shadow.
* First, shrink the bounding box by 4 pixels
* and move 4 pixels to the right and down
oBounds.W = oBounds.W - 4
oBounds.H = oBounds.H - 4
oBounds.X = oBounds.X + 4
oBounds.Y = oBounds.Y + 4
* and draw the shadow in a 66% black
oBrush.BrushColor = 0xA8000000
oGr.DrawStringA( This.cOverlayText ;
, oFont, oBounds, oStringFormat, oBrush )
* Now move the bounding box back to its original
* position, and draw the same string in opaque white
oBounds.X = oBounds.X - 4
oBounds.Y = oBounds.Y - 4
oBrush.BrushColor = 0xFFFFFFFF
oGr.DrawStringA( This.cOverlayText ;
, oFont, oBounds, oStringFormat, oBrush )
你现在应该可以看到如 图6 所示。注意:要是你RESIZE这个表单,这文本也会跟着伸缩。你还可以用 GpStringFormat物件 的其他属性控制她的伸缩。

图6

结束语

你要注意写文本的方法叫“DrawStringA”而不是“DrawString”,这是因为这个函数有2个版本。DrawStringA为VFP中8位元的字串而设计,DrawStringW为16位元的Unicode字串而设计(这个W是WIDE的意思)。这同样用于ReportListener,在那里你用Unicode往往比用VFP的字串值更好。

建一个象GpGraphics和GpFont这样的GDI+物件是需要代价的(很占CPU资源)。为了提高性能,你可以缓冲这些Paint()事件,但你必须注意有时这些缓冲会失效,比如,表单RESIZE了,或你画的那个绑定框改变了等等。

性能方面还有些小技巧。如果你习惯用ARGB值,你就完全可以不使用GpColor物件--你使用32位元的颜色值就好了。还有个技巧可以充分提高性能:双缓冲,不过这个技巧在另一篇文章里面。

下个月:图表在报表中的应用
这个系列的下个月部分,我要展示如何把图表移到VFP9的报表里面,而且还有很多有趣的技巧。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值