怎样自动创建分类树形表(节点信息已存在数据库中,创建时要求从数据库中提取)?(100分)

  • 主题发起人 主题发起人 tonky
  • 开始时间 开始时间
T

tonky

Unregistered / Unconfirmed
GUEST, unregistred user!
怎样自动创建分类树形表,节点信息已存在数据库中,树形表创建时要求从数据库中
提取节点内容,我的目的是想创建一个无限分类树形表,节点信息要求保存在数据库中,
诚心企盼各位高手指点迷津,我会双手献出分100分以上。
 
用treeview.
  具体方法是:创建一个数据库,字段根据实际业务而定,其中必然有一个字段的信息将
在树型控件的节点上显示,另外还要一个字段来保存节点的惟一标识号,该标识号由长
度相等的两部分组成,前段表示当前节点的父节点号,后段表示当前节点的节点号,
此标识号相当于一个“链表”,记录了树上节点的结构。该方法的优点:用户操作
“大树”时,一般不会展开所有的节点,而只用到有限的一部分,同时只能从树根
一层一层地展开,该法只在树上产生“看得见”的节点,所以,存储和加载“大树”
的速度快,数据量小,系统开销和数据冗余较小。缺点:编程较复杂,但可以结合该
方法编成一个新的树控件,将大大提高编程效率。值得注意的是,ID号必须惟一,
所以在编程中如何合理产生ID尤为重要。
  数据库结构示例
  创建一个数据库,为简化程序,我只创建两个数据库字段,定义如下:
字段名 类型 长度
text c 10
longid c 6
 LongID字段实际上由两段组成,每一段3位,LongID只能表示1000条记录。
将LongID定义为索引字段。给数据表新建一条记录,Text字段设为TOP,LongID字段
设为“000”(3个“0”前为三个空格)。 创建演示程序
  在Form1上放置TreeView1、Table1、TableName属性设为tree.dbf,IndexFieldNames
属性设为LongID;
 在treeunit.pas的Type关键字后加入一行:Pstr:^string;{Pstr为字符串指针}
  为Form1的OnCreate事件添加代码:
  procedure TForm1.FormCreate(Sender: TObject);
 var p:Pstr;Node:TTreeNode;
  begin
   with Table1,Treeview1 do
   begin
   open;
   first;
   new(p);{为指针p分配内存}
   p^:=FieldByName(′LongID′).AsString;
   Node:=Items.AddChildObject(nil,FieldByName(′Text′).AsString,p);
   if HasSubInDbf(Node) then Items.AddChildObject(Node,′ ′,nil);{有子节点则加一个空子节点}
   end;
  end;
  HasSubInDbf为自定义函数,自变量为Node,检查节点Node有无子节点,有则返回True,反之返回False,并在TForm1的类定义里加入原型声明(其它自定义函数的原型也在TForm1的类定义里声明,不另作解释),函数代码如下:
  function TForm1.HasSubInDbf(Node:TTreeNode):Boolean;
  begin
   with Table1 do
   begin
   Table1.FindNearest([copy(Pstr(Node.Data)^,4,3)+′000′]);
   result:=copy(FieldByName(′LongID′).AsString,1,3)=copy(Pstr(Node.Data)^,4,3);{如数据库里当前记录的LongID字段内容的前3位和节点Node的Data的后3位相同,则Node应该有子节点}
   end;
  end;
 
我也想知道
 
用DBTreeView

unit DBTrees;

interface

uses ComCtrls, DB, Classes, Sysutils;

type

TDBTreeView=class(TTreeView)
private
fTable:TDataSet;
fId,fParentId,fName:string;
function AddItem(var ParentIDList:TStringList):TTreeNode;
procedure GetInitParentID(var parentIdList:TStringList);
public
function GetId:integer;
procedure BuildTree;
procedure ClearTree;
function FindItem(Id:integer):TTreeNode;
function SelectItem(Id:integer):TTreeNode;
published
property FieldID:string Read fID Write fID;
property FieldParentID:string Read fParentId Write fParentId;
property FieldName:string Read fName Write fName;
property DataSource:TDataSet Read fTable Write fTable;
property ActiveId:integer Read GetId;
end;

