VFP在运行时扩展报表系统,这是报表转换任意格式的秘决

2022-04-07 19:50:24 浏览数 (2)

译者:Fbilo.其实你只要掌握了VFP9的报表系统,你就可以开发出报表转任意的文件格式。

除了在第六章“在设计时扩展报表系统”中讨论的设计时扩展能力以外,VFP 9 还提供了在报表运行的时候扩展报表系统行为的能力。在这一章中,你将学到的有 VFP 9 的report listener 的概念、它是如何在一个报表正在运行的时候接收事件的、以及除了经典的打印和预览之外你可以如何通过建立自己的 listener 来提供不同类型的输出。

在 VFP 9 中新的报表引擎已经将责任拆分成了几块,报表引擎现在只负责数据处理和对象定位,一个新的对象 report listener 负责处理绘制和输出。Report listener 基于的是 VFP 9 的一个新的基类 ReportListener。

在一个报表的运行过程中,VFP 会触发在一个 report listener 上的那些事件,好像这些事件发生了一样。例如,当一个报表在运行前被 Load 的时候会触发它的 LoadReport 事件。当一个对象被画到报表页上的时候会触发 Render 方法。ReportListener 基类有一些内建的行为,但你可以建立并使用你自己的子类来扩展它。例如,一个 ReportListener 可以动态地格式化一个字段,于是在某些条件下它打印的是红色的文本,而在另一些条件下它打印的是黑色文本。

这一章从讨论 report listener 是如何工作的开始,然后研究 ReportListener 基类的属性、事件和方法(PEMs)。接着,我们讨论 VFP 自带的一些 ReportListener 的子类。这一章剩下部分的焦点将集中在建立 report listener 的一些很酷的用法:你在过去版本的 VFP 中无法做到的特殊效果上,包括不用 ActiveX 控件就能在报表上画图和建立你自己的报表预览。

Report listener 基础

Report listener 以两种途径来生成输出:“一次一页”模式,每次生成一页然后输出该页、再生成下一页然后再输出这一页、如此等等一直到做完报表。这个模式通常用于打印一个报表的时候。在“一次所有页”模式中,Report listener 会绘制所有的页并将它们放在内存的缓存中,然后它根据需要输出这些绘制好了的页,比如当用户在打印预览窗口中单击了 next 按钮。这个模式通常用于预览一个报表。

可以以两种途径使用 Report listener。一种是通过指定 REPORT 命令的 OBJECT 子句。OBJECT 支持两种使用它的方式:指定一个 Report listener 或者指定一个 report type。 要让 VFP 为一个报表使用一个特定的 Report listener,需要先建立这个 listener 类的实例,然后在 REPORT 命令的 OBJECT 子句中指定这个对象的名称。这里是一个示例:

代码语言:javascript复制
loListener = createobject('MyReportListener')
report form MyReport object loListener

如果你不愿意手动建立一个 listener 的实例,你可以通过指定一个 report type 来让 VFP 自动为你做好这件事情:

代码语言:javascript复制
report form MyReport object type 1

这些定义了的对象类型是:0 表示输出到一台打印机,1 为打印预览,2为“一次一页”模式同时不将输出发送到一台打印机,3 为“一次所有页”又不打开预览窗口,4 为 XML 输出,5 为 HTML 输出。你还可以定义你自己的对象类型,本章的“注册 listener”一节中会讨论这方面的内容。

当你以这种途径运行一个报表的时候,指定在新的系统变量 _REPORTOUTPUT 中的应用程序(默认是在 VFP 主目录下的 ReportOutput.APP)被调用来弄清楚要为指定的 type 建立何种 listener 类的实例。ReportOutput.APP 在一个 listener 注册的表中搜索指定的 listener type。如果它找到了期望的类,它会建立这个类的实例,并交给报表引擎一个对这个 listener 对象的引用。ReportOutput 主要是一个对象工厂,但它还包括一些提供 XML 和 HTML 输出的 listener 以及其它一些工具函数。

使用 report listener 的另一种途径是利用新的 SET REPORTBEHAVIOR 90 命令。这个命令启用“支持对象”的报表,这样一来,REPORT 命令的表现将等于当你使用 TO PRINT 子句时指定 OBJECT TYPE 0、或者当你使用 PREVIEW 子句时使用 OBJECT TYPE 1的效果。

ReportListener

