PB是进行数据库软件开发较常用的工具,其获得多项专利技术的DataWindow控件能让开发者很方便地向用户显示或打印各种数据、报表。也有很多的第三方软件为PB提供强大的自定义报表功能,使开发者能满足用户对报表的个性要求。但用户不同时间对报表的要求不尽相同,使用同一软件的不同用户对报表的要求也不尽相同。开发者遇到此种情况,只能根据用户的新要求重新设计制作DataWindow对象,并生成新的可执行文件。
DataWindow对象有十多种类型,Tabular类型是较常用的一种,能很容易地制作出(如图1所示)带报表标题、页码等的报表。Tabular类型的DataWindow对象中所包含的所有子控件可以在制作时或软件运行中通过特定的代码任意移动、排列。根据这一特性,我们可以先制作出包含所有可供显示的数据项(列)的Tabular类型的DataWindow对象,再在软件中增加必要的代码,允许用户在使用中随意指定需要显示或打印的数据项(列),并按选中的序重新排列,隐藏没有选中的数据项(列)。如此,即可实现能允许用户随意指定数据项(列)及顺序,友好性和通用性极佳的PB自定义报表,而且不需要借助任何第三方软件。

图1 报表类型之一

图2 数据表结构
下面,我们以一个具体的实例来介绍实现此功能的方法和程序代码。
一、设置ODBC数据源
在SQL Server中新建数据库,取名为PB_report,并在该库中新建表t_Cp(具体定义如图2所示)。
在ODBC数据源管理器中新建连接Sql Server中PB_report数据库的系统DSN,取名为PB_report。
二、添加全局变量
在PB中新建WorkSpace,取名为PB_report。
在新建的Workspace中新建一个Application,取名为PB_report,并添加相应代码。
在Global Variables中添加语句,定义全局变量:
//用于向数据列选择窗口传递DataWindow对象的名称
string data_name
//用于从数据列选择窗口带回选择的数据列列表
string sjl_name
在Open事件中输入如下代码:
//设置连接数据库的字符串,连接第二步中建立的系统DSN:PB_report
//笔者测试用的Sql Server服务器的帐号为:sa,密码为:sa,
//如有不同请根据实际情况自行更改
sqlca.dbms="ODBC"
sqlca.dbparm="ConnectString='DSN=pb_report;UID=sa;PWD=sa'"
//连接数据库
connect;
//错误处理
if sqlca.sqlcode<0 then
messagebox("错误","连接失败,请尝试使用其他的数据源!",stopsign!)
return
end if
//打开功能窗口,演示可以随意指定数据列的功能
open(w_cp)
在Close事件中输入如下代码:
//当程序退出时关闭数据库连接
disconnect;
三、新建数据窗口
新建Tabular类型的DataWindow对象,取名为:d_cpxx,显示表t_cp中的所有数据列(外观如图3所示,预览的效果如图1所示),并作如下设置。
1.SQL语句为(使用向导生成时无需手工输入SQL语句):
SELECT t_cp.cpbh,t_cp.txm,t_cp.qc,t_cp.jc,t_cp.bz,t_cp.dbsl,t_cp.cbj,t_cp.dhj,t_cp.lsj,t_cp.lscxj,t_cp.zqj,t_cp.jfbz,t_cp.shgz,t_cp.kcsl,t_cp.zdkc,t_cp.zgkc FROM t_cp Order by cpbh
2.增加Header区的高度,并在上方添加一个Text控件,用于显示报表标题,取名为:t_bt。左边与第一个数据列左对齐,右边与最后一个数据列右对齐,在Text属性中输入:产品信息表,将Alignment属性设为:Center(2)。
3.修改Header区中各数据列标题控件的Text属性为:产品编号、条形码等。各控件的宽度必须与对应数据列左对齐,且宽度一致,名称必须按PB的默认规则(即数据列名称后面加_t),不得更改。

图3 产品信息表的设计界面
4.增加Summary区的高度,并添加一个显示页码的控件,取名为page_1。右边与最后一个数据列的右边对齐,在Compute Expression属性中输入:‘第' + page() +‘页,共' + pageCount()+‘页'。
5.在Header区中数据列标题的下方和Summary区页码显示控件Page_1的上方各添加一个Line控件(分隔线),分别取名为L_1、L_2,在Pen Width属性中输入:2,左边与第一个数据列左对齐,宽度与显示报表标题的控件t_bt等宽。
四、新建功能窗口
新建一个Window,取名为w_cp。在其Title属性中输入:产品管理,Window Type属性选择:response!,添加一个DataWindow控件和三个CommandButton控件(外观及各控件的名称如图4所示),并进行如下操作。

