动态生成报表,一个奇怪的题+一个难题!!!!(50分)

  • 主题发起人 主题发起人 del520
  • 开始时间 开始时间
D

del520

Unregistered / Unconfirmed
GUEST, unregistred user!
要动态生成报表,就是在窗体上放许多 CheckBox,根据用户的显示来打印表内容
一个空白的QuickRep和一个空白的QrSubDetail,然后使用语来动态生成QRlabel和
QrDbText,用.top 和.left来控件距离,发生难题:
1:要控制好报表之间的距离,必须得到相关字段的长度,再转换成相应的坐标长度,
怎么办?
2.在宽度没对好的情况下先试试动态控件生成问题,一切正常!可出现一个奇怪问题,
我第一次预览后(假如是3个字段),然后再次预览(2个字段),前次显示的东西还
上面!!!于是我用componentsCount来查检是不是生成之后的控件在预览之后还在上面,
但结果是0,证明动态生成的控件在显示完后就释放了。
为什么会这样??
 
你把代吗贴出来看看
 
1.
可以考虑使用DrawText这个API,把最后一个参数设为DT_CALCRECT,通过在QuickRep.QRPrinter.Canvas
上预先输出字符串的办法来计算指定字符串所占的宽度,而这个字符串可以用类似这样的方式简单得到:
StringOfChar('W', DataSet1.Fields[0].Size);仔细看看API的说明,注意把相应的Canvas的Font属性
设成与QRDBText等一致的,还要注意调整和设置QRLabel等的相关属性(如AutoSize等),多试试。
2.
如果你只用一个固定的(不是每次动态生成的)QuickRep,就肯定要在每次生成报表前把上面的
所有QRLabel等控件释放掉,做循环的时候用ControlCount和Controls,不要用ComponentCount和
Components。另外要考虑它们的容器到底是谁,可能还要做嵌套,根据你给出的描述,只对
QRSubDetail做Controls循环就可以了。不过为了通用和适应以后扩展的需要,最好做成嵌套的,
即从QuickRep开始,对每一级的容器类做循环释放操作,当然第二级(Band)容器类大多数情况下
是可以保留的(通过is TQRBand这样的语句来判断是否保留),因为它们的变动相对很少,但是如果
报表比较复杂还是会涉及到这类对象的释放和创建的。
 
procedure Tform1.ChoiceFieldPrint(aleft:integer;lab,field:String);
//aleft是为了控制字段的位置,lab是标题显示,field对应标题的字段
var
ppName:TQrLabel;
ppText:TQrDbtext;
begin
with form3 do
begin
//做一个显示标题如 姓名,性别等等
ppName:=TqrLabel.create(self);
ppName.Parent:=QRSubDetail1;
PPName.Caption:=lab;
ppName.Top:=10;
ppName.Left:=aleft;
ppName.Show;
//与上面标题对应的字段显示,显示所有记录
form3.QRSubDetail2.DataSet:=form1.ADOTable1;
ppText:=TqrDbtext.create(self);
PPText.Parent:=QrSubDetail2;
PpText.DataSet:=QrSubDEtail2.DataSet;
ppText.DataField:=field;
ppText.Left:=aleft;
pptext.Show;
end;
end;

这个便例子可以动态的生成一个对应字段的报表
假如button1.click是
choiceFieldPrint(10,'姓名','姓名');
choiceFieldPring(80,'出生年月','生日');
button2.click是
choiceFieldPrint(10,'姓名','姓名');
choiceFieldPrint(10,'性别','性别');
这样两个按钮都可以生成两列分别生成姓名和出生年月以及姓名和性别的报表。
问题是哪果我现在按BUTTON1之后,再按BUTTON2的话,前面的出生年月也在,也且位置
和‘性别’重合。
qrSubDetail是原来就有的,但上面没有任何控件。
 
?你没有释放那些DBText和Label,它们当然还在了,你在楼顶说的
>>“……证明动态生成的控件在显示完后就释放了。”
这个说法是不对的。试试在你创建报表前加一句:
with QRSubDetail1 do while ControlCount > 0 do Controls[0].Free;
另外,不知道你为什么用QRSubDetail而不用QRDetail呢?
 
呵呵,更正一下,我是指DetailBand
 
刚才试了一下,我使用预览之后,再使用 qrSubDetail1的contolcount检查一下,的
确控件是在上面。:(

但我清不掉!!

for i:=1 to QrSubDetail1.controlCount-1 do
QrSubDetail1.control.free;

清不掉!!!清到第 2个就报错!!!
 
呵呵,为什么没试试我给出的写法呢?
你的写法的确是很容易出现的一种错误,因为Free掉一个Control则ControlCount就要减1,
所以肯定会出现Index out of bounds这样的错误,做这样的清除一定要倒着来:)
用downto循环同时下标始终指向最后一个或0才行,不要想当然地指向“当前”这个。
再有你的循环不是从0开始的。其实我的写法是最简单的一种,Delphi的源码里就是这么做的。
 
再举个例子详细分析一下这种for循环,假设循环之前Controls为1,2,3,4,5,则:
(1,2,3,4,5)——初始状态
i=0;(2,3,4,5)
i=1;(2,4,5)
i=2;(2,4)
i=3;——报错,因为现在最大下标为1,此时的ControlCount实际变成了2

有一点要注意的是,for语句本身的循环中止条件只在第一次执行时计算一次,因此
尽管ControlCount在不断减小,但是这里的“for i = 0 to ControlCount - 1 do”
始终相当于“for i = 0 to 4 do”,这才会出现异常;而如果不是这样则不能完全释放。
 
呵呵,谢谢你·
的确如此!!:)

第二个问题搞定了,有一半分是你的。

但第一题:怎样得知这个表的结构信息,如我要得到某个字段的字节长度,然后转成标植!
 
发现你不太爱自己动手研究问题啊兄弟,方法其实我在第一帖里就说明了,你只要多试试就出来了。
字段长度取自TField.Size,访问某个字段则通过Fields[index]或FieldByName(name)或FieldDefs,
算宽度则可以随便找一个能用的Canvas(如Form的),用DrawText就可以得到了(我原来说可以用
QuickRep.QRPrinter的,但刚才在D6下试了一下不论在哪个事件里似乎都不可用了,D5下是可以的)
至于测试宽度用的字符串是可以用StringOfChar得出,不过第一个参数可能需要多试验几个,建议用'H'。
注意事项还是我在第一贴里指出的那几个,可能还有别的,你自己多考虑周全一些就行了。

不过用取字段长度的方法确定控件位置有个缺点,当报表上出现的列比较多的时候就比较明显了,
因为字段长度是它的最大容量,而一般数据库设计时都会为字段留有一定的富余,所以实际效果
会显示得相当稀疏。故此当字段较多时会出现显示不下的情况,如果是这样倒不如不用字段长度,
可以在INI里为每个字段指定一个合适的大小(仍以字节为单位即可),只要灵活读取处理设置
一样可以保证动态的效果,并且还可以利用这样的动态参数进行更多的报表界面控制。

多动动手,多想想,用QuickRep还是能做出不错的动态报表的。
 
http://www.delphibbs.com/delphibbs/dispq.asp?lid=1237069
http://www.delphibbs.com/delphibbs/dispq.asp?lid=1247545
 
接受答案了.
 
后退
顶部