本章的下一部分将通过研究 ReportListener 的 PEMs 来理解它的能力。关于 ReportListener 的一件要注意的事情是:它的计量单位(例如,由 GetPageWidth 方法返回的值、或者传递给 Render 方法的 size 参数)是 1/960 英寸。

属性

表1展示了 ReportListener 的属性。

表1、ReportListener 基类的属性

属性

类型

说明

AllowModalMessages

L

如果为 .T. 则允许用一个模式消息窗口显示报表的进程(默认为.F.)

CommandClauses

O

一个基于 Empty 类的对象,带有表示“使用在 REPORT 或者 LABEL 命令中的子句”的属性。该对象的属性参见表2

CurrentDataSession

N

报表数据的数据工作期ID

CurrentPass

N

表示报表的当前pass through。一个带有 _PageTotal 或者设置为 .T. 的 TwoPassProcess 属性的报表需要两次 pass;而其它报表则只需要一次。0表示一个两次 pass 报表中的第一次 pass、或者一次pass报表的唯一一次,而1则表示第二次 pass。

DynamicLineHeight

L

若为.T.(默认)则使用 GDI 的线条间距,这种间距是根据字体的特征而变化的。若为 .F. 则使用旧样式的固定线条间距。

FRXDataSession

N

FRX游标(为让一个 ReportListener 使用而打开的、当前报表引擎正在运行的那个报表的一个只读拷贝)的数据工作期ID

GDIPlusGraphics

N

用于绘制的那个 GDI 图形对象的句柄。只读

ListenerType

N

listener 生成的报表输出类型。默认为 -1,表示不输出。因此你将需要把它改成一个更有意义的值。可用值的列表参见对 OutputPage 方法的讨论。

OutputPageCount

N

已经绘制好的页数。只读

OutputType

N

指定在 REPORT 或者 LABEL 命令的 OBJECT TYPE 子句中的输出类型。

PageNo

N

正在被绘制中的当前页的页码。只读

PreviewContainer

O

对要预览的报表的显示界面的一个对象引用

PrintJobName

C

显示在 Windows 打印队列对话框中的打印任务的名称

QuietMode

L

若为 .T.(默认为.F.)则禁止进程信息。

SendGDIPlusImage

N

设为1或者更大数字(默认为0)来为一个 General 字段发送对一幅图像的一个句柄给 Render 方法。这是一个数值型而不是逻辑型字段,以允许在子类中根据需要以不同的途径处理图像。

TowPassProcess

L

表示是否为报表使用两次 pass。将这个属性设置为 .T. 以强制执行一次预先的 pass,即使报表中没有任何地方用到了 _PageTotal 也一样。

CommandClauses 属性包含着对一个 Empty 对象的引用,该对象带有一些表示“使用在 REPORT 或者 LABEL 命令中的子句”的属性。表2列出了这些属性。

表2、CommandClauses 对象的属性

属性

类型

说明

ASCII

L

当输出到一个文件的时候,若指定了 ASCII 关键词则为.T.

DE_Name

C

报表的数据环境对象的名称。它是在 NAME 子句中指定的名称、或者没有NAME子句则为该报表的名称

Environment

L

若指定了 ENVIRONMENT 关键词则为 .T.

File

C

将要被运行的报表名称

Heading

C

由 HEADING 关键词指定的 heading

InScreen

L

若指定了 SCREEN 关键词则为 .T.

InWindow

C

在 IN WINDOW 关键词指定的窗口名称

IsDesignerLoaded

L

若报表是从报表设计器中被运行的则为 .T.

IsDesignerProtected

L

如果指定了 PROTECTED 关键词则为 .T.

IsReport

L

若这是一个报表则为.T.,若为一个 label 则为 .F.

NoConsole

L

若指定了 NOCONSOLE 关键词则为 .T.

NoDialog

L

若指定了关键词 NODIALOG 则为.T.

NoEject

L

若指定了关键词 NOEJECT 则为.T.

NoPageEject

L

若指定了关键词 NOPAGEEJECT 则为.T.

NoReset

L

指定了关键词 NORESET 则为.T.

NoWait

L

若指定了关键词 NOWAIT 则为.T.

Off

L

若指定了关键词 OFF 则为.T.

OutputTo

N

指定在 TO 子句中的输出类型:0=没有指定 TO 子句,1=打印机,2=文件

PDSetup

L

若指定了关键词 PDSETUP 则为.T.

Plain

L

若指定了关键词 PLAIN 则为.T.

Preview

L

若指定了关键词 PREVIEW 则为.T.

PrintPageCurrent

N

默认为0。不过一个打印预览窗口可以在它调用 listener 的 OnPreviewClose 方法来将这个属性设置为当前被显示的页。Listener 可以使用这个属性来启用在一个打印对话框中的“打印当前页”选项。

PrintRangeFrom

N

默认为1。不过,一个 listener 可以在预览完毕开始打印时将这个属性设置为打印起始页

PrintRangeTo

N

默认为-1。不过,一个 listener 可以在预览完毕开始打印时将这个属性设置为打印终止页

Prompt

L

若指定了关键词 PROMPT 则为.T.

RangeFrom

N

指定在 RANGE 子句中的起始页。若没有指定则为1。

RangeTo

N

指定在 RANGE 子句中的终止页。若没有指定则为-1。

RecordTotal

N

在主游标上将被生成报表的记录总数。

Sample

L

若在 LABEL 命令中指定了 SAMPLE 关键词则为 .T.

StartDataSession

N

REPORT 或者 LABEL 命令开始执行时所在的数据工作期

Summary

L

如果 REPORT 命令指定了 SUMMARY 关键词则为 .T.

ToFile

C

在 TO FILE 子句中指定的文件名

ToFileAdditive

L

当输出到一个文件时,若指定了 ADDITIVE 关键词则为 .T.

Window

C

在 WINDOW 关键词中指定的窗口名称

有一点特别要注意的就是游标们被处理的顺序。在一个报表的运行过程中实际上涉及四个数据工作期。

第一个数据工作期是建立 ReportListener 的实例时所在的数据工作期;从一个 ReportListener 的方法中执行 SET('DATASESSION')将会给你适当的值。

第二个数据工作期是REPORT 或者 LABEL 命令从中开始执行的数据工作期;可以察看 CommandClauses 对象的 StartDataSession 属性来判定这个数据工作期的ID。第三个数据工作期是FRX游标在其中打开的数据工作期。FRXDataSession 属性包含这个游标的数据工作期ID,如果你需要访问这个 FRX 的话可以使用 SET DATASESSION TO This.FRXDataSession。第四个是报表的数据所在的数据工作期。如果该报表有一个私有数据工作期,那么这将是一个唯一的数据工作期;否则它将是REPORT 或者 LABEL 命令从中开始执行的数据工作期。CurrentDataSession 属性可以告诉你使用哪个数据工作期,因此如果一个 ReportListener 需要去访问该报表的数据,你需要执行 SET DATASESSION TO This.CurrentDataSession。请保存下 ReportListener 的数据工作期,并在选中了报表数据或者 FRX 数据工作期后记得再切换回来。

查看在 TestDataSessions.PRG 中的代码并运行它,看看这些不同的数据工作期是如何工作的。

报表事件

总体上,当某些事情对报表造成影响时,会触发报表事件,如表3所示: 表3、ReportListener 基类的报表事件

事件

参数

说明

LoadReport

类似于表单的Load事件,它是第一个被触发的事件,返回.F.可以阻止报表的运行。由于这个事件的触发发生在加载FRX文件和打开打印机池之前,所以,你可以从这里来在报表运行前改变磁盘上FRX文件的内容、或者改变打印机环境。

UnloadReport

就像表单的Unload事件,UnloadReport 在报表运行完之后触发。通常用于清理任务

BeforeReport

在FRX文件被加载了之后、但在报表运行之前被触发

AfterReport

在报表运行之后被触发

带区事件

当一个带区被处理的时候会触发带区事件。这些事件如表4所示: 表4、ReportListener 基类的带区事件

事件

参数

说明

BeforeBand

nBandObjCode,nFRXRecno

在一个带区被处理前触发。第一个参数表示该带区在FRX表中记录的OBJCODE字段中的值,第二个参数是该带区在FRX游标中记录的记录号

AfterBand

nBandObjCode,nFRXRecno

在一个带区被处理之后触发。参数跟BeforeBand相同

对象事件