procedure Register;

implementation

function TDBTreeView.FindItem(Id:integer):TTreeNode;
var
i:integer;
begin
Result:=nil;
For i:=0 to Items.Count-1 do
begin
if integer(Items.Data) = Id then
begin
Result:=Items;
Exit;
end;
end;
end;

function TDBTreeView.GetId:integer;
begin
If Selected=nil then Result:= -1
else Result:=integer(Selected.Data);
end;

procedure TDBTreeView.GetInitParentID(var parentIdList:TStringList);
var
childIdList:TStringList;
i:integer;
begin
childIDList := TStringList.Create;
try
parentIDList.Clear;

fTable.Filtered := false;
fTable.First;

While not fTable.Eof do
begin
parentIdList.add(fTable.FieldByName(fParentId).AsString);

if fTable.FieldByName(fParentId).AsString <> fTable.FieldByName(fId).AsString then
childIdList.add(fTable.FieldByName(fId).AsString);

fTable.next;
end;

for i := parentIdList.Count - 1 downto 0 do
begin
if childIdList.IndexOf(parentIdList) <> -1 then
parentIdList.Delete(i);
end;

finally
childIdList.Free;
end;
end;

function TDBTreeView.AddItem(var ParentIDList:TStringList):TTreeNode;
var
CurrentItem:TTreeNode;
Name:string;
CurId:integer;
begin
CurrentItem:=FindItem(fTable.FieldByName(fId).AsInteger);

//已经安装
If CurrentItem <> nil then
begin
Result := nil;
Exit;
end;

//父节点还未安装
if ParentIdList.Indexof(fTable.FieldByName(fParentID).AsString) = -1 then
begin
Result := nil;
exit;
end;

Name:=fTable.FieldByName(fName).AsString;
CurId :=fTable.FieldByName(fID).Asinteger;

//找到父节点,装在父节点下面
CurrentItem := FindItem(fTable.FieldByName(fParentID).Asinteger);
Result:=Items.AddChildObject(CurrentItem, Name, Pointer(CurID));

ParentIdList.Add(IntToStr(CurID));
end;

procedure TDBTreeView.BuildTree;
var
ChangeEvent:TTVChangedEvent;
initParentIdList:TStringList;
HasChanged:boolean;
begin
If (fTable=nil) or (not fTable.Active) then Exit;
ChangeEvent:=OnChange;
OnChange:=nil;
Items.BeginUpdate;
try
initParentIdList := TStringList.Create;
try
GetInitParentID(initParentIdList);
ClearTree;
HasChanged := true;

while HasChanged do
begin
HasChanged := false;
fTable.Filtered := false;
fTable.First;
While not fTable.Eof do
begin
if AddItem(initParentIdList) <> nil then
HasChanged := true;
fTable.Next;
end;
end;
finally
initParentIdList.Free;
end;
finally
Items.EndUpdate;
OnChange:=ChangeEvent;
end;
end;

procedure TDBTreeView.ClearTree;
begin
Items.BeginUpdate;
While Items.Count>0 do Items.Delete(Items[0]);
Items.EndUpdate;
end;

procedure Register;
begin
RegisterComponents('QuickMail',[TDBTreeView]);
end;

function TDBTreeView.SelectItem(Id: integer): TTreeNode;
var
aNode:TTreeNode;
begin
result := FindItem(Id);
if result = nil then exit;
aNode := result.parent;
while aNode <> nil do
begin
aNode.Expand(false);
aNode := aNode.Parent;
end;
result.Selected := true;
end;

end.





 
用树型结构表示科目代码的一种高效算法
松本电工实业有限公司电脑部
舒嵩嵩
---- 在很多常见的财务软件中,科目代码一般都用树型结构来显示。要实现这一点,
通常的做法是用多个(嵌套)循环,甚至递归等算法,将科目表中的代码"织"成树,
但这样不但算法复杂,而且执行效率低。本人在实际的开发应用中,摸索出一种简
单高效的算法,在此和盆托出,只在抛砖引玉,找出最佳解决方案。下面介绍在
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);
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环境下编译通过。

摘自计算机世界日报
 
多人接受答案了。
 
后退
顶部