楼主,给你这段代码,完全可以运行的。procedure TForm2.BitBtn1Click(Sender: TObject); var newid,oldid,jg_mc:string; idno:real; MyTreeNode1, MyTreeNode2: TTreeNode; begin //添加[到站油库名称]到树型目录框 TreeView1.Items.Clear;
with datamodule22.adoquery3 do begin close; sql.clear; sql.add('select * from inoilstation order by jgno'); open; first; idno:=0; if recordcount >0 then begin newid:=FieldValues['jgno']; oldid:=''; // MyTreeNode1 := TreeView1.Items.Add(nil, newid); while not eof do begin with datamodule22.adoquery2 do //根据NEWID查出表machinery中的机构名称给jg_mc变量 begin close; sql.clear; sql.add('select * from machinery where jgno=:j'); Parameters.ParamByName('j').Value:=newid; open; if recordcount >0 then jg_mc:=FieldValues['jgmc'] else begin showmessage('机构名称读不到!'); exit; end; end; if newid=oldid then begin TreeView1.Items.AddChild(MyTreeNode1,FieldValues['dzmc']); idno:=idno; end else begin idno:=idno+1; MyTreeNode1 := TreeView1.Items.Add(nil, jg_mc); // MyTreeNode1 := TreeView1.Items.Add(nil, newid); TreeView1.Items.AddChild(MyTreeNode1,FieldValues['dzmc']); end; datamodule22.adoquery3.Next; oldid:=newid; newid:=FieldValues['jgno']; end; end; end; end;
procedure Tfrm_gdzccggl.refresh_tree; var i,j:integer; t0,t,tt:ttreenode; fst,old_sydw,old_jhzt,old_xmxh:string; begin IF NOT (B_CX_JY OR B_CX_FJY) THEN EXIT; if rd_zcfl.ItemIndex<0 then begin messagebox(handle,'你没有足够的权限查看和维护本单位固定资产采购计划!','错误信息',mb_ok+mb_iconerror); exit; end; for i:=0 to componentcount-1 do begin if components[i].Tag>100 then (components[i] as tcontrol).enabled:=false; end; tree1.Items.BeginUpdate; tree1.Items.Clear; old_table:='-1'; fst:=copy(get_filter(0),5,200); if user_info.zcbz=1 then begin t0:=tree1.Items.add(nil,'总 厂'); t0.SelectedIndex:=0; t0.ImageIndex:=0; add_zc_child(t0); end else begin t:=tree1.Items.add(nil,user_info.dwmc); t.SelectedIndex:=0; t.ImageIndex:=0; end; q_exe.SQL.Text:='select distinct a.sydw sydw,nvl(a.jhzt,-1) jhzt,nvl(a.jhlb,''XZ'') jhlb ,B.DWXH from gdzccg_jhgl a,SYDWXXB B where '+fst+' AND A.SYDW=B.DWMC(+) order by B.DWXH,nvl(a.jhzt,-1)'; q_exe.Open; old_sydw:='-1'; while not q_exe.Eof do begin if old_sydw<>q_exe.FieldByName('sydw').AsString then begin old_sydw:=q_exe.FieldByName('sydw').AsString; old_jhzt:='-2'; t0:=tree1.Items.add(nil,old_sydw); t0.SelectedIndex:=0; t0.ImageIndex:=0; end; if old_jhzt<>q_exe.FieldByName('jhzt').AsString then begin old_jhzt:=q_exe.FieldByName('jhzt').AsString; tt:=tree1.Items.addchild(t0,'['+q_exe.fieldbyname('jhzt').asstring+']'+ ls_jhzt.strings[q_exe.fieldbyname('jhzt').asinteger+1]); tt.SelectedIndex:=1; tt.ImageIndex:=2; end; q_exe.Next; end; q_exe.close; tree1.Items.endUpdate; // search_near(0); end;
表结构可以采用两种方式: 1、编码方式,采用001001001的字符串,然后按照每级的长度切开,然后建树。这种方法最慢,而且树的层次数有限。 2、链式存储方式。结构是 节点编号、父节点编号、名称、编码... 这种方法是最好的解决方法。我详细说一下: 关键是两个编号字段,顶级接点的编号可以是-1或者其他特殊值(字段类型用数字或者字符串都可以)。比如树形是 A----B |--C |--D----E |--F那么数据库内容是: 节点编号 父节点编号 名称 1 -1 A 2 1 B 3 1 C 4 1 D 5 4 E 6 4 F 建立树的操作可以使用一个先根顺序建立各个节点(实际上是一个递归函数,广度优先),算法如下://建立一个记录,存储树节点的信息 PNodeData = ^TNodeData; TNodeData = record ID, ParentID: LongInt; Text: ShortString; end;//ParentTreeNode TreeView父节点 //ParentID 父节点编号 //TreeDataSet 存储该树的数据表、之前要打开 procedure BuildTree(ParentNode: TTreeNode; ParentID: Integer); var I: Integer; ANodeData: PNodeData; NewNode: TTreeNode; begin //过滤出该父节点下的子节点 TreeDataSet.Filter := Format('父节点编号 = %d', [ParentID]); TreeDataSet.Filtered := True;
for I := 0 to TreeDataSet.RecordCount - 1 do begin ANodeData := New(PNodeData); ANodeData^.ID := TreeDataSet.FieldValues['节点编号']; ANodeData^.ParentID := TreeDataSet.FieldValues['父节点编号']; ANodeData^.Text := TreeDataSet.FieldValues['名称']; NewNode := TreeView.AddChild(ParentNode, ANodeData^.Text); NewNode.Data := ANodeData; Next; end; //递归调用本函数,为各个子节点生成树 //很明显,如果当前节点没有子节点,那么就是递归调用的结束条件了 //这里的判断是检查第一次调用传进来的参数,如果是nil就用TreeView的属性来访问 if Assigned(ParentNode) then begin for I := 0 to ParentNode.Count - 1 do BuildTree(ParentNode.Item[I], PNodeData(ParentNode.Item[I].Data)^.ID); else begin for I := 0 to TreeView.Items.Count - 1 do BuildTree(TreeView.Items[I], PNodeData(TreeView.Items[I].Data)^.ID); end; end; 初始的调用方法是 TreeDataSet.Open;BuildTree(TreeView.Add(nil, '树根'), -1);//或者 BuildTree(nil, -1);TreeDataSet.Close; TreeDataSet.Filter := ''; TreeDataSet.Filtered := False;
对了,上面那段代码在程序退出的时候要记得释放每个树节点的data指针占用的内存,DELPHI不会负责释放那些空间的。如果不做,将导致内存泄露。 再多加一点注释: //把数据库中查询出来的每个节点加入到ParentNode下 for I := 0 to TreeDataSet.RecordCount - 1 do begin //为节点分配一个记录,记录节点的ID等信息 ANodeData := New(PNodeData); //记录该节点的各个属性,后面要用到 ANodeData^.ID := TreeDataSet.FieldValues['节点编号']; ANodeData^.ParentID := TreeDataSet.FieldValues['父节点编号']; ANodeData^.Text := TreeDataSet.FieldValues['名称']; NewNode := TreeView.AddChild(ParentNode, ANodeData^.Text); //把该节点的DATA指针指向这个记录 NewNode.Data := ANodeData; //继续添加下一个节点 Next; end; 如果不执行下面的代码,那么就只生成一级的树,否则就生成全部的树。按照你的问题的要求,去掉下面的代码,在每次点一个+号的时候调用BuildTree就可以了。 if Assigned(ParentNode) then begin for I := 0 to ParentNode.Count - 1 do BuildTree(ParentNode.Item[I], PNodeData(ParentNode.Item[I].Data)^.ID); else begin for I := 0 to TreeView.Items.Count - 1 do BuildTree(TreeView.Items[I], PNodeData(TreeView.Items[I].Data)^.ID); end;
var
newid,oldid,jg_mc:string;
idno:real;
MyTreeNode1, MyTreeNode2: TTreeNode;
begin //添加[到站油库名称]到树型目录框
TreeView1.Items.Clear;
with datamodule22.adoquery3 do
begin
close;
sql.clear;
sql.add('select * from inoilstation order by jgno');
open;
first;
idno:=0;
if recordcount >0 then
begin
newid:=FieldValues['jgno'];
oldid:='';
// MyTreeNode1 := TreeView1.Items.Add(nil, newid);
while not eof do
begin
with datamodule22.adoquery2 do //根据NEWID查出表machinery中的机构名称给jg_mc变量
begin
close;
sql.clear;
sql.add('select * from machinery where jgno=:j');
Parameters.ParamByName('j').Value:=newid;
open;
if recordcount >0 then jg_mc:=FieldValues['jgmc']
else
begin
showmessage('机构名称读不到!');
exit;
end;
end;
if newid=oldid then
begin
TreeView1.Items.AddChild(MyTreeNode1,FieldValues['dzmc']);
idno:=idno;
end
else
begin
idno:=idno+1;
MyTreeNode1 := TreeView1.Items.Add(nil, jg_mc);
// MyTreeNode1 := TreeView1.Items.Add(nil, newid);
TreeView1.Items.AddChild(MyTreeNode1,FieldValues['dzmc']);
end;
datamodule22.adoquery3.Next;
oldid:=newid;
newid:=FieldValues['jgno'];
end;
end;
end;
end;
var i,j:integer;
t0,t,tt:ttreenode;
fst,old_sydw,old_jhzt,old_xmxh:string;
begin
IF NOT (B_CX_JY OR B_CX_FJY) THEN EXIT;
if rd_zcfl.ItemIndex<0 then
begin
messagebox(handle,'你没有足够的权限查看和维护本单位固定资产采购计划!','错误信息',mb_ok+mb_iconerror);
exit;
end;
for i:=0 to componentcount-1 do
begin
if components[i].Tag>100 then
(components[i] as tcontrol).enabled:=false;
end;
tree1.Items.BeginUpdate;
tree1.Items.Clear;
old_table:='-1';
fst:=copy(get_filter(0),5,200);
if user_info.zcbz=1 then
begin
t0:=tree1.Items.add(nil,'总 厂');
t0.SelectedIndex:=0;
t0.ImageIndex:=0;
add_zc_child(t0);
end
else
begin
t:=tree1.Items.add(nil,user_info.dwmc);
t.SelectedIndex:=0;
t.ImageIndex:=0;
end;
q_exe.SQL.Text:='select distinct a.sydw sydw,nvl(a.jhzt,-1) jhzt,nvl(a.jhlb,''XZ'') jhlb ,B.DWXH from gdzccg_jhgl a,SYDWXXB B where '+fst+' AND A.SYDW=B.DWMC(+) order by B.DWXH,nvl(a.jhzt,-1)';
q_exe.Open;
old_sydw:='-1';
while not q_exe.Eof do
begin
if old_sydw<>q_exe.FieldByName('sydw').AsString then
begin
old_sydw:=q_exe.FieldByName('sydw').AsString;
old_jhzt:='-2';
t0:=tree1.Items.add(nil,old_sydw);
t0.SelectedIndex:=0;
t0.ImageIndex:=0;
end;
if old_jhzt<>q_exe.FieldByName('jhzt').AsString then
begin
old_jhzt:=q_exe.FieldByName('jhzt').AsString;
tt:=tree1.Items.addchild(t0,'['+q_exe.fieldbyname('jhzt').asstring+']'+
ls_jhzt.strings[q_exe.fieldbyname('jhzt').asinteger+1]);
tt.SelectedIndex:=1;
tt.ImageIndex:=2;
end;
q_exe.Next;
end;
q_exe.close;
tree1.Items.endUpdate; // search_near(0);
end;
table(id,name,parent_id)(当然这不是唯一的结构)
根结点的id一般为最小值。当然树的层次是固定的的话。也可以设计多个表如下:
corp(corp_id,corp_name);
department(corp_id,dpt_id,dpt_name);
office(dpt_id,office_id,office_name);
employee(office_id,emp_id,emp_name);
corp代表公司信息表,
department代表部门信息表,
office代表办公室表,
employee代表职工表,
一个公司有多个部门,一个部门有多个办公室,一个办公室有多个职工。
当初始化时只画出公司一层就行了。并增加一个空部门,当要onExpanded里画出部门树。。
CREATE TABLE
创建新表。语法
CREATE TABLE
[ database_name.[ owner ] .| owner.] table_name
( { < column_definition >
| column_name AS computed_column_expression
| < table_constraint > ::= [ CONSTRAINT constraint_name ] } | [ { PRIMARY KEY | UNIQUE } [ ,...n ]
) [ ON { filegroup | DEFAULT } ]
[ TEXTIMAGE_ON { filegroup | DEFAULT } ] < column_definition > ::= { column_name data_type }
[ COLLATE < collation_name > ]
[ [ DEFAULT constant_expression ]
| [ IDENTITY [ ( seed , increment ) [ NOT FOR REPLICATION ] ] ]
]
[ ROWGUIDCOL]
[ < column_constraint > ] [ ...n ] < column_constraint > ::= [ CONSTRAINT constraint_name ]
{ [ NULL | NOT NULL ]
| [ { PRIMARY KEY | UNIQUE }
[ CLUSTERED | NONCLUSTERED ]
[ WITH FILLFACTOR = fillfactor ]
[ON {filegroup | DEFAULT} ] ]
]
| [ [ FOREIGN KEY ]
REFERENCES ref_table [ ( ref_column ) ]
[ ON DELETE { CASCADE | NO ACTION } ]
[ ON UPDATE { CASCADE | NO ACTION } ]
[ NOT FOR REPLICATION ]
]
| CHECK [ NOT FOR REPLICATION ]
( logical_expression )
} < table_constraint > ::= [ CONSTRAINT constraint_name ]
{ [ { PRIMARY KEY | UNIQUE }
[ CLUSTERED | NONCLUSTERED ]
{ ( column [ ASC | DESC ] [ ,...n ] ) }
[ WITH FILLFACTOR = fillfactor ]
[ ON { filegroup | DEFAULT } ]
]
| FOREIGN KEY
[ ( column [ ,...n ] ) ]
REFERENCES ref_table [ ( ref_column [ ,...n ] ) ]
[ ON DELETE { CASCADE | NO ACTION } ]
[ ON UPDATE { CASCADE | NO ACTION } ]
[ NOT FOR REPLICATION ]
| CHECK [ NOT FOR REPLICATION ]
( search_conditions )
} 抄的嘎嘎,自己翻SQL SERVER的帮助去!
CREATE TABLE [dbo].[WAREOUTH] (
[period] [char] (6) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[noteno] [char] (14) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[notedate] [datetime] NULL ,
[operant] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[checkman] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[houseman] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[custno] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[PZDate] [datetime] NULL ,
[totalcurr] [money] NULL ,
[PayNo] [char] (3) COLLATE Chinese_PRC_CI_AS NULL ,
[houseno] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[InvoiceNo] [char] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[paycurr] [money] NULL ,
[type0] [char] (2) COLLATE Chinese_PRC_CI_AS NULL ,
[dptno] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[printman] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[tag2] [char] (1) COLLATE Chinese_PRC_CI_AS NULL ,
[tag1] [int] NULL ,
[noteno1] [char] (14) COLLATE Chinese_PRC_CI_AS NULL ,
[manager] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[postil] [char] (60) COLLATE Chinese_PRC_CI_AS NULL ,
[saleman] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[re0] [char] (100) COLLATE Chinese_PRC_CI_AS NULL ,
[Check1] [int] NULL ,
[Check2] [int] NULL ,
[Check3] [int] NULL ,
[Man1] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[NoteType] [int] NULL ,
[PZNo] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[InvoiceDate] [datetime] NULL ,
[Printed] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[Tag3] [smallint] NULL ,
[moddate] [datetime] NULL ,
[modperiod] [char] (6) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GOALTER TABLE [dbo].[WAREOUTH] WITH NOCHECK ADD
CONSTRAINT [WAREOUTHPRIMARYKEY1] PRIMARY KEY CLUSTERED
(
[noteno]
) ON [PRIMARY]
GO
在SQL SERVER当中手动创建一个表,然后生成其SQL脚本就可以看到。
1、编码方式,采用001001001的字符串,然后按照每级的长度切开,然后建树。这种方法最慢,而且树的层次数有限。
2、链式存储方式。结构是 节点编号、父节点编号、名称、编码... 这种方法是最好的解决方法。我详细说一下:
关键是两个编号字段,顶级接点的编号可以是-1或者其他特殊值(字段类型用数字或者字符串都可以)。比如树形是
A----B
|--C
|--D----E
|--F那么数据库内容是:
节点编号 父节点编号 名称
1 -1 A
2 1 B
3 1 C
4 1 D
5 4 E
6 4 F
建立树的操作可以使用一个先根顺序建立各个节点(实际上是一个递归函数,广度优先),算法如下://建立一个记录,存储树节点的信息
PNodeData = ^TNodeData;
TNodeData = record
ID, ParentID: LongInt;
Text: ShortString;
end;//ParentTreeNode TreeView父节点
//ParentID 父节点编号
//TreeDataSet 存储该树的数据表、之前要打开
procedure BuildTree(ParentNode: TTreeNode; ParentID: Integer);
var
I: Integer;
ANodeData: PNodeData;
NewNode: TTreeNode;
begin
//过滤出该父节点下的子节点
TreeDataSet.Filter := Format('父节点编号 = %d', [ParentID]);
TreeDataSet.Filtered := True;
for I := 0 to TreeDataSet.RecordCount - 1 do
begin
ANodeData := New(PNodeData);
ANodeData^.ID := TreeDataSet.FieldValues['节点编号'];
ANodeData^.ParentID := TreeDataSet.FieldValues['父节点编号'];
ANodeData^.Text := TreeDataSet.FieldValues['名称'];
NewNode := TreeView.AddChild(ParentNode, ANodeData^.Text);
NewNode.Data := ANodeData;
Next;
end; //递归调用本函数,为各个子节点生成树
//很明显,如果当前节点没有子节点,那么就是递归调用的结束条件了
//这里的判断是检查第一次调用传进来的参数,如果是nil就用TreeView的属性来访问
if Assigned(ParentNode) then
begin
for I := 0 to ParentNode.Count - 1 do
BuildTree(ParentNode.Item[I], PNodeData(ParentNode.Item[I].Data)^.ID);
else begin
for I := 0 to TreeView.Items.Count - 1 do
BuildTree(TreeView.Items[I], PNodeData(TreeView.Items[I].Data)^.ID);
end;
end;
初始的调用方法是
TreeDataSet.Open;BuildTree(TreeView.Add(nil, '树根'), -1);//或者 BuildTree(nil, -1);TreeDataSet.Close;
TreeDataSet.Filter := '';
TreeDataSet.Filtered := False;
再多加一点注释:
//把数据库中查询出来的每个节点加入到ParentNode下
for I := 0 to TreeDataSet.RecordCount - 1 do
begin
//为节点分配一个记录,记录节点的ID等信息
ANodeData := New(PNodeData);
//记录该节点的各个属性,后面要用到
ANodeData^.ID := TreeDataSet.FieldValues['节点编号'];
ANodeData^.ParentID := TreeDataSet.FieldValues['父节点编号'];
ANodeData^.Text := TreeDataSet.FieldValues['名称'];
NewNode := TreeView.AddChild(ParentNode, ANodeData^.Text); //把该节点的DATA指针指向这个记录
NewNode.Data := ANodeData; //继续添加下一个节点
Next;
end;
如果不执行下面的代码,那么就只生成一级的树,否则就生成全部的树。按照你的问题的要求,去掉下面的代码,在每次点一个+号的时候调用BuildTree就可以了。 if Assigned(ParentNode) then
begin
for I := 0 to ParentNode.Count - 1 do
BuildTree(ParentNode.Item[I], PNodeData(ParentNode.Item[I].Data)^.ID);
else begin
for I := 0 to TreeView.Items.Count - 1 do
BuildTree(TreeView.Items[I], PNodeData(TreeView.Items[I].Data)^.ID);
end;