当一个报表对象正在被处理的时候触发这些事件。 EvaluateContents(nFRXRecno, oObjProperties):这个事件会在每个字段对象(但不包括标签对象)被绘制前被触发,所以这就给了 listener 以改变这个字段的表现的机会。第一个参数是这个正在被处理字段对象在FRX文件中对应记录的记录号,第二个参数是一个对象,它有着一批属性,这些属性中包含的都是关于这个字段对象的信息。该对象包含的属性如表5所示。你可以改动这些属性中的任何一个以改变该字段在报表中的表现。如果你真这么做了,请将该对象的 Reload 属性设置为 .T. 以通知报表引擎,告诉它你改动了一个或者多个其它属性。此外,如果别的 listener 可以对该字段做更多改动的话,则返回一个 .T.。

表5、被传递给EvaluateContents 事件的 Object 参数的属性

属性

类型

说明

FillAlpha

N

填充颜色的 alpha、或者透明部分。值的范围从代表透明的0一直到代表不透明的255,这样跟简单的指定透明还是不透明比起来有着更好的控制。

FillBlue

N

填充色RGB()值的蓝色部分。

FillGreen

N

填充色RGB()值的绿色部分。

FillRed

N

填充色RGB()值的红色部分。

FontName

C

字体的名称

FontSize

N

字体的尺寸

FontStyle

N

一个代表字体样式的值,可以是以下值的和:1(粗体)、2(斜体)、4(下划线)、以及128(删除线)

PenAlpha

N

画笔颜色的alpha值

PenBlue

N

画笔颜色RGB()值的蓝色部分

PenGreen

N

画笔颜色RGB()值的绿色部分

PenRed

N

画笔颜色RGB()值的红色部分

Reload

L

如果你改动了一个或多个其它属性,请将这个值设置为.T.

Text

C

要为字段对象输出的文本

Value

-

该字段要输出的真正值

AdjustObjectSize(nFRXRecno, oObjProperties):这个事件在每个Shape或者Image对象刚要被绘制前触发。它给了你改变这个对象的能力,通常被用在当你想要将Shape或者Image替换成一个自定义的绘制好了的对象、或者需要动态改变对象的大小的时候。第一个参数是被处理对象的FRX记录号,第二个参数是一个对象,它包含着一批属性,这些属性中带有关于Shape或者Image的信息。这些属性如表6所示。如果你改变了Height或者Width,请将该对象的Reload属性设置为.T.以通知报表引擎你改动了这些属性。将对象的高度改变为使得对象要分开多页才能完整显示这种事情是不支持的;如果你改变了一个对象的高度以至于当前页上放不下这个对象了,那么这个对象将整个被放到下一页上去。

MaxHeightAvailable和Reattempt属性能帮助你判定当前页上还余留着多少空间、和对象是否会被推到下一页上去。

表6、被传递给AdjustObjectSize的对象参数的属性

属性

类型

说明

Height

N

计量单位为960分之1英寸的对象高度,可用值从0到64000。加大这个值(减小这个值会被忽略)会导致在带区上的其它浮动对象被向下推、并且带区会被缩放。

Left

N

对象的左边位置。只读。

Top

N

对象的顶边位置。只读。

Width

N

计量单位为960分之1英寸的对象宽度,可用值从0到64000。

MaxHeightAvailable

N

当前页上可以用来放置该对象的最大空间数量。只读。

Reattempt

L

如果为.T.,则该对象已经被推到下一页上去了,因为当前页上放不下它。只读。

Reload

L

将这个值设置为.T.以通知报表引擎,告诉它你已经改动了一个或者多个其它属性。

Render(nFRXRecno, nLeft, nTop, nWidth, nHeight, nObjectContinuationType, cContentsToBeRendered, GDIPlusImage):这个事件是一个大家伙。对每个要被绘制的对象,报表引擎都要调用该方法至少一次(对跨带区或者页的对象则超过一次)。跟其它对象事件一样,第一个参数是被绘制对象的FRX记录号。接下来的四个参数表示该对象的位置和大小。

nObjectContinuationType指示一个field、shape、或者Line对象是否跨越一个带区或者页;它包含下面四种可能的值中的一个: 0:这个对象是空的;它不会延伸到下一带区或者页上; 1:这个对象已经被开始,但不会在当前页上结束; 2:这个对象正在绘制中;它既不从当前页上开始、也不在当前页上结束; 3:这个对象已经在当前页上结束。 cContentsToBeRendered 包含着一个字段的文本、或者一幅图片的文件名。对于字段,其内容以Unicode的格式提供,使用与FRX记录相关联的FontCharSet信息来适当的将之转换为正确的本地化字符。如果你想要对它做些什么——比如要将之存储到一个表中去——可以使用STRCONV()来对这个字符串进行转换。GDIPlusImage参数用在被绘制的是来自一个General字段中的图片、而且SendGDIPlusImage属性大于0的情况下;它包含着这副图片的句柄。