图4 产品管理外观设计
1.在Window的Open事件中输入如下代码:
//设置事务对象
dw_1.settransobject(SQLCA)
//显示数据
dw_1.retrieve()
2.在DataWindow控件dw_1的DataObject属性中输入:d_cpxx,选中HscrollBar、VscrollBar属性。
3.在CommandButton控件cb_1的Text属性中输入:选择数据列,并在Clicked事件中输入如下代码:
//定义变量
long l_zs
string k1,k2
//x1用于控制报表中每一列的X坐标,第一列的X坐标为9
long x1=9
//将需要选择打印列的数据窗口对象的名字存于全局变量data_name中,传递到列选择窗口中
data_name="d_cpxx"
//调用列选择窗口
open(w_sjl)
//如果全局变量sjl_name带回的不是字符串“-1”,就说明是按“确定”按钮返回的(选择了一些列或者一列都没有选择)
if sjl_name<>'-1' then
//如果带回的是空串,就说明没有选择列,此时需要显示所有的列,将数据窗口刷新即可
if sjl_name='' then
dw_1.retrieve()
return
end if
//如果带回的不是空串,说明选择了某些列。先隐藏所有的列,再根据选择的列及顺序逐一显示
//从第一列开始处理
l_zs=1
do while (dw_1.setcolumn(l_zs))<>-1
//取得指定列号的列名
k1=dw_1.getcolumnname()
//修改列控件、列标题控件的X坐标为足够大的负数,使其隐藏
//产生修改数据列X坐标用的字符串
k2=k1+".x="+string(-10000)
//修改指定列的X坐标
dw_1.modify(k2)
//产生个性数据列标题X坐标用的字符串(列标题控件的名称比列控件的名称多_t)。
k2=k1+"_t.x="+string(-10000)
//修改指定列标题的X坐标
dw_1.modify(k2)
//准备处理下一列
l_zs++
loop
//根据选定的列及顺序让对应数据列及列标题显示出来
//保证带回的字符串尾部有一空格,以满足循环处理的需要
if right(sjl_name,1)<>' ' then sjl_name=sjl_name+' '
do while len(sjl_name)>0
//取出第一列
k1=mid(sjl_name,1,pos(sjl_name,' ')-1)
//报表左边需要多空的,增加X1的初值即可
k2=k1+".x="+string(x1)
//修改对应列的X坐标
dw_1.modify(k2)
//列名加"_t"就是列标题控件的名字
k2=k1+"_t.x="+string(x1)
//修改对应列标题的X坐标
dw_1.modify(k2)
//读出这一列的宽度,用于确定下一列的X坐标,两列之间间隔14个PB单位点
k2=k1+".width"
x1=x1+14+long(dw_1.describe(k2))
//去掉已经处理完的这一列,让下一列成为第一列,为下次处理做准备
sjl_name=mid(sjl_name,pos(sjl_name,' ')+1)
loop
//计算总宽度,用于修改报表标题的宽度
//X1在最后一次计算时也加了14个PB点,需要减下来
x1=x1 - 14
//修改报表标题的宽度。没有添加报表标题的下一行删除
dw_1.object.t_bt.width=x1
//修改报表中上下分隔线的X2坐标,控制分隔线的长度。没有添加分隔线的下两行删除
dw_1.object.l_1.x2=x1
dw_1.object.l_2.x2=x1
//修改页码的显示位置。没有添加页码的下一行删除
dw_1.object.page_1.x=x1 - integer(dw_1.object.page_1.width)
end if
4.在CommandButton控件cb_2的Text属性中输入:打印,并在Clicked事件中输入如下代码:
//按用户的选择打印报表
dw_1.print()
5.在CommandButton控件cb_3的Text属性中输入:返回Esc,选中Cancel属性。并在Clicked事件中输入如下代码:
//关闭窗口并返回
close(parent)
五、新建可以让用户随意选择打印列及顺序的功能窗口
新建一个Window,取名为w_sjl。在其Title属性中输入:选择数据列,Window Type属性选择:response!,添加一个DataWindow控件、一个ListBox控件、四个StaticText控件和两个CommandButton控件(外观及各控件的名称如图5所示),并进行如下操作。
1.在Window的Open事件中输入如下代码:
//data_name中存储的是调用窗口传递过来的数据窗口对象的名称
//通过隐藏的数据窗口控件dw_1显示此数据窗口对象,用于读取列标题
dw_1.dataobject=data_name
dw_1.settransobject(SQLCA)
dw_1.retrieve()
//定义变量
int l_zs
string l_name
//将数据窗口的所有列标题添加到列表框lb_1中
//从第一列开始,先通过setcolumn()函数定位到指定列
l_zs=1
do while (dw_1.setcolumn(l_zs))<>-1
//通过getcolumnname()函数得到数据列的名字,加上"_t"就是列标题控件的名字
//读取列标题控件的text属性的值就得到了列标题上显示的内容
//生成用于读取Text属性的字符串
l_name=dw_1.getcolumnname()+'_t.text'
//describe()函数用于读取指定控件指定属性的值, 此处用于读取列标题
//读取列标题
l_name=string(dw_1.describe(l_name))
//添加到列表框lb_1中,供用户选择
lb_1.additem(l_name)
//准备读取下一列
l_zs++
loop
//先让sjl_name存储'-1'字符串,用户点“取消”按钮时带回调用窗口
sjl_name='-1'

