---- 在很多常见的财务软件中,科目代码一般都用树型结构来显示。要实现这一点,通常的做法是用多个(嵌套)循环,甚至递归等算法,将科目表中的代码"织"成树,但这样不但算法复杂,而且执行效率低。本人在实际的开发应用中,摸索出一种简单高效的算法,在此和盆托出,只在抛砖引玉,找出最佳解决方案。下面介绍在delphi中的实现方法。
一.表结构 ---- 首先建立如下结构的数据表code.db,并输入一些测试数据: 字段名 类型 长度 说明 acode 字符型 20 科目代码 aname 字符型 30 科目代码名称 ...... ...... ...... ...... 表(一)
---- 其中,科目代码acode的数据类型一定要字符型(一定),长度按具体要求而定,假如要支持六级编码,且代码结构是"3-2-2-2-2-2",则该字段的长度不小于18,而其他字段则不作要求 。另外,要为字段acode建一索引(切记),因为要用它来排序。 二.编写程序 ---- 1.新建一project:codetree.drp,主窗体命名为frmmain,单元存为main.pas。在frmmain上添加一ttreeview控件,命名为tvecode,一个timagelist,命名为imgicon,并装入三个icon和bmp,最后添加一ttable控件,命名tblcode。 frmmain和各控件的属性按表(二)设置: 组件 属性 设置 frmmain caption '科目代码' font 宋体 9号 borderstyle bsdialog tvwcode images imgicon readonly true imgicon imagelist 装入三个图标 btnclose caption 关闭(c) 表(二)
---- 2. 单元main.pas的完整源代码如下: unit main;
interface
uses windows, messages, sysutils, classes, graphics, controls, forms, dialogs, db, dbtables, comctrls, imglist, stdctrls;
type tform1 = class(tform) tvwcode: ttreeview; tblcode: ttable; imagelist1: timagelist; btnclose: tbutton; procedure formcreate(sender: tobject); procedure btncloseclick(sender: tobject); private { private declarations } function loadcode(crtbl:tdbdataset):integer; function getlevel(sformat,scode:string):integer; public { public declarations } end;
var form1: tform1;
const scodeformat = '322222'; //科目代码结构 sfirstnodetxt = '科目代码'; //首节点显示的文字
implementation
{$r *.dfm} //以下函数是本文的重点部分, 其主要功能是用一循环将code.db表中的 //科目代码和科目代码名称显示出来 function tform1.loadcode(crtbl:tdbdataset):integer; var nowid,sname,showtxt:string; i,level:integer; mynode:array[0..6]of ttreenode; //保存各级节点,最长支持6级(重点) begin screen.cursor:=crhourglass; level:=0; with crtbl do begin try if not active then open; first; tvwcode.items.clear; //以下是增加第一项 mynode[level]:=tvwcode.items.add (tvwcode.topitem,sfirstnodetxt); mynode[level].imageindex:=0; mynode[level].selectedindex:=0; //以上是增加第一项 while not eof do begin nowid:=trim(fieldbyname('acode').asstring); showtxt:=nowid+' '+fieldbyname('aname').asstring; level:=getlevel(scodeformat,nowid); //返回代码的级数 //以下是增加子项 //以下用上一级节点为父节点添加子节点 if level> 0 then//确保代码符合标准 begin mynode[level]:=tvwcode.items.addchild (mynode[level-1],showtxt); mynode[level].imageindex:=1; mynode[level].selectedindex:=2; end; //以上是增加子项 next; end; finally close; end; end; mynode[0].expand(false);//将首节点展开 screen.cursor:=crdefault; end; //以上函数将code.db表中的科目代码和科目代码名称显示出来
//下面函数的功能是返回一代码的级数, 参数sformat传递科目代码结构; //参数scode传递某一科目代码 function tform1.getlevel (sformat,scode:string):integer; var i,level,ilen:integer; begin level:=-1;//如果代码不符合标准,则返回-1 ilen:=0; if (sformat< > '')and(scode< > '')then for i:=1 to length(sformat) do begin ilen:=ilen+strtoint(sformat[i]); if length(scode)=ilen then begin level:=i; break; end; end; result:=level; end; //上面函数的功能是返回一代码的级数
procedure tform1.formcreate(sender: tobject); begin with tblcode do begin databasename:=paramstr(1); //使tblcode的databasename指向应用程序所在的路径 tablename:='code.db'; //指向数据表code.db open; indexfieldnames:='acode'; //按字段acode排序(不要漏掉) end; loadcode(tblcode); end;
procedure tform1.btncloseclick(sender: tobject); begin close; end;
end.
---- 其中,常量scodeformat是科目代码的代码结构,其定义的规则一定要和数据表code..db中的字段acode的值相符。所以在实际应用中,让用户新增科目代码时,必须严格检查其规范性,只有完全符合事先定义的代码结构,才能添加入库。 ---- 函数getlevel是求某一科目代码的级数,例如,有一科目代码"10102",在代码结构是"322222"的情况下,调用函数getlevel('322222','10102')将返回整数2 。
---- 当然,本文的核心是loadcode函数,该函数用了一个循环来遍历数据表code.db的所有记录,将字段acode和aname的内容按层次显示出来。而在该函数中定义的二维数组mynode[0..6],则显得优为重要,在这里,它作用类似于递归中的栈。因为在ttreeview添加子节点的方法addchild(node: ttreenode; const s: string)中,要为其指定一父节点作为参数,而父节点的代码级数一定是要添加节点的代码级数减1,所以只要用一数组来动态保存和指定父节点就成功了。
三.运行结果 ---- 好了,现在把code.db复制到和可执行文件相同的目录下,按下f9键编译运行,本人运行的效果如图(一)。只要在以上的基础上加以完善,如增加维护功能,就可搬到实际应用中了。当然,本算法不单能用在科目代码上,其他类似的树型结构都能奏效。本人就已将此算法应用于[科目代码]、[物料清单(bom)]、[库房管理]和[物料主文件]等多个模块中,取得令人满意的效果。 ---- 以上程序在中文windows 9x、delphi 4 c/s环境下编译通过。
|