你可以在这个方法中插入代码以按你自己想要的途径来绘制一个对象。注意,那样做的话,几乎你需要做的每一件事情都需要调用GDI API函数,所以它不适合心脏衰弱的人。参见本章后面的“_GDIPlus.VCX”主题部分。

方法

ReportListener的方法列表如表7。

表7、ReportListener基类的方法

事件

参数

说明

CancelReport

允许VFP代码及早的中止一个报表。这样就要求ReportListener能够完成象关闭打印机池之类重要的清理工作。

OnPreviewClose

lPrint

应该从一个预览窗口中、当用户关闭预览窗口或者从预览状态打印一个报表时调用这个方法。

OutputPage

nPageNo,eDevice,nDeviceType[,nLeft,nTop,nWidth,nHeight[,nClipLeft,nClipTop,nClipWidth,nClipHeight]]

将指定绘制好了的页输出到指定的设备。当设备类型是一个容器的时候,可选的从nLeft到nClipHeight这几个参数允许这个listener去指定目标设备上的哪个区域被用于绘制。后面将会讨论更多细节。

IncludePageInOutput

nPageNo

如果指定的页包含在输出中则为.T.,否则则为.F.

SupportsListenerType

nType

如果listener支持指定类型的输出则为.T.

GetPageHeight

在一个报表运行过程中返回页的高度

GetPageWidth

在一个报表运行过程中返回页的宽度

DoStatus

cMessage

在一个报表运行的时候提供无模式的反馈

UpdateStatus

更新用于反馈的用户界面

ClearStatus

去掉无模式的反馈界面

DoMessage

cMessage[,nParams[,cTitle]]

如果AllowModalMessages为.T.则在一个报表的运行过程中提供模式的反馈;否则则调用DoStatus。nParams和cTitle是可选的参数;如果传递了这两个参数,则它们被用作调用MESSAGEBOX()时的第二和第三个参数。

这里的OutputPage方法需要详细讨论。nDeviceType参数决定这个方法将会执行的输出的类别;它还决定eDevice参数希望接收到的数据类型。表8列出了ReportListener基类支持的输出类型以及nDeviceType和eDevice的值。其子类可以接收其它类型的输出,比如PDF或者其它的自定义格式。

表8、OutputPage支持的输出类型

nDeviceType

说明

eDevice

-1

没有设备

0

0

打印机

打印机句柄

1

图形设备

GDI 图形句柄

2

VFP预览窗口

对要输出的VFP控件的对象引用

100

EMF文件

文件名

101

TIFF文件

文件名

102

JPEG文件

文件名

103

GIF文件

文件名

104

PNG文件

文件名

105

BMP文件

文件名

201

多页TIFF

文件名(这个文件必须已存在)

ListenerType属性会影响OutputPage的值。表9显示了ListenerType的各种值以及每一种对输出的影响。

表9、ListenerType的各种值时如何影响OutputPage的

ListenerType

输出类型

如何影响OutputPage

0

“一次一页”模式,发送到打印机

报表引擎在每一页被绘制完后调用一次OutputPage以输出到一台打印机。报表引擎给这个方法传递0(打印机)作为 nDeviceType、传递给打印机的GDI 句柄作为eDevice。

1

“一次所有页”模式,自动打开预览窗口

当所有的绘制都完成之后,报表引擎会打开一个预览窗口,方法是或者调用(_ReportPreview)来新建一个、或者直接使用在Listener.PreviewContainer中的那个。预览窗口调用OutputPage来显示指定的页。在这种情况下,nDeviceType是2而eDevice是一个对“一个用作输出时的占位符的VFP控件”的对象引用。

2

“一次一页“模式,不发送到打印机

报表引擎在每一页被绘制好之后调用OutputPage,但没有输出被发送到打印机。报表引擎传递-1作为nDeviceType、0作为eDevice。

3

“一次所有页”模式,没有自动预览窗口

在绘制完成后,必须手动调用OutputPage来输出指定的页。

通过这种途径,由于report listener使用的是VFP代码,所以现在想要在报表运行的过程中跟踪代码、完成过去没法做到的事情以及曾经是痛苦之源的在报表中使用用户自定义函数等等都已经变为可能了。

0 人点赞