程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Delphi中根據分類數據生成樹形結構的最優方法

Delphi中根據分類數據生成樹形結構的最優方法

編輯:Delphi

一、 引言:

TreeView控件適合於表示具有多層次關系的數據。它以簡潔的界面,表現形式清晰、形象,操作簡單而深受用戶喜愛。而且用它可以實現ListView、ListBox所無法實現的很多功能,因而受到廣大程序員的青睐。

樹形結構在Windows環境中被普遍應用,但在數據庫開發中面對層次多、結構復雜的數據,如何快速構造樹形目錄並實現導航呢?

二、 實現關鍵技術:

在Delphi提供的控件中包含了TreeView控件,但樹的具體形成還需要用戶編寫代碼。即它的列表項要在程序中動態添加,而這些列表數據通常由用戶已錄入在數據庫表中,並作為數據庫維護的一項內容。

許多人用TreeView構造樹形目錄時,通常都使用多個嵌套循環,或遞歸算法,將代碼“編織”成樹。這樣不但算法復雜,且運行效率低下,不是最佳選擇。這裡介紹的是基於編碼結構的高效算法。該算法的主要優點是:程序短小精悍,運行效率高,能快速實現數據庫的樹形結構,可以適應任何復雜的層次數據,實現方法簡單,且樹的內容有變動時,無需更改程序行。

算法的關鍵在於代碼字典表中的代碼字段的設計上一定要符合一定的代碼設計要求,數據類型使用字符型。用戶新增和修改代碼時,必須有嚴格的約束,否則會導致程序出錯。編碼表的基本字段包括編碼和編碼名稱,其編碼規則是以數字、字母的位數來區分不同層次,同一層編碼位數相同,層次按位數遞增,程序通過判斷編碼位數來決定所在層數。

本例程中編碼結構是“222”,編碼格式為 “XX XX XX”。例如:第一層為10~99兩位,第二層為1001~1099四位,用戶需要做的是先要設計樹的結構和對應編碼,並錄入相應名稱,然後程序在讀取這些數據時形成樹。本例程不需要用戶自己進行編碼,程序中自動實現各層編碼,保證了代碼格式的正確性。

用TreeView導航表時,采用彈出式菜單,通過對話框操作數據表,同步更新樹形控件和數據庫。在所有操作中,樹形控件不用重構,從而避免了重構時TreeView控件出現的閃動,也提高了程序的運行速度。

本示例程序為了使大家看清楚數據表中記錄是否同步更新,用TDBGrid控件顯示當前數據庫表中所有記錄。下面給出范例程序和主要代碼分析。

三、 范例程序和主要代碼分析:

我們以建立一個城市名稱的樹形結構為例來說明如何快速生成樹形並實現導航數據表。

1. 先建立編碼表:city_tree(bianma,cityname)。

2. 新建一個項目,按默認保存。

3. 新建一公共單元pubvar,在其中定義以下常量:

Const

cTreeCodeFormat = ‘222’;//編碼格式為 XX XX XX

cTreeMaxLevel = 3;//最大編碼層次

cTreeRootTxt = ‘城市’;//樹根結點名稱

這樣做為了提高程序的通用性,以後用於其他代碼字典的維護時,只需要更改這些特征常量。

4. 程序源代碼:

unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, DB, DBTables, ImgList, ComCtrls , PubVar, Grids, DBGrids, Menus , StrUtils, StdCtrls;
type
TForm1 = class(TForm)
Tree: TTreeView;
ImageList1: TImageList;
Table1: TTable;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
PopupMenu1: TPopupMenu;
AddMenu: TMenuItem;
DeleteMenu: TMenuItem;
RenameMenu: TMenuItem;
Query1: TQuery;
DataSource2: TDataSource;
procedure AddMenuClick(Sender: TObject);//點擊增加子項菜單
procedure RenameMenuClick(Sender: TObject);//點擊重命名菜單
procedure DeleteMenuClick(Sender: TObject); //點擊刪除該項菜單
procedure FormCreate(Sender: TObject);
procedure TreeClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure LoadTree(treeDB:TDBDataSet);//初始化樹
procedure UpdateTree(curNode:TTreenode; nodeTxt:string; state:string);//更新樹
function GetNodeLevel(sFormat,sCode:string):integer;//獲得節點層數
function GetCurrChildNodeMaxMa(curNode:TTreenode):string;
//獲得當前節點子節點的最大編碼
function GetCurrentNodeBianma(curNode:TTreenode):string;//獲得當前節點的編碼
procedure UpdateTable(bianma:string; cityname:string ;state:string); //更新數據庫
end;
var
Form1: TForm1;
CurrentTreeNode: TTreeNode;
// AddChildeTreeNode: TTreeNode;
// flag:boolean; //用於標識是否需要在重命名樹結點時更新數據
implementation
uses AddChildUnit,RenameItemUnit;
{$R *.dfm}
procedure TForm1.LoadTree(treeDB:TDBDataSet);//初始化樹
var
curID,nodeTxt:string;
level:integer;
mynode:array[0..3] of TTreenode;
begin //初始化變量
Screen.Cursor:=crHourGlass;
tree.Enabled:=True;
tree.Items.Clear;
level:=0 ;
//設置根節點
mynode[level]:=tree.items.add(Tree.Topitem,cTreeRootTxt);
mynode[level].ImageIndex:=1;
//遍歷數據表,利用編碼字段記錄排序規律,依次添加樹節點
with treeDB do
begin
try
if not Active then open;
first;
while not Eof do
begin
curID:=trim(FieldByName(’bianma’).AsString);
nodeTxt:=curID+’-’+trim(FieldByName(’cityname’).AsString);
level:=GetNodeLevel(cTreeCodeFormat,curID);
//這裡返回代碼的層次數
if level〉0 then
begin
//增加下一節點時,用添加子節點的方法可輕松實現節點間的層次關系
//注意:這裡的父節點是用當前節點的上一級節點mynode[level-1]
mynode[level]:= tree.items.addchild(mynode[level-1],nodeTxt);
mynode[level].ImageIndex:=2;
end;
next;//下一條記錄
end;
finally
close;
End;
mynode[0].expand(False);
Screen.Cursor:=crDefault;
end;
end;
function TForm1.GetNodeLevel(sFormat,sCode:string):integer;//獲得節點層數
var i,level,iLen:integer;
begin
level:=-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.UpdateTree(curNode:TTreenode; nodeTxt:string; state:string);
Begin
if UpperCase(state)=’ADD’ then
begin
curNode:=tree.items.addchild(curNode,nodeTxt);
curNode.ImageIndex:=2;
end;
if UpperCase(state)=’DEL’ then
begin
curNode.DeleteChildren;
curNode.delete;
end;
if UpperCase(state)=’EDI’ then curNode.Text:=nodeTxt;
end;
procedure TForm1.AddMenuClick(Sender: TObject);//點擊增加子項菜單
var AddChildText, AddTableText,maxbianma : string;
begin
AddChildForm.Label1.Caption:=’為“’+CurrentTreeNode.Text+’“增加子項 ’;
if AddChildForm.ShowModal=mrOk then
begin
AddChildText:=AddChildForm.Edit1.Text;
maxbianma:=GetCurrChildNodeMaxMa(CurrentTreeNode);
if (CurrentTreeNode.Text=’城市’) and (maxbianma=’1000’) then
maxbianma:=’11’//如果當前節點為根節點,且只有一個子節點,使增加節點編碼為11
else if CurrentTreeNode.Text=’城市’ then
maxbianma:=IntToStr(StrToInt(LeftStr(maxbianma,2))+1)
else
maxbianma:=IntToStr(StrToInt(maxbianma)+1); //使子項編碼自動增1
if maxbianma〈〉’0’ then
begin
//增加樹子層
AddTableText:=maxbianma+’-’+AddChildText;
UpdateTree(CurrentTreeNode,AddTableText,’add’); //更新樹
UpdateTable(maxbianma,AddChildText,’add’); //更新表
ShowMessage(’添加成功!’);
end
else ShowMessage(’此層為最低子層,不能在該層增加子層’);
AddChildForm.Edit1.Text:=’’;
end;
end;
function TForm1.GetCurrChildNodeMaxMa(curNode:TTreenode):string;
//獲得當前節點子節點的最大編碼
var
aSQL,maxbianma:string;
li_pos:integer;
begin
li_pos:=pos(’-’,curNode.Text);
if li_pos=7 then
begin result:=’-1’; exit; end;
if (li_pos=0) and (not(curNode.HasChildren)) then // 如果當前節點為根節點並且沒有子節點
begin
result:=’9’; //使根節點第一個節點編碼為10
exit;
end
else begin
aSQL:=’select bianma from city_tree where bianma like “’ + MidStr(curNode.Text, 1, li_pos-1) + ’%“’;
Query1.UnPrepare;
Query1.Close;
Query1.SQL.Clear;
Query1.SQL.Text:=aSQL;
Query1.Prepare;
Query1.ExecSQL;
Query1.Active:=true;
Query1.Last;
maxbianma:=Query1.fieldbyname(’bianma’).AsString;
if Query1.RecordCount=1 then//如果當前項沒有子項
maxbianma:=maxbianma+’00’;
Query1.Active:=false;
end;
result:=maxbianma;
end;
procedure TForm1.RenameMenuClick(Sender: TObject);//點擊重命名菜單
var
bianma:string;
itemtext:string; //用於重命名時保存輸入的Edit.text
begin
RenameItemForm.Label1.Caption:=’將“’+CurrentTreeNode.Text+’“命名為 ’;
if RenameItemForm.ShowModal=mrOk then
begin
itemtext:=RenameItemForm.Edit1.Text;
bianma:=GetCurrentNodeBianma(CurrentTreeNode);
Table1.Locate(’bianma’,bianma,[]);
UpdateTable(’’,itemtext,’edi’);
itemtext:=bianma+’-’+itemtext;
UpdateTree(CurrentTreeNode,itemtext,’edi’);
ShowMessage(’重命名成功!’);
end;
end;
//以下過程在新增、刪除、修改記錄時,同步更新數據庫表
procedure TForm1.UpdateTable(bianma:string; cityname:string ;state:string); //更新數據庫
begin
if state=’add’ then
begin
Table1.Active:=True;
Table1.Insert;
Table1.SetFields([bianma,cityname]);
Table1.Post;
end;
if state=’del’ then
begin
Table1.Active:=True;
Table1.Locate(’bianma’,bianma,[]);
Table1.Delete;
end;
if state=’edi’ then
begin
Table1.Edit;
Table1.FieldByName(’cityname’).AsString:=cityname;
Table1.Post;
end;
end;
procedure TForm1.DeleteMenuClick(Sender: TObject); //點擊刪除該項菜單
var
bianma:string;
begin
CurrentTreeNode.expand(False);
if CurrentTreeNode.Text=’城市’ then //如果當前節點為根節點
begin
ShowMessage(’不能刪除根節點’);
exit;//退出該過程
end;
if CurrentTreeNode.HasChildren then //如果當前節點具有子項
begin
ShowMessage(’請先刪除其子項’);
exit;//退出該過程
end;
if Application.MessageBox(PChar(’真的要刪除“’+CurrentTreeNode.Text+’“這項嗎?’),’警告’,MB_YESNO)=mrYES then
begin
bianma:=GetCurrentNodeBianma(CurrentTreeNode);
UpdateTree(CurrentTreeNode,’’,’del’);
UpdateTable(bianma,’’,’del’);
ShowMessage(’刪除成功!’);
Table1.Refresh;//更新TBGrid控件中的顯示
Table1.Active:=true;
CurrentTreeNode:=Form1.tree.selected;
end;
end;
function TForm1.GetCurrentNodeBianma(curNode:TTreeNode):string;//獲得當前節點的編碼
var
li_pos:integer;
bianma:string;
begin
li_pos:=pos(’-’,curNode.Text);
bianma:=MidStr(curNode.Text,1,li_pos-1);
result:=bianma;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadTree(Table1);
Table1.Active:=true;
end;
procedure TForm1.TreeClick(Sender: TObject);
begin
CurrentTreeNode:=Form1.tree.selected; //獲得當前節點
end;
end.
unit PubVar;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, DbTables, StdCtrls, ExtCtrls, Buttons, Dialogs,Registry,Db, ComCtrls;
const
cTreeCodeFormat=’222’; //編碼格式:xx xx xx
cTreeMaxLevel=3; //最大編碼(樹節點)層次
cTreeRootTxt=’城市’; //樹的根節點名稱
implementation
end.

5. 編譯運行結果圖:(附後)

四、 小結:

本程序與編碼法快速生成樹形結構,通過TreeView控件直接操作數據表,實現了對表的數據導航。如果你想通過點擊TreeView控件某項直接顯示該項的相關信息,可在該程序的基礎上進行修改。

原文載於《電腦編程技巧與維護》2003年第一期P27

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved