〔连载〕VFP9增强报表-多细节带区

2022-04-07 19:40:08 浏览数 (2)

译者:Fbilo

多细节带区

Crystal Reports 是世界上被用的最多的报表工具。尽管 VFP 已经有了内建的报表编辑器,但许多 VFP 程序员还是使用 Crystal 的原因之一,就是因为它支持子报表。子报表就是运行在一个报表中的报表。子报表最常见的用途是为一个父表生成多个子表的报表。

例如,假定你有一个客户表 Customers、一个订单表 Invoices、以及一个信用证表。你可能会希望在一个报表上同时显示客户、他们的订单、以及信用证。这里的难点在于这个报表有三个需要遍历的表,虽然订单表和信用证表都关联到客户表上,但前两者之间却没有彼此关联。在 Crystal 中的解决办法是:先建立一个显示客户和他们的订单的报表,然后给它添加一个子报表来为当前客户显示信用证数据。

不幸的是,到现在为止,在 VFP 中也没有能做到同样事情的办法。一个常用的变通办法是建立一个合并了订单表和信用证表的游标,添加一个字段“Record type”来指示某条记录是来自哪个表的数据。报表的细节带区中同时包含着来自两种记录类型中的全部字段,在那些字段上还要做一个 Print When 表达式以使得为每种类型的记录仅打印属于它的字段。做出来的是一个非常不便于维护的报表! 幸运的是,VFP 9 通过一个新的功能很好的解决了这个问题:多细节带区。

记录处理

在探讨多细节带区之前,让我们先讨论一下在一个报表中,VFP 是如何在记录们中间移动的。一个报表有且只有一个“驱动”游标。VFP 以单循环遍历这个游标,也就是说,这个游标仅会被处理一次。这些记录的处理会在分组的时候暂停,报表引擎采取被指定的任何操作(例如,为前一个组打印一个组注脚、并为新的组打印一个组标头),然后继续处理这个游标。一组不中断就被连续处理的记录被称为一个“细节范围(detail scope)”。如果报表中有任何组存在,细节范围指的就是在分得最小的组内部的那些记录。如果报表中没有组,则细节范围就是整个报表的范围。

在 VFP 9 中,现在可以有多个细节范围了(超过20个)。一个特定的细节范围中的记录可以是来自子表中的相关记录,也可以是驱动游标中的记录,而这就意味着它可以被处理多次。报表设计器把这些多细节范围当作多细节带区来呈现。要注意的一件重要事情是:细节范围们是连续的,而不像组中断一样是嵌套的。 计算字段和报表变量的作用范围现在可以是在一个特定的细节带区内了。有趣的是,变量会一直保留着它们的值,一直到它们被指定的有效范围被再次处理为止。这就意味着如果需要的话你可以在后面的细节带区中使用这些变量。报表属性对话框的 Variables (变量)页现在使用 “reset based on(基于…重置)”而不是“reset at(在何时重置)”作为一个变量作用范围的提示,来增强这个功能。

建立多细节带区

每个报表都有至少一个细节带区。要建立额外的细节带区,请从报表菜单、报表快捷菜单中选择 Optional Bands(可选带区),或者打开报表属性对话框、然后选择 Optional Bands 页(参见图17)。Add(添加)按钮添加一个新的细节带区,而 Remove (删除)按钮会删除选中的细节带区。你可以重新排列在列表中那些带区的顺序。

在一个细节带区的属性对话框中,你可以指定该带区是否要有一个标头和一个注脚带区,并为这个细节带区指定目标别名表达式(Targe alias expression,参见图18)。通常,报表引擎在移动到下一个细节带区之前会处理在驱动游标中的一条记录。然而,如果你指定了一个子表游标作为目标别名,报表引擎会在移动到下一个带区之前处理当前驱动游标记录的所有子表记录。注意:你应将目标别名作为一个表达式输入;要使用一个硬编码的名称,请在该名称两端加上引号。由于这是一个表达式,所以你可以输入一个包含目标别名的变量名称、或者甚至是调用一个用户自定义函数(UDF)。这也许会导致产生某些非常有趣类型的报表!

图17、你可以在报表属性对话框的 Option Bands 页上定义多细节带区

图18、使用细节带区属性对话框来指定选定带区是否有标头和注脚带区,并为(该细节带区)提供目标别名

目标别名表达式可以被运算为下列三个值之一:

  1. 一个空的字符串,表示使用驱动游标;
  2. 一个子表的别名,告诉报表引擎在移动到下一个带区之前处理当前驱动游标记录的所有子表记录。这要求在驱动游标和子表之间存在着一个关联,可以使用 SET RELATION 命令或者在报表的数据环境中建立一个关系。
  3. 驱动游标别名,这只在第一个细节带区中有效,它告诉报表引擎在移动到下一个细节带区前去处理所有的驱动游标记录,直到遇到一个组中断、或者报表范围的末尾。

细节带区现在还可以有一些与组带区同样的选项:在一个新的列或者页上开始、为每个细节集重置页码为1、在每一页上重新打印细节标头、当一页上的空间数量小于一个希望的值的时候,在新一页上开始细节集。

让我们来看两个多细节带区报表的示例。

示例1:多个子表

第一个示例 EmployeesMD.FRX 使用来自 VFP 自带的 Northwind 示例数据库(在 VFP 主目录下的 SamplesNorthwind 子目录中)中的 Employees、EmployeeTerritories 和 Orders 表。EmployeeTerritories 和 Orders 都是 Employees 的子表,它们之间以每个表中的 EmployeeID 字段相关联。我们想要的是这么一个报表:显示每个雇员(Employee)、他或她负责的地域(territories)、以及这个雇员所接到的订单(Orders)。 这个报表的数据环境设置如图19所示。在 Employees 和它的子表们之间的是一对多关系(Relation 对象的 OneToMany 属性为 .T.),所以一个指定雇员记录的所有子表记录都会在一个细节带区中被处理。注意这一点要求并不严格,因为如果你遗漏了设置 OneToMany,报表引擎会自动使用 SET SKIP 来做到同样的事情

图19、EmployeesMD.FRX 的数据环境将 EmployeeTerritories 和 Orders 定义为 Employees 的子表

这个报表有一个定义为 Employees.EmployeeID 的组表达式 ,这个来自 Employees 表的字段将出现在组标头带区中。这里有两个细节带区,一个的目标别名(Target alias)为 EmployeeTerritories,另一个的目标别名则为 Orders;相应的字段出现在每个带区中。图20显示的是这个报表在报表设计器中的情况。图21 显示的是该报表运行的结果。

图20、EmployeesMD.FRX 有两个细节带区,一个用于 EmployeeTerritories 的字段们,一个用于 Orders。

图21、运行 EmployeesMD.FRX 演示了一个多细节带区报表的工作

示例2:预先计算的合计

下一个示例与第一个类似,但是它不显示两个子表,而是运行同一个子表两次。这里我们的想法是为每个雇员计算订单的数量和合计,只是我们希望在显示真正的订单之前就显示这些计算结果。此外,我们还想要显示每个订单的合计占全部订单合计数的百分比,这就意味着我们要预先计算合计。 在过去版本的 VFP 中,实现这些功能需要在运行报表前就先进行好计算,并在报表中使用这些计算的结果。在 VFP 9 中,这只是简单的意味着要有一个细节带区来进行计算、而用另一个细节带区来显示结果。在这个示例 EmployeesMD2.FRX 中,这两个细节带区都使用 Orders 表作为目标别名。图22显示的是这个报表在报表设计器中的情况,注意在细节带区1中是没有对象的。

图22、EmployeesMD2.FRX 使用细节带区1来预先计算在余下报表中的合计

这个报表的数据环境的设置如图23所示。在 Employees 和 Orders 之间的关系是一对多,因此一个指定雇员的所有订单在两个细节带区中都将被处理。

Customers 表与 Orders 表相关,因此一个订单的客户名称被包含在这个报表里。Order_Subtotals 是一个视图,它计算出每个订单的子合计,然后放到它自己的 Subtotal 字段中。我们想要让 Order_Subtotals 称为 Orders 表的一个子表,但是由于你不能在数据库中为视图定义索引或者关系,所以我们在数据环境的 OpenTables 方法中以代码来实现:

代码语言:javascript复制
local lnSelect
dodefault()
if empty(cdx(1, 'Order_Subtotals'))
  lnSelect = select()
  select Order_Subtotals
  index on OrderID tag OrderID
  select Orders
  set relation to OrderID into Order_Subtotals additive
  select (lnSelect)
endif empty(cdx(1, 'Order_Subtotals'))
nodefault

图23、EmployeesMD2.FRX 的数据环境为这个报表设置需要的关系

报表里定义了两个变量:OrdersCount,这是一个基于细节带区1重置的“count(计数)”变量;还有 OrdersTotal,它对 Order_Subtotals 求和,并且也是基于细节带区1重置的。因此,细节带区1的全部用途,就是为当前雇员处理所有的订单记录,并为 OrdersCount 和 OrdersTotal 变量计算出属性值。然后,订单记录在细节带区2中再次被处理。订单的数量和合计被显示在细节带区2的标头中,而订单和每个订单所占总合计的百分比现在在细节带区中。 图24显示了这个报表运行时的情况。

图24、使用多细节带区可以很容易的在细节之前显示合计并计算一个合计的百分比

0 人点赞