图5 数据列选择
2.取消DataWindow控件dw_1的Visible属性,使该控件隐藏。
3.选中ListBox控件lb_1的HscrollBar、VscrollBar、MultiSelect属性,允许使该控件支持多项选择;取消Sorted属性,不允许该控件对数据项进行排序。并在selectionChanged事件中输入如下代码:
if lb_1.state(index)=1 then
//现在是选中状态
//添加到选定的列标题列表,并在末尾加空格。在st_sjl中显示出来
st_sjl.text=st_sjl.text+lb_1.text(index)+' '
//选定的列的数量+1,选定的总列数在st_ls中显示出来
st_ls.text=string(integer(st_ls.text)+1)
else
//现在是未选中状态,说明以前是选中状态
//将列标题从选定的列标题列表中删除,改变st_sjl中显示的内容
st_sjl.text=replace(st_sjl.text,pos(st_sjl.text,lb_1.text(index)),len(lb_1.text(index))+1,'')
//选定的列的数量-1,并改变st_ls中显示的内容
st_ls.text=string(integer(st_ls.text)-1)
end if
4.在CommandButton控件cb_1的Text属性中输入:确定Enter,选中Default属性,当用户在窗口上按Enter键时执行此控件的Clicked事件。并在Clicked事件中输入如下代码:
//定义变量
int i
string k
//赋值成空串,为带回选择结果做准备
data_name=''
sjl_name=''
//如果选择了某些列
if integer(st_ls.text)>0 then
//将选定列的标题存入data_name中
data_name=st_sjl.text
//以空格为分隔,逐一取出列标题
do while len(data_name)>0
k=mid(data_name,1,pos(data_name,' ')-1)
//查找列标题在列表框中的位置(顺序号)
i=lb_1.finditem(k,0)
//取得的顺序号,即该列在数据窗口中的列号。定位到该列
dw_1.setcolumn(i)
//读出列的名称,并添加到选定列列表中,尾部加空格作为分隔
sjl_name=sjl_name+string(dw_1.getcolumnname())+" "
//去掉已经处理的列标题,为下一次处理做准备
data_name=mid(data_name,pos(data_name," ")+1)
loop
//data_name中的内容已被更改,再次将选定列的标题存入其中
data_name=st_sjl.text
end if
//一列都没选时,直接关闭该窗口并返回。此时data_name,sjl_name中带回空字符串
close(parent)
5.在CommandButton控件cb_1的Text属性中输入:取消Esc,选中Cancel属性,当用户在窗口上按Esc键时执行此控件的Clicked事件。并在Clicked事件中输入如下代码:
//取消了选择列的操作,关闭该窗口并返回
//此时sjl_name带回的是在open事件中赋值的字符串'-1'
close(parent)
六、操作示范
点击PB工具栏上的 运行按钮。程序运行时,DataWindow中首先显示的是所有的数据列,点击“选择打印列”按钮,随意地选择需要显示或打印的数据列(在数据列选择窗口中随意选中五列时的运行效果如图6所示),并可通过选中的次序控制在DataWindow中的排列顺序。

图6 运行效果
上面的实例根据Tabular类型DataWindow对象的特性,通过setcolumn()、getcolumnname()、describe()、modify()等函数的巧妙运用,制作出了能让用户在使用中随意指定需要显示或打印的数据列,并能按选中的次序重新排序的PB自定义报表。这一功能的实现必将大大增加软件的通用性和友好性。大家还可以在数据库中添加一张表,在软件中增加专供用户对各DataWindow对象选择数据列的功能窗口,记录下各用户的选择结果,使用时直接读取这些结果,省去用户必须每次选择的环节,进一步提高软件的技术含量及友好性。
以上程序在WinXP/PowerBuilder 9.0/SQL Server 2000环境下调试通过。
|