制作动态数据报表
陈冬艳
PowerBuilder是功能强大的数据库前端开发工具,深受软件开发人员的青睐,然而由于PowerBuilder所提供的报表格式较少,如果只是简单地使用PB中提供的报表对象制作报表,往往不能满足某些专业报表的制作要求,尤其是动态报表制作,因此,要想制作动态报表必须使用PowerScript脚本,在报表生成后调整各种属性设置。
问题的提出
对于一张A4大小的纸,在固定好标题和注脚的位置后,其报表内容根据当时报表选择的字体、字号的不同可以容纳不同的数量,参看图1、图2。
|
标题 |
|
| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
|
1 |
|
图1 8行/页
| 标题 | |
| 1 | |
| 2 | |
| 3 | |
| 4 | |
|
1 |
|
图2 4行/页
如果用PB制作类似图1或图2的报表,报表中能容纳多少行数据,开发人员在设计报表对象时是未知的,只有当程序运行起来之后才能看到。如果能在程序运行过程中获取报表每页的行数,动态控制行高,就可以实现动态调整报表每页行数的功能。
PowerBuilder中的报表分两种,一种是直接用数据窗口(DataWindow)做报表,一种是使用PB的报表控件和报表对象(Report)制作报表。如果程序对报表的要求不是很特殊,使用Report就能制作符合要求的报表。用Report制作报表的局限性在于:一旦对Report的格式、内容、所挂的表及显示的列进行设置之后,就不能在运行时修改这些设置,当然也就不能满足在程序中动态设置报表各种属性的要求。而数据窗口则灵活得多,它的各种属性都可以在程序中动态设置,因此对于有特殊要求,尤其是在打印之前需要做格式调整的报表来说,使用数据窗口无疑是更方便、灵活的。
使用数据窗口做打印报表,与制作显示数据的数据窗口的过程和技巧一样,只是要在其放置的窗口上加上打印功能,用Print(DataWindow)命令,将其输出到打印机上即可,因而开发人员可以集中精力专心制作报表的各种功能。下面就介绍三种使用数据窗口制作动态报表的方法。
控制打印行数
一般来说,报表只是供打印输出的,很少有人关心报表每页可以容纳多少行数据。而且大家也知道,数据窗口中每页有多少行数据在开发过程中是未知的,那么,能不能在程序运行的过程中,动态地设置报表每页的行数呢?
事实上,要想实现打印行数的控制,关键问题在于控制DataWindow对象的细目带(Detail
Band)的高度(Height)。
图3 数据窗口的Print.Preview = No,此时,LastRowOnPage = 6
控制Detail Band高度的方法有两种,一种方法是计算法:即把DataWindow对象的Units属性改为1/1000厘米,然后得到Print
Paper(打印纸)的高度(单位厘米),用此高度减去Header Band、Summary
Band、Footer Band的高度,再除以每页打印的行数即得到Detail Band的高度。公式如下:
Detail.Height =[Paper.Height -(Header.Height + Summary.Height +Footer.Height)]/(行数/每页)
图4 数据窗口的Print.Preview = Yes,此时LastRowOnPage = 40
另一种方法是:不改变DataWindow对象的Units属性,Units仍为PBU,然后在程序运行过程中读取当前DataWindow每页的行数,与用户输入的行数做比较,若当前行数大于用户输入行数,则将Detail
Band的Height增大,若小于则减小。
在两种方法中,我个人认为后一种方法比较好。首先,PBU是PB定义的系统单位(不是Windows系统的单位,是PB自己的系统单位),它根据机器安装的系统字体信息(Windows系统字体)计算自己的PBU单位,这样起码从移植性来说要比前一种方法好;其次,第一种方法由于计算公式中有除法,所以结果有可能是小数,程序可能不好控制,而第二种方法使用的是PBU单位,一般来说都是整数运算,程序的控制部分相对简单一些。
应用第二种方法实现打印行数的控制,首先需要知道数据窗口中与此相关的几个属性。
1. LastRowOnPage Property
PB的数据窗口有一LastRowOnPage属性,它的作用是:记录数据窗口在当前打印模式下每一页中最后一行记录的行号。例如:在数据窗口处于非打印模式时,每页有5条记录,那么LastRowOnPage的值为5、10、15……;同样是这个数据窗口,如果在打印模式下,每页可以容纳30条记录,那么LastRowOnPage的值为30、60、90……,显然LastRowOnPage的值和数据窗口当前的打印模式和当前页面有关。
PB的数据窗口有两种模式:非打印模式和打印模式。这两种模式之间的切换是通过Modify语句,将数据窗口的Print.Preview属性设置为Yes或No实现的。缺省情况下,通过窗口的数据窗口控件所显示的数据窗口对象Print.Preview的值为No。此时,数据窗口对象中页面的大小和数据窗口控件的大小相同,也就是放置在窗口上的数据窗口控件中能够显示多少行数据,则数据窗口对象的页面也只能显示同样多的数据,如图3所示。
而一般来说,此时LastRowOnPage的值并不是真正打印时,每页最后一条记录的行号。因此只有在数据窗口对象处于打印模式下,LastRowOnPage的值才是每页的最后一条记录的行号,如图4所示。
使用LastRowOnPage属性需要注意的一点是:当前数据窗口中必须有数据。如果数据窗口中无数据,则该值为0;如果数据窗口中的数据不足一页,则该值只是最后一条记录的行号。因此,若要在程序中实现打印行数的控制,必须保证数据窗口中的数据足够多,尤其是使用External数据源的数据窗口,在加载打印数据之前必须加载足够一页多的数据,取得LastRowOnPage的值,然后进行调整。
2. Detail Band
Detail Band姑且称它为细目带,是数据窗口对象加载、显示、编辑数据的地方。从后台数据库取出的记录被全部加载到细目带中,因此,它也是报表打印中一个需要调整的重要部分。
Detail Band的一行显示一条数据库记录,因此,打印时每页可以容纳多少行数据和Detail
Band的行高有关。在标题带(Header Band)、注脚带(Footer Band)和统计带(Summary
Band)的高度定义好以后,Detail Band的高度就是决定每页可打印行数的关键。
数据窗口的Band属性有多种,其中我们要用到的一个是Height属性。在程序运行时可以使用Describe语句及时获得Detail
Band的Height属性,然后,根据用户需求,用Modify语句调整Height的值,实现打印行数的控制。
3. 代码实现
(1)新建一External数据源、Grid风格的数据窗口(根据程序需要使用External数据源,使用其他数据源生成的数据窗口原理类似);
(2)新建一窗口,在其上放置数据窗口控件,并把数据窗口控件的数据窗口对象属性赋予刚才建立的External数据窗口;
(3)在窗口的Open事件中写入如下代码:
long i
long li
FOR i = 1 TO 40
li = dw_print_modify.InsertRow(0)
dw_print_modify.SetItem(li,1,string(i))
dw_print_modify.SetItem(li,2,string(i))
dw_print_modify.SetItem(li,3,string(i))
......
NEXT
dw_print_modify.Modify("DataWindow.Print.Preview = Yes")
li_pagerow_user=long(dw_print_modify.Describe("DataWindow.LastRowOnPage"))
-1*
sle_pagerow.Text = string(li_pagerow_user)
上述代码的作用就是加载足够多的数据,保证这些数据能够装满一页,然后取得LastRowOnPage的值。
注意上述代码中做了标记*的部分,减1的目的是为了给Summary
Band留出空间。因为PB中的Summary Band是在全部数据加载完毕后才显示或打印出来,而在初始化DW的时候,由于不知道DW中的数据最多可以容纳多少行,则若循环终值设成40,那么DW中的第一页的最后一行占据了Summary
Band的地方,所以DW实际容纳的数据的行数要减去1。
(4)在窗口上放置单行编辑框(Sle_pagerow),作为用户输入的接口,将调整Detail
Band行高的代码写在单行编辑框的Modify事件中,如下:
long li_row // 用户输入的行数
long li_page_modify //调整Detail高度后每页的行数
long li_detail //Detail Band的高度(整数)
string str_detail // 用Describe语句取出的行高
li_row = long(This.Text) + 1
str_detail = dw_print_modify.Describe("DataWindow.Detail.Height")
// 得到Detail Band的高度
IF li_row 〉 li_pagerow_user THEN //
如果输入的行数大于当前行数,则高度减小
li_page_modify = li_row
IF li_page_modify 〉= 1 THEN
DO
li_detail = long (str_detail)// 将取出的高度转变成整数
li_detail = li_detail - 1// 高度减小
str_detail = string(li_detail)// 将高度转变成字符串
dw_print_modify.Modify("DataWindow.Detail.Height = " +
str_detail) // 修改Detail Band的高度
li_row= long(dw_print_modify.Describe("DataWindow.LastRowOnPage"))
LOOP WHILE li_row 〈 li_page_modify //
若调整后的行数大于等于用户输入的行数则停止
li_pagerow_user = li_row - 1
sle_pagerow.Text = string(li_pagerow_user)
ELSE
MessageBox("提示信息","每页不能少于1行",information!,OK!)
END IF
ELSE // 如果用户输入的行数小于当前行数则高度增大
li_page_modify = li_row
IF li_page_modify 〉= 1 THEN
DO
li_detail = long (str_detail)
li_detail = li_detail + 1
str_detail = string(li_detail)
dw_print_modify.Modify("DataWindow.Detail.Height = " +
str_detail)
li_row=long(dw_print_modify.Describe("DataWindow.LastRowOnPage"))
LOOP WHILE li_row 〉 li_page_modify //
若调整后的行数小于等于用户输入的行数则停止
li_pagerow_user = li_row - 1
sle_pagerow.Text = string(li_pagerow_user)
ELSE
MessageBox("提示信息","每页不能少于1行",information!,OK!)
END IF
END IF
上述代码在PB 6.5、Win 95环境下调试通过。
使DW中的数据垂直居中
凡是使用过PB的人都知道,PB是面向对象的开发工具,开发过程充分体现了对象的概念。比如:在数据窗口对象的设计中,我们要想让Detail
Band中的每一列数据都横向居中排列,要设置列的Alignment属性为Center。
但是,用户提出要数据在列中垂直居中的要求,如果数据格式没有变化,我们也许可以做到,只要多次调试行高、字体、字号找到一个比较合适的位置就可以了。然而如果数据的格式不固定,或用户想在打印之前选择不同字体、字号的情况下,这种方法就不适用了。
例如:用户要求的报表格式为:
图5 用户要求的打印格式
那么就需要程序在加载数据的时候做到让第一行中E字段的数据垂直居中。如果不用PowerScript编写代码,那么用PB的数据窗口直接显示的结果见图6:
图6 PB的DW生成的结果
PB中没有提供专门处理数据垂直居中的函数,列属性中也没有相应的属性,因此实现列数据的垂直居中就要使用PowerScript脚本编写代码,通过动态设置列对象的各种属性实现这一功能。
思路是:将需要调整的E字段的Y值,加上一个变化量,也就是下移该字段的长度。使用Modify语句,调用DW中的表达式If(b,t,f),根据E字段的数据设置不同Y值。假设:E字段和ID字段在同一行上,有相同的Y值,不论用户是否调整Detail
Band的行高,ID列的Y值是不变的。使用Modify,判断如果E列中的数据大于6(视情况而定),则将E列的Y值加上(下移)20(同样视情况而定),否则,Y值等于ID列的Y值。
代码如下(在放置数据窗口的窗口中再放置一命令按钮,在按钮的Clicked事件中写入代码):
string str_col_name // 字段名
string str_pos_y // 欲调整字段的Y值
string modstring,str_rtn
long li_pos_y // 欲调整字段的Y值(整型)
str_col_name = "列名"
str_pos_y = dw_print_modify.Describe(str_col_name + ".Y")
li_pos_y = long(str_pos_y)
// 初始条件:所有列都在同一行上,Y值相同
modstring = str_col_name + ".Y = '" + str_pos_y + " ~t if(
Len( " + str_col_name + " )〈= 8 ," + string(li_pos_y + 15) +
"," + string(li_pos_y - 10) + ") '"
str_rtn = dw_print_modify.Modify(modstring)
设置字体“胖瘦”
PB中字体Font对象有个Width属性,但是一般来说用不到它,因为Windows系统已经内置了诸多字体,各个字体的信息都已设置好。我们只需根据不同的需要,把不同字体名FontName(如宋体、楷体)赋给所使用的对象的字体名属性即可。
但是在报表打印输出的时候,尤其是Grid风格的报表,表格带有边框线,若列数据的长度超过了列允许的长度,那么后面的数据就看不到了。怎样在固定好列长度之后动态调整字体宽度,以便超过长度限制的数据能排在一行中呢?
解决的办法就是动态设置Width属性。Font.Width属性值可为整型数字,程序员可以根据自己的需要进行设置。通过试验发现,在PB中普通系统字体对应的Width值在45~50之间,小于这个值则字变瘦,大于这个值则字变胖。Width值在每次变化量为2的情况下,人眼在屏幕上可看出明显变化。
通过Modify语句修改Width的代码如下:
string str_col_name// 列名
string str_rtn // Modify语句返回值
string modstring
modstring = str_col_name + ".Font.Width = '" + string(45) +
" ~t if( Len(" + str_col_name + " ) 〈= 16 ," + string(45)
+ "," + "if (Len( " + str_col_name + ") 〈= 18 ,"
+ em_think.Text + "," + string(long(em_think.Text) - 2) + ")
)'"
str_rtn = dw_print_modify.Modify(modstring)
上述代码实现的功能是:假设列允许的长度为16个英文字母(8个汉字),则小于16的数据,字体宽度为45(即普通系统字体宽度,近似值),长度大于16而小于18的数据,字体宽度为em_think(EditMask编辑屏蔽框)中设置的数字大小,而长度大于18的数据的字体宽度为em_think的整型数字减去2。