揭密报表监听器的秘密,自定义报表模块成为可能

加菲猫的VFP 2022-03-19 12:07:37

揭密报表监听器的秘密,自定义报表模块成为可能

译者:Fbilo

现在你知道一个ReportListener是什么样的了,你可以建立拥有你所需要行为特性的不同的子类。不过,在你动手做之前,先来看一下如何把它们(你的自定义子类)的事情告诉ReportOutput.APP。

象ReportBuilder.APP一样(参见第六章“在设计时扩展报表系统”以了解关于ReportBuilder.App的详情),ReportOutput.App使用一个注册用的表来跟踪记录它所了解的listener。尽管这个表被包含编译进了ReportOutput.APP里面,你可以使用 DO (_ReportOutput) WITH -100 来为名为 OutputConfig.DBF的这个文件建立一个拷贝。如果ReportOutput.APP在当前目录或者VFP主目录下发现有这么一个名字的表,它就会在运行一个报表的时候使用这个表作为它查找listener的来源。表10展示了这个表的结构。

表10、被ReportOutput.App用作listener注册表的表结构

字段名称类型说明
OBJTYPEI若这是1条listener记录则为100也可以使用其它类型的记录,参见VFP文档以了解详情
OBJCODEI任何有效的listener类型
OBJNAMEV(60)要建立实例的类
OBJVALUEV(60)OBJNAME中指定的类所在类库
OBJINFOM包含这个类库的应用程序

你可以不受内建listener类型(从0到5)的限制;你可以向一条你添加到这个注册表中的记录的OBJCODE字段中添加你自定义的值,然后在一个REPORT或者LABEL命令的OBJECT TYPE子句中使用这个值。

注意ReportOutput只会选择它在注册表中找到的第一条OBJTYPE=100同时OBJCODE等于要找的listener类型的记录。因此,你需要删除或者反注册(将OBJCODE设置为另一个值,比如给它再加大100之类的)其它同一类型的listener记录。此外,还要注意的是这个注册表中还包含着少量的OBJTYPE被设置为非100的一些记录。在ReportOutput.App中内建的listener——尤其是XMLListener——会因为自己的需要而使用这些记录。

你并不是一定要注册一个listener再去使用它,可以简单的手动建立它的实例,然后把对它的一个对象引用传递给REPORT命令的OBJECT子句。这个机制要做的工作稍微多了一点,但它给了你更好的控制,又不要求有一个ReportOutput.APP注册表的拷贝,而且还让你可以做一些象把几个报表链接起来之类的事情。这是我们推荐的、也是我们将在本章的余下部分中使用的机制。

在FFC中的工具

VFP主目录下的FFC(FoxPro Foundation Classes,FoxPro基础类库)子目录中包含着少量几个有助于报表问题的类库。

_ReportListener

_ReportListener.VCX中包含着一些ReportListener的子类,跟基类相比它们有着更多的功能。其中最有用的一个就是_ReportListener(ReportOutput.APP中也包含着_ReportListener.VCX 文件)。
_ReportListener最重要的功能之一,是对后续者(Successors)的支持。当运行一个报表的时候,你可能会想要超过一个report listener。例如,你想要在预览一个报表的同时,还要将这个报表输出到HTML,这时就必须涉及到超过一个report listener了。在这一章的后面部分中你可以看到,listener可以被用于象动态格式和或者旋转文本之类的任务,而建立一些小的、单一功能的listener的想法要比建立一个集成的、什么都做的listener来得要好。一个需要多种行为的报表应该需要多个listener。

_ReportListener通过提供一个“可以包含对另一个listener的对象引用”的Successor属性来允许链接多个listener。为了对这个机制提供支持,大多数事件会在有这么一个后继者对象(Successor object)的情况下调用该对象上的同名方法,使用的代码与以下类似:

If vartype(This.Successor) = ‘OThis.Successor.ThisMethodName()
Endif vartype(This.Successor) = ‘O

例如,假设ListenerA和ListenerB各自都要执行一些任务并且都是_ReportListener的子类,而你想要为一个特定的报表用上这两个listener。这里是如何将它们链接起来的办法:

loListener = createobject(‘ListenerA’)
loListener.Successor = createobject(‘ListenerB’)
report form MyReport object loListener

报表引擎只会跟指定在 REPORT 或者 LABLE命令中的那个Listener进行通讯;这是个“领头的”listener。不过,当报表引擎触发报表事件的时候,领头的listener会调用它的后继者的相应的方法,而该后继者则会调用它自己的后继者(如果有的话)的相应方法,如此等等一直贯穿整个链条。这种类型的结构以“责任链”而闻名,因为在这个链条中的任何listener都能够决定是自己采取行动呢、还是把消息传递给在链条中的下一个环节。

由于报表引擎会自动设置那个领头的listener的属性,比如FRXDataSession和CurrentDataSession,所以_ReportListener只会在需要的时候设置其余后继者的这些属性。许多别的方法都会调用SetSuccessorDynamicProperties方法负责设置那些经常变动的属性们:OutputPageCount、PageNo、以及PageTotal。别的一些属性会在需要的时候被设置;

例如,BeforeReport设置会把后继者的FRXDataSession、CurrentDataSession、CurrentPass、TwoPassProcess、CommandClauses属性设置为这个类的值。

_ReportListener另一个有趣的能力是链接报表。AddReport方法把一个报表添加到自定义的ReportFileNames集合。给这个方法传递一个报表的名称、可选的需要用到的报表子句(比如RANGE子句)、以及对另一个listener对象的一个对象引用。RemoveReports方法会从这个集合中删除所有报表。RunReports运行这些报表;把一个.T.传递给它作为第一个参数以指定在这些报表运行完之后将它们从该集合中删去,再传递一个.T.给它作为第二个参数以忽略在AddReport指定的任何listener。
下面取自TestChainedReports.PRG的代码将会把TestDynamicFormatting和TestRotate这两个报表当作是一个报表来运行:

USE _samples + 'Northwind\orders'
loListener = NEWOBJECT('_ReportListener',;
    HOME() + 'ffc\_ReportListener.vcx')
loListener.OutputType = 1
loListener.AddReport('TestDynamicFormatting.frx', 'next 20 nopageeject')
loListener.AddReport('TestRotate.frx', 'next 20')
loListener.RunReports()

大量已有的工具方法使得你可以轻松的使用listener。例如,SetFRXDataSession可以切换到FRX游标所在的数据工作期,SetCurrentDataSession可以切换到报表数据所在的数据工作期,而ResetDataSession则会将数据工作期恢复到report listener运行时所在的那个数据工作期。

_ReportListener有几个自定义属性。DrivingAlias包含着报表的主游标的名称。ReportUsesPrivateDataSession如果为.T.,则象该属性的名称一样,报表会使用私有数据工作期。如果一个报表正在运行中,则IsRunning为.T.。如果一个多报表的集合正在被运行,则IsRunningReports为.T.。如果当前报表不是领头的报表,则IsSuccessor为.T.。SharedGDIPlusGraphics、SharedPageHeight、和SharedPageWidth包含着GDIPlusGraphics属性的值以及GetPageHeight和GetPageWidth方法的返回值,这样这些值就可以被用于后继者之中了。

UpdateListener

除了_ReportListener以外,_ReportListener.VCX还包含一个UpdateListener类,它是_ReportListener的一个子类,用来显示报表运行的反馈信息。它有着几个你可以用来定制反馈的表现的属性。InitStatusText包含着在报表运行之前显示的消息。PrepassStatusText包含着当报表正在进行“预处理”以计算_PAGETOTAL的值时要显示的消息。RunStatusText包含着在报表运行中的时候要显示的消息。ThermFormCaption包含着用于反馈表单的标题。ThermCaption中包含着一个表达式,该表达式的计算结果文本将被显示在进度条中间。由于这是个基于_ReportListener的类,所以UpdateListener可以被跟其它listener链接在一起。
这里是一个取自TestUpdateListener.PRG的示例,它演示了如何使用这个listener:

USE _samples + 'Northwind\orders'
loListener = NEWOBJECT('UpdateListener',;
    HOME() + 'ffc\_ReportListener.vcx')
WITH loListener
    .InitStatusText   = 'Preparing report...'
    .RunStatusText    = 'Running...'
    .ThermFormCaption = 'Report Progress'
ENDWITH
loListener.ListenerType = 1

REPORT FORM TestDynamicFormatting.FRX PREVIEW OBJECT loListener
lnRun =;
    loListener.ReportStopRunDateTime - loListener.ReportStartRunDateTime
WAIT WINDOW  'The report took ' + TRANSFORM(lnRun) + ' seconds to run'

_GDIPlus.VCX

前面在讨论Render方法的时候(在本章“对象事件”部分)曾提到,一个进行自定义绘制的listener几乎肯定会用到GDI+功能。GDI+是上百个执行各种图形操作和输出的WINDOWS API函数的一个集合。关于GDI+的详情请见:
http://msdn.microsoft.com/library/en-us/gdicpp/GDIPlus/GDIPlusReference/FlatGraphics.asp

为了让GDI+功能更容易使用,经过仔细考虑后,微软在FFC目录中包含了由Walter Nicholls编写的_GDIPlus.VCX。_GDIPlus由一些封装了GDI+函数的类组成,既容易使用又具备了面对对象的特性。在VFP帮助文件中的“GDI Plus API Wrapper Foundation Classes (GDI+ API 封装基础类)”主题列出了这些类,并提供了关于这些类的一点背景资料。有趣的是,它推荐你去阅读.NET FRAMEWORK中相似的那些类的文档,因为_GDIPlus类们是仿造它们的.NET对应品来建模的。

最常用到的类是GPGraphics。它提供了一些在GDI+画布上绘图的方法、以及其它一些工具功能。它要求一个可以用来对之工作的GDI+句柄,因此在调用其它方法之前,需要先给它的SetHandle方法传递GDIPlusGraphics属性(或者如果你使用的是_ReportListener的一个子类,则请传递给它SharedGDIPlusGraphics属性)的值。然后你就可以调用象DrawArc、DrawCurve、DrawLine、以及DrawPie这样的方法来在报表页上绘制图形,或者调用RotateTransform、ScaleTransform、以及TranslateTransform来改变绘图发生的途径。

部分方法要求有对另一种GDI+对象的一个引用,比如一个Pen、brush、font、或者color对象。在_GDIPlus中的其它一些类,如GPPen、GPSolidBrush、GPHatchBrush、GPFont、GPColor可以用来提供这些对象。这些类的用法相当简单:建立一个类的实例,以一些期望的属性(比如颜色)来调用它的Create方法以对它进行初始化,然后把它传递给一个GPGraphics方法。

本章有一对使用_GDIPlus类来执行自定义绘制任务的示例。参见在“指示句柄”部分对SFRotateDirective类的讨论,那里有一个旋转文本的示例;以及“自定义绘制”部分,那里有一个绘制列图表的示例。

...全文
155 回复 打赏 收藏 举报
写回复
回复
切换为时间正序
请发表友善的回复…
发表回复
相关推荐
发帖
VFP

2545

社区成员

VFP,是Microsoft公司推出的数据库开发软件,用它来开发数据库,既简单又方便。
社区管理员
  • VFP社区
加入社区
帖子事件
创建了帖子
2022-03-19 12:07
社区公告
暂无公告