自己定义一个记录类型,如:
PMyData = ^TMyData;
TMyData = record
ID: Integer;
Name: String;
end;
当然,你可以加入其它你所需要的属性,然后与TreeView的Data属性关联。
Delphi的帮助中有如下内容:
The following code defines a record type of TMyRec and a record pointer type of PMyRec.
type
PMyRec = ^TMyRec;
TMyRec = record
FName: string;
LName: string;
end;
Assuming these types are used, the following code adds a node to TreeView1 as the last sibling of a specified node. A TMyRec record is associated with the added item. The FName and LName fields are obtained from edit boxes Edit1 and Edit2. The Index parameter is obtained from edit box Edit3. The item is added only if the Index is a valid value.
procedure TForm1.Button1Click(Sender: TObject);
var
MyRecPtr: PMyRec;
TreeViewIndex: LongInt;
begin
New(MyRecPtr);
MyRecPtr^.FName := Edit1.Text;
MyRecPtr^.LName := Edit2.Text;
TreeViewIndex := StrToInt(Edit3.Text);
with TreeView1 do
begin
if Items.Count = 0 then
Items.AddObject(nil, 'Item' + IntToStr(TreeViewIndex), MyRecPtr)
else if (TreeViewIndex < Items.Count) and (TreeViewIndex >= 0) then
After an item containing a TMyRec record has been added, the following code retrieves the FName and LName values associated with the item and displays the values in a label.
procedure TForm1.Button2Click(Sender: TObject);
begin
Label1.Caption := PMyRec(TreeView1.Selected.Data)^.FName + ' ' +
PMyRec(TreeView1.Selected.Data)^.LName;
end;
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;
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;
procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode);
begin
Dispose(Node.Data);{释放节点数据内存}
end;
为Add1选单项的OnClick事件添加代码如下:
procedure TForm1.Add1Click(Sender: TObject);
var
p:pstr;
Tmpstr:string;
i:integer;
begin
try
StrToInt(Edit2.Text);
Tmpstr:=Edit2.Text;{注:在实用中,必须用更好的方法来产生ID}
except;
ShowMessage(′重新输入Edit2的内容′);
abort;
end;
with TreeView1 do
begin
new(p);
p^:=copy(Pstr(Selected.Data)^,4,3)+TmpStr;
Items.AddChildObject(Selected,Edit1.Text,p);
end;
with Table1 do{ 在数据库里添加记录 }
begin
Append;
FieldByName(′Text′).AsString:=Edit1.text;
FieldByName(′LongID′).AsString:=p^;
Post;
end;
TmpStr:=inttostr(strtoint(TmpStr)+1);
for i:=length(TmpStr) to 2 do TmpStr:=′0′+TmpStr;
Edit2.Text:=TmpStr;
end;
为Del1菜单项的OnClick事件添加代码如下:
procedure TForm1.Del1Click(Sender: TObject);
var
DelList:TStringList;
LongID,NSubLongID:string;
begin
DelList:=TStringList.create;
DelList.Sorted:=True;
DelList.Add(Pstr(TreeView1.Selected.Data)^);
while DelList.Count>0 do
begin
LongID:=DelList.Strings[0];
DelList.Delete(0);
Table1.SetKey;
Table1.FieldByName(′LongID′).AsString:=LongID;
if Table1.GotoKey then Table1.Delete;
if HasSubInDbf(TreeView1.Selected) then
begin
NSubLongID:=Table1.FieldByName(′LongID′).AsString;
while (copy(NSubLongID,1,3)=copy(LongID,4,3))and(not Table1.Eof) do
begin
dellist.Add(NSubLongId);
Table1.Next;
NSubLongId:=Table1.FieldByName(′LongID′).AsString;
end;
end;
end;
DelList.Free;
TreeView1.Items.Delete(TreeView1.Selected);
end;
为TreeView1的OnExpanding事件添加代码:
procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
var
TmpNode:TTreeNode;
NSubLongID:String;
p:Pstr;
bm:TBookMark;
begin
with Table1,TreeView1 do
begin
Items.BeginUpdate;
SetKey;
FieldByName(′LongID′).AsString:=Pstr(Node.Data)^;
if not GotoKey then Items.Delete(Node)
else
begin
TmpNode:=Node.GetFirstChild;
if (TmpNode.Text=′ ′)and(TmpNode.Data=nil) then
begin
TmpNode.Delete;
if HasSubInDbf(Node) then
begin
NSubLongID:=FieldByName(′LongID′).AsString;
while (copy(NSubLongID,1,3)=copy(Pstr(Node.Data)^,4,3))and(not Eof) do
begin
new(p);
p^:=FieldByName(′LongID′).AsString;
bm:=GetBookMark;
TmpNode:=Items.AddChildObject(Node,FieldByName(′Text′).AsString,p);
if HasSubInDbf(TmpNode) then Items.AddChildObject(TmpNode,′ ′,nil);
GotoBookMark(bm);
FreeBookMark(bm);
Next;
NSubLongId:=FieldByName(′LongID′).AsString;
end;
end;
end;
end;
Items.EndUpdate;
end;
end;
Delphi中树型控件的使用技巧
boymaster摘 要:Delphi中树型控件的使用技巧关键字:控件,使用技巧类 别:COM & ActiveX
我们都知道,开发者主要用Delphi来开发数据库管理软件,正因如此,树型控件的使用最好与数据库联系起来。Delphi提供了一个树型控件TTreeView,可以用来描述复杂的层次关系。树节点信息的存储和加载 常用的方法是用树控件的 LoadFromFile和SavetoFile方法,来实现树控件和文件之间的交互;或用Assign方法实现树控件和DBMemo,也就是和数据库间的交互。该方法的优点是编程相对简单,缺点是树控件的实际节点数可能会很大,对于“大树”,每次加载和存储的数据量会加大,将降低速度,增大系统开销,造成数据冗余。另一种方法,就是只在树上产生“看得见”的节点,没有专门记录全部树节点结构的文件或数据库字段,而将树节点结构分散在数据库的每一个记录中。 具体方法是:创建一个数据库,字段根据实际业务而定,其中必然有一个字段的信息将在树型控件的节点上显示,另外还要一个字段来保存节点的惟一标识号,该标识号由长度相等的两部分组成,前段表示当前节点的父节点号,后段表示当前节点的节点号,此标识号相当于一个“链表”,记录了树上节点的结构。该方法的优点:用户操作“大树”时,一般不会展开所有的节点,而只用到有限的一部分,同时只能从树根一层一层地展开,该法只在树上产生“看得见”的节点,所以,存储和加载“大树”的速度快,数据量小,系统开销和数据冗余较小。缺点:编程较复杂,但可以结合该方法编成一个新的树控件,将大大提高编程效率。值得注意的是,ID号必须惟一,所以在编程中如何合理产生ID尤为重要。数据库结构示例 创建一个数据库,为简化程序,我只创建两个数据库字段,定义如下: 字段名 类型 长度 text c 10 longid c 6 LongID字段实际上由两段组成,每一段3位,LongID只能表示1000条记录。将LongID定义为索引字段,存为c:\testtree\tree.dbf。编辑该DBF文件,新建一条记录,Text字段设为TOP,LongID字段设为“000”(3个“0”前为三个空格)。创建演示程序 在Form1上放置TreeView1、Table1、PopupMenu1、Edit1、Edit2。TreeView1的PopupMenu属性设为PopupMenu1;Table1的DataBaseName属性设为c:\testtree,TableName属性设为tree.dbf,IndexFieldNames属性设为LongID;为PopupMenu1加选单项Add1和Del1,Caption分别为Add和Del;Edit1用来输入新节点的Text属性值,Edit2用来输入新节点的3位ID号。存为c:\testtree\treeunit.pas和c:\testtree\testtree.dpr。 在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; 为TreeView1控件的OnDeletion事件添加代码,需要指出的是,不仅调用Delete方法可以触发OnDeletion事件,而且当树控件本身被释放前,也触发OnDeletion事件,所以,在此处加入dispose(node.data)会很“安全”: procedure TForm1.TreeView1Deletion(Sender: TObject; Node: TTreeNode); begin Dispose(Node.Data);{释放节点数据内存} end; 为Add1选单项的OnClick事件添加代码如下: procedure TForm1.Add1Click(Sender: TObject); var p:pstr; Tmpstr:string; i:integer; begin try StrToInt(Edit2.Text); Tmpstr:=Edit2.Text;{注:在实用中,必须用更好的方法来产生ID} except; ShowMessage(′重新输入Edit2的内容′); abort; end; with TreeView1 do begin new(p); p^:=copy(Pstr(Selected.Data)^,4,3)+TmpStr; Items.AddChildObject(Selected,Edit1.Text,p); end; with Table1 do{ 在数据库里添加记录 } begin Append; FieldByName(′Text′).AsString:=Edit1.text; FieldByName(′LongID′).AsString:=p^; Post; end; TmpStr:=inttostr(strtoint(TmpStr)+1); for i:=length(TmpStr) to 2 do TmpStr:=′0′+TmpStr; Edit2.Text:=TmpStr; end; 为Del1菜单项的OnClick事件添加代码如下: procedure TForm1.Del1Click(Sender: TObject); var DelList:TStringList; LongID,NSubLongID:string; begin DelList:=TStringList.create; DelList.Sorted:=True; DelList.Add(Pstr(TreeView1.Selected.Data)^); while DelList.Count>0 do begin LongID:=DelList.Strings[0]; DelList.Delete(0); Table1.SetKey; Table1.FieldByName(′LongID′).AsString:=LongID; if Table1.GotoKey then Table1.Delete; if HasSubInDbf(TreeView1.Selected) then begin NSubLongID:=Table1.FieldByName(′LongID′).AsString; while (copy(NSubLongID,1,3)=copy(LongID,4,3))and(not Table1.Eof) do begin dellist.Add(NSubLongId); Table1.Next; NSubLongId:=Table1.FieldByName(′LongID′).AsString; end; end; end; DelList.Free; TreeView1.Items.Delete(TreeView1.Selected); end; 为TreeView1的OnExpanding事件添加代码: procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean); var TmpNode:TTreeNode; NSubLongID:String; p:Pstr; bm:TBookMark; begin with Table1,TreeView1 do begin Items.BeginUpdate; SetKey; FieldByName(′LongID′).AsString:=Pstr(Node.Data)^; if not GotoKey then Items.Delete(Node) else begin TmpNode:=Node.GetFirstChild; if (TmpNode.Text=′ ′)and(TmpNode.Data=nil) then begin TmpNode.Delete; if HasSubInDbf(Node) then begin NSubLongID:=FieldByName(′LongID′).AsString; while (copy(NSubLongID,1,3)=copy(Pstr(Node.Data)^,4,3))and(not Eof) do begin new(p); p^:=FieldByName(′LongID′).AsString; bm:=GetBookMark; TmpNode:=Items.AddChildObject(Node,FieldByName(′Text′).AsString,p); if HasSubInDbf(TmpNode) then Items.AddChildObject(TmpNode,′ ′,nil); GotoBookMark(bm); FreeBookMark(bm); Next; NSubLongId:=FieldByName(′LongID′).AsString; end; end; end; end; Items.EndUpdate; end; end; 以上简要谈了谈数据库的树状显示的基本方法,另外,编辑树上节点的Text属性的同时对数据库进行修改、同一数据
做此类程序关键是你数据库表结构的设计如何???
如果按下面的结构设计肯定没有问题!
create table t_item
(fitemid int default 0 not null primary key, --主键
fnumber varchar(50) not null, --代码
fname varchar(50) not null, --名称
fparentid int default 0 not null, --父级fitemid
flevel int default 1 not null, --级次
fdetail int default 1 not null ---是否为明细
)
function TForm1.GiveNode(ID: integer): TTreeNode;
var i : integer;
begin
Result := nil;
if NodeList.Count = 0 then
Result := TreeView1.Items.GetFirstNode else
for i:=0 to NodeList.Count-1 do
if ID = PNodeTemp(NodeList.Items[i])^.TreeID then
Result := PNodeTemp(NodeList.Items[i])^.NodeTemp;
end;
procedure TForm1.LoadData;
var DataInput : PTreeData;
begin
with ADOQuery1 do begin
Close;
Sql.Clear;
Sql.Add('select treeid,parentid,treetext from treetab ');
Open;
end;
//TreeID := ADOQuery1.RecordCount;
DataList := TList.Create;
ADOQuery1.First;
while NOT ADOQuery1.Eof do begin
New(DataInput);
DataInput^.ParentID := ADOQuery1.FieldByName('parentid').AsInteger;
DataInput^.TreeID := ADOQuery1.FieldByName('treeid').AsInteger;
DataInput^.TreeText := ADOQuery1.FieldByName('treetext').AsString;
DataList.Add(DataInput);
ADOQuery1.Next;
end;
end; //将数据库中的数据全部提出来,保存到指针数组里;
procedure TForm1.ProduceTree;
var i,j : integer;
Temp : TTreeNode;
ANodeTemp : PNodeTemp;
begin
NodeList := TList.Create;
if TreeView1.Items.Count = 0 then begin
Temp := TreeView1.Items.AddChildObject(nil,PTreeData(DataList.Items[0])^.TreeText,DataList.Items[0]);
New(ANodeTemp);
ANodeTemp^.NodeTemp := Temp;
ANodeTemp^.TreeID := PTreeData(DataList.Items[0])^.TreeID;
NodeList.Add(ANodeTemp);
end;
for i:=0 to DataList.Count-2 do
for j:=i+1 to DataList.Count-1 do begin
New(ANodeTemp);
if PTreeData(DataList.Items[i])^.TreeID = PTreeData(DataList.Items[j])^.ParentID then begin
Temp := GiveNode( PTreeData(DataList.Items[i])^.TreeID );
ANodeTemp^.NodeTemp := TreeView1.Items.AddChildObject(Temp,PTreeData(DataList.Items[j])^.TreeText,DataList.Items[j]);
ANodeTemp^.TreeID := PTreeData(DataList.Items[j])^.TreeID;
PTreeData(DataList.Items[i])^.ImageID := 0;
//if ANodeTemp^.NodeTemp.getFirstChild = nil then PTreeData(DataList.Items[j])^.ImageID := 9 else
//PTreeData(DataList.Items[j])^.ImageID := 2;
NodeList.Add(ANodeTemp);
end else PTreeData(DataList.Items[j])^.ImageID := 1;
end;
NodeList.Clear;
DataList.Clear;
end;
procedure TForm1.N5Click(Sender: TObject);
var NewNode : PTreeData;
begin
if TreeView1.Selected = nil then exit;
New(NewNode);
Inc(TreeID);
NewNode^.TreeID := TreeID;
NewNode^.ParentID := PTreeData(TreeView1.Selected.Data)^.TreeID;
NewNode^.TreeText := '新增节点';
TreeView1.Items.AddChildObject(TreeView1.Selected,'新增节点',NewNode);
with ADOQuery1 do begin
Close;
Sql.Clear;
Sql.Add ('insert into treetab (treeid,parentid,treetext) values (:treeid,:parentid,:treetext)');
Parameters.ParamByName('treeid').Value := NewNode^.TreeID ;
Parameters.ParamByName('parentid').Value := NewNode^.ParentID;
Parameters.ParamByName('treetext').Value := '新增节点';
Execsql;
end;
end;
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的值相符。所以在实际应用中,让用户新增科目代码时,必须严格检查其规范性,只有完全符合事先定义的代码结构,才能添加入库。