delphi中TreeView控件使用
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
DELPHI中利用TreeView控件建立目录树
2000-06-26 00:00:00·-·中国计算机报社
p>Rainbow的话:关于TreeView的使用,还可以参看:联合使用TreeView 组件
TreeView是一个显示树型结构的控件,通过它能够方便地管理和显示具有层次结构的信息,是Windows应用程序的基本控件之一。
DELPHI虽然具有比较强大的文件管理功能,提供了多个用于文件管理的标准控件,如DriveComboBox、DirectoryListBox、FileListBox等,通过设置它们的属性,使其建立起联系,甚至不用编写一行程序,我们就可以实现在不同的目录之间进行切换,然而这样的目录切换只适用于进行文件的查找定位,而不能方便地进行目录的浏览,例如我们要从c:\windows目录转到c:\program files目录,就必须返回到根目录才能进行切换,而不能象Windows资源管理器那样任意地在不同的目录之间进行浏览与切换。
要实现在不同目录之间任意切换和浏览,还是需要使用TreeView控件,以下程序就利用DELPHI的TreeView控件来建立目录树。
在该程序中采用的各部件以及界面设计如下图所示:
各部件的主要属性设置如下:
部件属性属性值
form name
caption
form1 ‘目录浏览’
drivecommbobox name visible drivecommbobox1 false
filelistbox name visible filetype filelistbox1 false fddirectory
imagelist name imagelist1
treeview name images
该程序利用DriveCommboBox控件来获得系统具有的驱动器,并以此作为目录树的最上层,利用FileListBox控件,通过设置其Filetype属性为fdDirectory,可以获得所需的子目录,在TreeView控件的OnExpanding事件中将得到的子目录加到该控件的某一节点下。
整个程序的源代码如下:
unit main;
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, FileCtrl, ComCtrls, ImgList;
type
TForm1 = class(TForm)
DirTreeView: TTreeView;
FileListBox1: TFileListBox;
DriveComboBox1: TDriveComboBox;
ImageList1: TImageList;
procedure FormCreate(Sender: TObject);
procedure DirTreeViewExpanding(Sender: TObject; Node: TTreeNode;var AllowExpansion: Boolean);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure (Sender: TObject);
var
FirstNode,DirNode : TTreeNode;
ItemCount,Index:integer;
Itemstr:string;
begin
ItemCount:= and (itemstr 〈〉 ..) then
begin
DirNode := );
:=true;
:= 0;
:= 1;
icount:=icount+1;
end;
if icount = 0 then
:= false;
end;
end;
end;
end.
程序的运行效果如图所示:我们可以展开目录树中的任何一个节点,并且可以在任意节点之间切换,就象我们在Windows资源管理器中所作的那样,而不需要逐级回退之后才能进行切换。
•
•
•
•
•
•
•
•
•
Delphi中根据分类数据生成树形结构的最优方法
很多系统都有类似于如下的表结构(table1):
ID Name ParentID
---------------------------------------------------------
001 电子类0
002 金属类 0
003 电容电子 001
004 电阻电子 001
005 有色金属002
而且大家都习惯于用树(TreeView)来显示,这样就可以很好的显示整个表的分类情况。
但如果数据量多时会造成树的生成比较慢,特别是用递归来实现时要访问数据库的次数很多(根据层数),用在三层中效果更加显。
在此提供一个好的方法来生成树形结构。
这个算法只访问一次数据库,具体的实现如下:
1、一次性从数据库中取出所有的数据,并按照ParentID字段进行排序,这样就保证每一条数据的父节点都在它的前面。
2、取出第一条数据画到树中,在添加到树中时先找到这条数据的父节点,如果没有则将此记录直接作为树的第一级节点
3、如果还有数据,则取出来执行第2步,直到没有数据为止。
程序实现:
本程序将用一个stlID的TStringList变量来存放对应树中每一个节点的ID值,用FindParent函数来父节点。
function FindParent(ID:String):TTreeNode;
var
i:Integer;
begin
result:=nil;
for i:= downto 0 do
if [i]=ID then
begin
result:=[i];
break;
end;
end;
sString),('Name').AsString);
('ID').AsString);如何初始化一个TreeView?
弄一个窗口,放上一个TreeView和一个Button,分别取名为TV1和Btn1。
如果需要
在每个节点前有个图,请在窗口上放上一个ImageList,取名为ImageList1,双击
它,加入六个图标。
还要记得记得将TV1的Images属性改为ImageList1噢。
双击按
钮Btn1,在里面填入以下代码,然后按F9运行,点击Btn1就可以看到效果了。
procedure (Sender: TObject);
Const
MyDocDir = 'C:\My Documents';
PersonDir = '3hSoft';
Var
I : Word;
SubNodeName : array [1..5] of ShortString; RootNode, SubNode : TTreeNode;
P : PString;
begin
SubNodeName[1] := '便笺';
SubNodeName[2] := '发件箱';
SubNodeName[3] := '联系人';
SubNodeName[4] := '任务';
SubNodeName[5] := '日记';
New(P);
P^ := MyDocDir + '\' + PersonDir;
RootNode := '个人文件夹', P);
TreeView中如何设置选中结点
var
i:integer; {i为设置的选中结点的索引值}
begin
if i> then
[i].selected:=true;
或
:=[i];
三。
设置TreeView结点的图形
1. 设置TreeView的images属性为已存在的images对象:=imagelist1;
2. 在加入结点后执行:
var
anode:TTreeNode;
begein
anode:=(nil,'item1');
:=0; {结点未选中时显示的图标}
:=1; {结点选中时显示的图标}
end
3. 如果结点图形在改变后未发生变化,可以执行:
;
四。
如何批量处理TreeView结点
使用TreeView的items属性的BeginUpdate和EndUpdate方法,例:
for i:=0 to do
begin
file :ext:=lowercase[i].text);
end;
五。
实现TreeView结点拖拽的实例
下面的程序片段演示了如何实现拖拽treeview构件结点的例子
{鼠标按下时执行的语句}
procedure (Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin
{判断左键按下并且鼠标点在一个结点上开始实现拖拽}
if ( Button = mbLeft ) and
( htOnItem in ( X, Y ) ) then
begin
( False );
end;
end;
{鼠标拖动执行语句}
procedure ( Sender, Source: TObject;
X, Y: Integer; State: TDragState; var Accept: Boolean);
var
Node : TTreeNode;
begin
if Source = Treeview1 then
begin
Node := ( X, Y ); {取当前结点}
if Node <> nil then {当前结点不为空才能实现拖拽,accept:=true}
Accept := true;
end;
end;
{鼠标释放时执行的语句}
procedure ( Sender, Source: TObject;
X, Y : Integer );
var
TempNode : TTreeNode;
AttachMode : TNodeAttachMode;
begin
if = nil then
Exit;
AttachMode := naAddChild; {设置结点移动模式,设移动结点为子结点}
{ 注意在这里存在一个bug,当移动结点时,如果目标结点没有子结点,}
{ 则加入的新的子结点会失败,所以先在当前目标结点的下面 }
{ 加入一个临时子结点,移动完毕后,再将临时结点删除 }
try
TempNode := ,
'Temp' );
try
{ 移动选中的结点到目标结点 }
, AttachMode );
finally
; { 不要忘了释放临时结点 }
end;
finally
end;
end;
今天上午到现在的心得想从以下的一段代码来说明。
代码很简单(当我搞完后才觉得简单,搞了半天,哎,没办法,资质有限呀),主要是要完成当双击TreeView中的一项的时候将数据库中符合条件的数据显示在一个DBGird中。
procedure (Sender: TObject);
var
i:Integer;
node:TTreeNode;
begin
node:=;
if <>'father'then
begin
LoadGDDBGrid;
end;
end;
procedure (username:String);
var
node1,node2:TTreeNode;
SearchOptions:TLocateOptions;
strSQL:String;
begin
strSQL := 'select * from user_information where = ''%s''' ;
strSQL:=Format(strSQL,[username]);
showmessage(strSql);
with ADODataSet1 do
begin
Close;
CommandText:=strSQL;
Open;
end;
end;
先说明第一个procedure的功能吧,TreeviewDblClick()主要就是双击后能触发DBGrid中的事件而已。
这里面主要要说明的是node := ,这句话的意思就是获得目前被选中的节点,然后就可以根据来获得此时选中的节点的内容是什么了。
这样的做法和我以前学习的语言有比较大的出入。
这里其实也弄了好久,因为一开始是以以前自己学习的语言程序的经验来考虑,认为要获得目前选中的内容的话,应该使用的是Treeview中的item,当然,也可以通过这个来获得,但它却需要告诉程序是哪个item(item[i]),这显然就不符合现在需要做的效果了。
目前需要做的应该是先找到一种方法能够让程序知道我目前选择的是哪个节点,然后就找了一下Treeview1中有哪些方法,发现了Selected,但看到它的返回值是一个note,这就让我有点迷惑了,为什么返回的不是一个item呢(这主要是因为对delphi的体系仍然没有一个很好的了解和总结),后来发觉在note里面也有一种方法是text的,然后试了一下,就获得了我所需用的效果了。
现在想了想,其实以前已经知道Treeview是由一个个的note组成的,想到这个,就觉得是很理所当然的了。
花时间最多的可能是第二段代码吧,LoadGDDBGrid()主要是把符合条件的数据显示到DBGrid中去。
首先,我需要解决的是如何能让数据显示出来,我想到了在设计界面上DBGrid有一个DataSet的属性选择,那里选择的是ADODataSet1(ADODataSet 类型),然后想起前两天连接数据库的时候只要将ADODataSet1中的Active设置成True的话,Grid就会自动显示相应的数据。
那么,我只要把ADODataSet1激活的话,就应该可以把数据显示出来了,于是就使用了 := True,确实,可以把它显示出来了。
但这种显示是把所有数据都显示出来,那么,下一步需要解决的就是如何变为可以根据条件显示了。
在ADODataSet1中有一个CommandText,是用来保存该ADODataSet所运行的sql语句的,那么又从这里开始入手。
到了这里,遇到的困难竟然是一个以前不算是困难的问题。
以前在使用java的时候,由于java的String是使用双引号来包括的,比如A="a",但现在的delphi却是使用单引,郁闷呀,因为我的where后面的field是varchard类型的,如果在java中就可以表示为"A='a'",但在delphi中就让我困惑了,这里搞了一阵子,最后才发觉只要在其中加多两个单引就可以了,如'A=''a''',呵呵,让我哭笑不得。
在写这个功能的时候遇到的问题大概就是这些。
但我还想讲一下几个地方,首先是with ADODataSet1 do的使用,其实也就是对ADODataSet1进行了引用,那么,在这个引用中就不需要再这样的写关闭命令了,只需要加close就可以了。
其次是Format的使用。
Format()函数是用来定义字符串。
它的基本格式是S := Format('My name is %s and I'm %d years old.'', ['jag',26]),得到的S的结果就是My name is jag and I'm 26 years old.从这里可以看出了吧,Format的左右其实就是把符合条件的值放入相应的位置。
%s这种就是格式化指示符。
c代表字符类型,d代表整数类型,f代表浮点类型,p代表指针类型,s代表字符串类型。
Delphi中树型控件的使用技巧
我们都知道,开发者主要用Delphi来开发数据库管理软件,正因如此,树型控件的使用最好与数据库联系起来。
Delphi提供了一个树型控件TTreeView,可以用来描述复杂的层次关系。
树节点信息的存储和加载
常用的方法是用树控件的 LoadFromFile和SavetoFile方法,来实现树控件和文件之间的交互;或用Assign方法实现树控件和DBMemo,也就是和数据库间的交互。
该方法的优点是编程相对简单,缺点是树控件的实际节点数可能会很大,对于“大树”,每次加载和存储的数据量会加大,将降低速度,增大系统开销,造成数据冗余。
另一种方法,就是只在树上产生“看得见”的节点,没有专门记录全部树节点结构的文件或数据库字段,而将树节点结构分散在数据库的每一个记录中。
具体方法是:创建一个数据库,字段根据实际业务而定,其中必然有一个字段的信息将在树型控件的节点上显示,另外还要一个字段来保存节点的惟一标识号,该标识号由长度相等的两部分组成,前段表示当前节点的父节点号,后段表示当前节点的节点号,此标识号相当于一个“链表”,记录了树上节点的结构。
该方法的优点:用户操作“大树”时,一般不会展开所有的节点,而只用到有限的一部分,同时只能从树根一层一层地展开,该法只在树上产生“看得见”的节点,所以,存储和加载“大树”的速度快,数据量小,系统开销和数据冗余较小。
缺点:编程较复杂,但可以结合该方法编成一个新的树控件,将大大提高编程效率。
值得注意的是,ID号必须惟一,所以在编程中如何合理产生ID尤为重要。
数据库结构示例
创建一个数据库,为简化程序,我只创建两个数据库字段,定义如下:
LongID字段实际上由两段组成,每一段3位,LongID只能表示1000条记录。
将LongID定义为索引字段,存为c:\testtree\。
编辑该DBF文件,新建一条记录,Text字段设为TOP,LongID字段设为“000”(3个“0”前为三个空格)。
创建演示程序
在Form1上放置TreeView1、Table1、PopupMenu1、Edit1、Edit2。
TreeView1的PopupMenu属性设为PopupMenu1;Table1的DataBaseName属性设为c:\testtree,TableName属性设为,IndexFieldNames属性设为LongID;为PopupMenu1加选单项Add1和
Del1,Caption分别为Add和Del;Edit1用来输入新节点的Text属性值,Edit2用来输入新节点的3位ID号。
存为c:\testtree\和c:\testtree\。
在的Type关键字后加入一行:Pstr:^string;{Pstr为字符串指针}
为Form1的OnCreate事件添加代码:
procedure (Sender: TObject);
var p:Pstr;Node:TTreeNode;
begin
with Table1,Treeview1 do
begin
open;
first;
new(p);{为指针p分配内存}
p^:=FieldByName(′LongID′).AsString;
Node:=(nil,FieldByName(′Text′).AsString,p);
if HasSubInDbf(Node) then (Node,′′,nil);{有子节点则加一个空子节点}
end;
end;
HasSubInDbf为自定义函数,自变量为Node,检查节点Node有无子节点,有则返回True,反之返回False,并在TForm1的类定义里加入原型声明(其它自定义函数的原型也在TForm1的类定义里声明,不另作解释),函数代码如下:
function (Node:TTreeNode):Boolean;
begin
with Table1 do
begin
([copy(Pstr^,4,3)+′000′]);
result:=copy(FieldByName(′LongID′).AsString,1,3)=copy(Pstr^,4,3);{如数据库里当前记录的LongID字段内容的前3位和节点Node的Data的后3位相同,则Node应该有子节点}
end;
end;
为TreeView1控件的OnDeletion事件添加代码,需要指出的是,不仅调用Delete方法可以触发OnDeletion事件,而且当树控件本身被释放前,也触发OnDeletion事件,所以,在此处加入dispose会很“安全”:
procedure (Sender: TObject; Node: TTreeNode);
begin
Dispose;{释放节点数据内存}
end;
为Add1选单项的OnClick事件添加代码如下:
procedure (Sender: TObject);
var p:pstr;Tmpstr:string;i:integer;
begin
try
StrToInt;
Tmpstr:=;{注:在实用中,必须用更好的方法来产生ID}
except;
ShowMessage(′重新输入Edit2的内容′);
abort;
end;
with TreeView1 do
begin
new(p);
p^:=copy(Pstr^,4,3)+TmpStr;
end;
with Table1 do{ 在数据库里添加记录 }
begin
Append;
FieldByName(′Text′).AsString:=;
FieldByName(′LongID′).AsString:=p^;
Post;
end;
TmpStr:=inttostr(strtoint(TmpStr)+1);
for i:=length(TmpStr) to 2 do TmpStr:=′0′+TmpStr; :=TmpStr;
end;
为Del1菜单项的OnClick事件添加代码如下:
procedure (Sender: TObject);
var DelList:TStringList;LongID,NSubLongID:string;
begin
DelList:=;
:=True;
(Pstr
while >0 do
begin
LongID:=[0];
(0);
(′LongID′).AsString:=LongID;
if then ;
if HasSubInDbf then
begin
NSubLongID:=(′LongID′).AsString;
while (copy(NSubLongID,1,3)=copy(LongID,4,3))and(not do
begin
(NSubLongId);
;
NSubLongId:=(′LongID′).AsString;
end;
end;
end;
;
end;
为TreeView1的OnExpanding事件添加代码:
procedure (Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
var TmpNode:TTreeNode;NSubLongID:String;p:Pstr;bm:TBookMark; begin
with Table1,TreeView1 do
begin
FieldByName(′LongID′).AsString:=Pstr^;
if not GotoKey then (Node)
else
begin
TmpNode:=;
if =′′)and=nil) then
begin
;
if HasSubInDbf(Node) then
begin
NSubLongID:=FieldByName(′LongID′).AsString;
while (copy(NSubLongID,1,3)=copy(Pstr^,4,3))and(not Eof) do begin
new(p);
p^:=FieldByName(′LongID′).AsString;
bm:=GetBookMark;
TmpNode:=(Node,FieldByName(′Text′).AsString,p);
if HasSubInDbf(TmpNode) then (TmpNode,′′,nil);
GotoBookMark(bm);
FreeBookMark(bm);
Next;
NSubLongId:=FieldByName(′LongID′).AsString;
;
end;
end;
以上简要谈了谈数据库的树状显示的基本方法,另外,编辑树上节点的Text属性的同时对数据库进行修改、同一数据库在多用户同时操作时数据库以及树的一致性、树上节点的拷贝与复制等就不再赘述,读者可自行完善。
本文程序在、Windows 98下调试通过。