Delphi環境下靈活的數據庫樹表實現
1 數據結構設計
在設計時可以採用兩種數據存儲方式(數據結構)。
1)信息分類表採用一個分類作爲表的一條記錄的方式進行存儲,並由父類編號建立各類之間的關係,即:
分類號 類的含義 父類的編號
0001 計算機
0002 無線電
2)這種分類表結構的建立不用動態生成表結構,但使用時相對麻煩。我們採用以下的表結構建立信息分類關係。這樣的結構可以實現任意級的樹型結構,如M層的一個結構:其中M是本單位的分類最大深度。類號要唯一識別每一個類。最上一級的分類號爲全宗號+本級的分類代號,其他任意級的類其類號都爲上一級的分類號+本級的分類代號。用戶根據本單位檔案的信息分類情況輸入類信息,由系統動態創建表。
信息分類號 全宗號 類1含義 類2含義 類M含義 分類深度
0001 Zzb20 電子 計算機 2
0002 Zzb20 電子 無線電 2
0003 Zzb20 通信 衛星
0004 Zzb20 通信 遙感
C4 C6 C20 C20 C20 C1
XXFLH QZH L1 L2 LM FLSD
在下面的例子中由於安全要求,對數據庫表的結構作了簡化,同時只使用了一些模擬數據以作說明。
2 樹表外形設計
由於Delphi提供的樹表控件比較簡單,外觀不很美觀,因此需要作一些“修飾”工作。爲了強調重點,忽略了數據庫操作如查詢等,在例程中只使用了有關樹表控件。
1)增加色彩
一般的樹表控件沒有底圖,沒有色彩,因此首先要解決色彩問題。解決的方法就是在樹表控伯的ONDraw事件響應中增加底圖顯示代碼。
procedure TForm1.Tree1CustomDraw(Sender: TCustomTreeView; const ARect: TRect; var DefaultDraw: Boolean);
begin
with tree1.Canvas do //取樹表控件的顯示底板
begin
brush.color:=RGB(200,200,255); //設置畫筆顏色
FillRect(ARect); //填充底板
end;
end;
這裏僅爲樹表控件設置的底板顏色,實際上還可以設置底圖,但由於底圖的色彩不易控制,在調整樹表結點時,容易出現顏色混亂的情況,而且只設置其顏色使樹表控件的顯示更加簡潔。但只在ONDraw事件中設置顏色是不夠的,還需要在其子項(結點)的顯示事件中增加以下代碼:
procedure TForm1.Tree1CustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
var
Noderect:TRect;
begin
with tree1.Canvas do
begin
case Node.Level of //根據不同的層次,設置不同的結點字體顏色
0: font.Color:=clBlue;
…
5: font.Color:=clGreen;
end;
if node=tree1.Selected then //爲突出選中的結點,將選中結點字體顏色設置爲紅色
font.Color:=clRed;
NodeRect:=Node.DisplayRect(false); //設置結點的顯示方式
brush.Color:=RGB(200,200,255);
fillrect(NodeRect); //將結點的背景色與控件底板色設置爲一致 end;
end;
2)設置圖標
圖像列表控件與樹表控件可以算是“老搭檔”,即爲了顯示不同層次的結點就需要爲樹表控件設置一個對應的圖像列表,其設置方法比較簡單,可以參見有關的Delphi程序設計手冊。由於樹結構層次不定,因此不能象常規的方法那樣建立了樹表與圖像列表之間的關係即大功告成,這裏還需要做的是建立樹表結點與圖像列表的關係,可以通過以下兩個函數實現。
procedure TForm1.Tree1GetImageIndex(Sender: TObject; Node: TTreeNode);
begin
node.ImageIndex:=node.Level; //由結點的層次決定其對應圖標在圖像列表中的位置 end;
procedure TForm1.Tree1GetSelectedIndex(Sender: TObject; Node: TTreeNode);
begin
node.SelectedIndex:=node.ImageIndex;
end;
3) 窗體激活事件響應
響應窗體激活事件時,需要作一些設置。由於在原系統中,此窗體涉及的功能和控件較多,許多功能的完成都需要激活另外的窗體。因此需要避免對數據庫信息的重複設置。可以如下實現:
procedure TForm1.FormActivate(Sender: TObject);
var
i:integer;
begin
if first_in=false then //區別是否首次激活,從而決定是否需要設置初始狀態
begin
hint.Caption := ' 提示: 正在準備,請稍候...';
application.ProcessMessages ;
tree1.Items.Clear; //設置第一個樹表狀態和顏色
tree1.Color:=RGB(200,200,255);
tree2.Items.Clear; //設置第一個樹表狀態和顏色
tree2.Color:=RGB(100,200,255);
qznode:=nil; //爲了顯示動態結構的分類層次,將各層樹結點初始化爲空
for i:=0 to 5 do
last_node[i]:=nil;
stackdepth:=0; //設置棧初始深度爲0
show_tree1(0); //調用show_tree1顯示分類內容,參數0表示從第一層開始顯示
hint.Caption:=' 提示: 數據成功調入';
Application.ProcessMessages;
end;
first_in:=true;
end;
4) 樹表內容顯示
由於樹表結點層次的不確定,我們採用了遞歸顯示的方法完成樹表內容的顯示。這裏設置了4個變量,其含義爲;
變量 類型 含義 可選範圍
cur_cds TQuery 當前的查詢集 dbmd.qz,dbmd.fl1,dbmd.fl2,dbmd.fl3,dbmd.fl4,dbmd.fl5
cur_str string 當前的查詢語句
num_str String 當前的層次數查詢語句
cur_level Integer 當前層次
cur_node TTreeNode 當前結點
這裏利用棧確定父結點與子結點的關係,若某結點對應的分類深度比當前深度要大,就需要將此結點入棧,並惟此結點爲父結點,以插入孩子結點方式遞歸地顯示它的下一層結點,若當前層的結點都已顯示完畢,就需要退棧,回到上一層次再作計算。主要的實現過程如下:
procedure TForm1.show_tree1(level:integer);
… //變量聲明
begin
case level of //根據輸入參數,確定當前數據集、結點和查詢語句
0:
begin
cur_cds:=dbmd.qz;
cur_node:=qznode;
cur_str:='select distinct QZH from XXFLB ';
end;
1:
begin
cur_cds:=dbmd.fl1;
cur_str:='select distinct L1 from XXFLB ';
cur_str:=cur_str +' where QZH= '+''''+dbmd.qz.Fields.Fields[0].value+'''';
end;
…
end;
cur_cds.close; //實現查詢
cur_cds.SQL.clear;
cur_cds.SQL.Add(cur_str);
cur_cds.open;
for i:=1 to cur_cds.RecordCount do //遍歷當前層的所有結點
begin
if level=0 then //若爲第一層則調用add方法創建結點
cur_node := tree1.Items.Add(cur_node,cur_cds.Fields.Fields[0].value)
else //否則,在上一結點基礎上調用AddChild方法創建其子結點
cur_node:=tree1.Items.AddChild(last_node[stackdepth-1],cur_cds.Fields.Fields[0].value);
if level<5 then //根據當前結點層次及結點內容確定當前類的分類深度
begin
case level of
0:
begin
num_str:='select FLSD from XXFLB ';
num_str:=num_str +' where QZH= '+''''+dbmd.qz.Fields.Fields[0].value+'''';
end;
1:
…
end;
dbmd.sd.close; //執行深度查詢
dbmd.sd.SQL.Clear;
dbmd.sd.sql.add(num_str);
dbmd.sd.Open;
val(dbmd.sd.Fields.Fields[0].value,cur_level,code); //取其深度
if cur_level>level then //若當前層次未達到其分類深度 begin
last_node[stackdepth]:=cur_node; //當前結點入棧
stackdepth:=stackdepth+1;
show_tree1(level+1); //遞歸顯示下一層次結點
end;
end;
cur_cds.Next; //取當前結果集中的下一條記錄
if i > cur_cds.RecordCount -1 then //若當前層中全部記錄顯示完畢則退棧
stackdepth:=stackdepth-1;
end;
end;
另外第二個樹表的內容顯示的基本方法與此類似,所不同的是第二個樹表的顯示函數所帶的參數爲串型參數,該參數有兩種可能,即爲‘’或由點擊第一個樹表的事件產生串參數。若參數爲空串,則要在第二個樹表中顯示所有項目,否則要根據參數創建過濾條件選擇顯示項目。具體方法可以參見源代碼。
5) 樹表調整
樹表的調整即是對樹表當前結點的條件,可以由兩種事件產生,即樹結點的擴展和點擊樹結點。由於一個全宗號下的分類種類很多,層次也可能很大,因此用戶希望打開一個類時,與其無關的類就關閉,這樣就可以保證僅有目前所選擇的結點,而不需要用戶利用滾動條在打開的結點中尋找。其實現要充分利用樹結點的方法和屬性。特別強調的是在對點擊樹表結點的響應事件中用到了結點的擴展方法,儘管對結點擴展事件的響應函數所實現的功能與之類似,但一定不能加入類似的結點擴展方法,如tree1.Selected.Expand(false)等,因爲在擴展事件中調用擴展方法會導致事件循環,甚至死機。
對第二個樹表的調整與第一個樹表的處理方式類似,但功能不同,這裏不多介紹。
procedure TForm1.Tree1Click(Sender: TObject);
… //變量聲明
begin
if Tree1.Selected=nil then //若未選擇結點,重置
begin
tree2.Items.Clear;
tree2.Refresh;
exit;
end
else
begin
tree2.Items.Clear;
tree2.Refresh;
str:=Tree1.Selected.Text; //取當前結點內容
fatherNode:=Tree1.Selected.Parent; //取當前結點父結點
tempnode:=Tree1.Selected; //設置當前結點
while fatherNode<>nil do //由當前層開始關閉所有非當前結點,
//同時返回由其第一層祖先到當前結點所組成的串,作爲第二個樹表顯示的參數。
begin
str:=fatherNode.Text+','+str; //取父結點內容與當前結點內容聯接
usenode:=fatherNode.getFirstChild; //遍歷父結點的所有兒子結點
while usenode<>nil do
begin
if usenode<>tempnode then //只要不是當前結點,則取消擴展狀態
usenode.Collapse(true);
usenode:=fatherNode.GetNextChild(usenode);
end;
tempnode:=fathernode; //當前層結點處理完畢後,調整父結點爲當前結點
fathernode:=tempnode.Parent; //再取當前結點的父結點
end;
sibNode:=tree1.Items[0]; // 第一層結點的處理與其它層稍有差異
while sibNode<>nil do
begin
if sibNode<>tempNode then
sibNode.Collapse(true);
sibNode:=sibNode.getNextSibling;
end;
tree1.Selected.Collapse(true); //強制打開當前結點
tree1.Selected.Expand(false);
if tree1.Selected.HasChildren=false then //若當前結點無孩子,
begin
show_tree2(str); //說明已構成一個完整的類,調用show_tree2函數實現第二個樹表//的內容顯示,參數str爲完整的類名
hint.Caption:=' 提示: 數據成功調入';
Application.ProcessMessages;
end;
end;
參考文獻
1 徐新華,IDE和Object Pascal 語言,人民郵電出版社,1998.12
2 鄭城榮,曾凡奎等,Delphi 運行時間庫RTL和組件庫VCL技術參考,人民郵電出版社,1999.1
1 徐新華,GUI編程技術,人民郵電出版社,1998.12
在設計時可以採用兩種數據存儲方式(數據結構)。
1)信息分類表採用一個分類作爲表的一條記錄的方式進行存儲,並由父類編號建立各類之間的關係,即:
分類號 類的含義 父類的編號
0001 計算機
0002 無線電
2)這種分類表結構的建立不用動態生成表結構,但使用時相對麻煩。我們採用以下的表結構建立信息分類關係。這樣的結構可以實現任意級的樹型結構,如M層的一個結構:其中M是本單位的分類最大深度。類號要唯一識別每一個類。最上一級的分類號爲全宗號+本級的分類代號,其他任意級的類其類號都爲上一級的分類號+本級的分類代號。用戶根據本單位檔案的信息分類情況輸入類信息,由系統動態創建表。
信息分類號 全宗號 類1含義 類2含義 類M含義 分類深度
0001 Zzb20 電子 計算機 2
0002 Zzb20 電子 無線電 2
0003 Zzb20 通信 衛星
0004 Zzb20 通信 遙感
C4 C6 C20 C20 C20 C1
XXFLH QZH L1 L2 LM FLSD
在下面的例子中由於安全要求,對數據庫表的結構作了簡化,同時只使用了一些模擬數據以作說明。
2 樹表外形設計
由於Delphi提供的樹表控件比較簡單,外觀不很美觀,因此需要作一些“修飾”工作。爲了強調重點,忽略了數據庫操作如查詢等,在例程中只使用了有關樹表控件。
1)增加色彩
一般的樹表控件沒有底圖,沒有色彩,因此首先要解決色彩問題。解決的方法就是在樹表控伯的ONDraw事件響應中增加底圖顯示代碼。
procedure TForm1.Tree1CustomDraw(Sender: TCustomTreeView; const ARect: TRect; var DefaultDraw: Boolean);
begin
with tree1.Canvas do //取樹表控件的顯示底板
begin
brush.color:=RGB(200,200,255); //設置畫筆顏色
FillRect(ARect); //填充底板
end;
end;
這裏僅爲樹表控件設置的底板顏色,實際上還可以設置底圖,但由於底圖的色彩不易控制,在調整樹表結點時,容易出現顏色混亂的情況,而且只設置其顏色使樹表控件的顯示更加簡潔。但只在ONDraw事件中設置顏色是不夠的,還需要在其子項(結點)的顯示事件中增加以下代碼:
procedure TForm1.Tree1CustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
var
Noderect:TRect;
begin
with tree1.Canvas do
begin
case Node.Level of //根據不同的層次,設置不同的結點字體顏色
0: font.Color:=clBlue;
…
5: font.Color:=clGreen;
end;
if node=tree1.Selected then //爲突出選中的結點,將選中結點字體顏色設置爲紅色
font.Color:=clRed;
NodeRect:=Node.DisplayRect(false); //設置結點的顯示方式
brush.Color:=RGB(200,200,255);
fillrect(NodeRect); //將結點的背景色與控件底板色設置爲一致 end;
end;
2)設置圖標
圖像列表控件與樹表控件可以算是“老搭檔”,即爲了顯示不同層次的結點就需要爲樹表控件設置一個對應的圖像列表,其設置方法比較簡單,可以參見有關的Delphi程序設計手冊。由於樹結構層次不定,因此不能象常規的方法那樣建立了樹表與圖像列表之間的關係即大功告成,這裏還需要做的是建立樹表結點與圖像列表的關係,可以通過以下兩個函數實現。
procedure TForm1.Tree1GetImageIndex(Sender: TObject; Node: TTreeNode);
begin
node.ImageIndex:=node.Level; //由結點的層次決定其對應圖標在圖像列表中的位置 end;
procedure TForm1.Tree1GetSelectedIndex(Sender: TObject; Node: TTreeNode);
begin
node.SelectedIndex:=node.ImageIndex;
end;
3) 窗體激活事件響應
響應窗體激活事件時,需要作一些設置。由於在原系統中,此窗體涉及的功能和控件較多,許多功能的完成都需要激活另外的窗體。因此需要避免對數據庫信息的重複設置。可以如下實現:
procedure TForm1.FormActivate(Sender: TObject);
var
i:integer;
begin
if first_in=false then //區別是否首次激活,從而決定是否需要設置初始狀態
begin
hint.Caption := ' 提示: 正在準備,請稍候...';
application.ProcessMessages ;
tree1.Items.Clear; //設置第一個樹表狀態和顏色
tree1.Color:=RGB(200,200,255);
tree2.Items.Clear; //設置第一個樹表狀態和顏色
tree2.Color:=RGB(100,200,255);
qznode:=nil; //爲了顯示動態結構的分類層次,將各層樹結點初始化爲空
for i:=0 to 5 do
last_node[i]:=nil;
stackdepth:=0; //設置棧初始深度爲0
show_tree1(0); //調用show_tree1顯示分類內容,參數0表示從第一層開始顯示
hint.Caption:=' 提示: 數據成功調入';
Application.ProcessMessages;
end;
first_in:=true;
end;
4) 樹表內容顯示
由於樹表結點層次的不確定,我們採用了遞歸顯示的方法完成樹表內容的顯示。這裏設置了4個變量,其含義爲;
變量 類型 含義 可選範圍
cur_cds TQuery 當前的查詢集 dbmd.qz,dbmd.fl1,dbmd.fl2,dbmd.fl3,dbmd.fl4,dbmd.fl5
cur_str string 當前的查詢語句
num_str String 當前的層次數查詢語句
cur_level Integer 當前層次
cur_node TTreeNode 當前結點
這裏利用棧確定父結點與子結點的關係,若某結點對應的分類深度比當前深度要大,就需要將此結點入棧,並惟此結點爲父結點,以插入孩子結點方式遞歸地顯示它的下一層結點,若當前層的結點都已顯示完畢,就需要退棧,回到上一層次再作計算。主要的實現過程如下:
procedure TForm1.show_tree1(level:integer);
… //變量聲明
begin
case level of //根據輸入參數,確定當前數據集、結點和查詢語句
0:
begin
cur_cds:=dbmd.qz;
cur_node:=qznode;
cur_str:='select distinct QZH from XXFLB ';
end;
1:
begin
cur_cds:=dbmd.fl1;
cur_str:='select distinct L1 from XXFLB ';
cur_str:=cur_str +' where QZH= '+''''+dbmd.qz.Fields.Fields[0].value+'''';
end;
…
end;
cur_cds.close; //實現查詢
cur_cds.SQL.clear;
cur_cds.SQL.Add(cur_str);
cur_cds.open;
for i:=1 to cur_cds.RecordCount do //遍歷當前層的所有結點
begin
if level=0 then //若爲第一層則調用add方法創建結點
cur_node := tree1.Items.Add(cur_node,cur_cds.Fields.Fields[0].value)
else //否則,在上一結點基礎上調用AddChild方法創建其子結點
cur_node:=tree1.Items.AddChild(last_node[stackdepth-1],cur_cds.Fields.Fields[0].value);
if level<5 then //根據當前結點層次及結點內容確定當前類的分類深度
begin
case level of
0:
begin
num_str:='select FLSD from XXFLB ';
num_str:=num_str +' where QZH= '+''''+dbmd.qz.Fields.Fields[0].value+'''';
end;
1:
…
end;
dbmd.sd.close; //執行深度查詢
dbmd.sd.SQL.Clear;
dbmd.sd.sql.add(num_str);
dbmd.sd.Open;
val(dbmd.sd.Fields.Fields[0].value,cur_level,code); //取其深度
if cur_level>level then //若當前層次未達到其分類深度 begin
last_node[stackdepth]:=cur_node; //當前結點入棧
stackdepth:=stackdepth+1;
show_tree1(level+1); //遞歸顯示下一層次結點
end;
end;
cur_cds.Next; //取當前結果集中的下一條記錄
if i > cur_cds.RecordCount -1 then //若當前層中全部記錄顯示完畢則退棧
stackdepth:=stackdepth-1;
end;
end;
另外第二個樹表的內容顯示的基本方法與此類似,所不同的是第二個樹表的顯示函數所帶的參數爲串型參數,該參數有兩種可能,即爲‘’或由點擊第一個樹表的事件產生串參數。若參數爲空串,則要在第二個樹表中顯示所有項目,否則要根據參數創建過濾條件選擇顯示項目。具體方法可以參見源代碼。
5) 樹表調整
樹表的調整即是對樹表當前結點的條件,可以由兩種事件產生,即樹結點的擴展和點擊樹結點。由於一個全宗號下的分類種類很多,層次也可能很大,因此用戶希望打開一個類時,與其無關的類就關閉,這樣就可以保證僅有目前所選擇的結點,而不需要用戶利用滾動條在打開的結點中尋找。其實現要充分利用樹結點的方法和屬性。特別強調的是在對點擊樹表結點的響應事件中用到了結點的擴展方法,儘管對結點擴展事件的響應函數所實現的功能與之類似,但一定不能加入類似的結點擴展方法,如tree1.Selected.Expand(false)等,因爲在擴展事件中調用擴展方法會導致事件循環,甚至死機。
對第二個樹表的調整與第一個樹表的處理方式類似,但功能不同,這裏不多介紹。
procedure TForm1.Tree1Click(Sender: TObject);
… //變量聲明
begin
if Tree1.Selected=nil then //若未選擇結點,重置
begin
tree2.Items.Clear;
tree2.Refresh;
exit;
end
else
begin
tree2.Items.Clear;
tree2.Refresh;
str:=Tree1.Selected.Text; //取當前結點內容
fatherNode:=Tree1.Selected.Parent; //取當前結點父結點
tempnode:=Tree1.Selected; //設置當前結點
while fatherNode<>nil do //由當前層開始關閉所有非當前結點,
//同時返回由其第一層祖先到當前結點所組成的串,作爲第二個樹表顯示的參數。
begin
str:=fatherNode.Text+','+str; //取父結點內容與當前結點內容聯接
usenode:=fatherNode.getFirstChild; //遍歷父結點的所有兒子結點
while usenode<>nil do
begin
if usenode<>tempnode then //只要不是當前結點,則取消擴展狀態
usenode.Collapse(true);
usenode:=fatherNode.GetNextChild(usenode);
end;
tempnode:=fathernode; //當前層結點處理完畢後,調整父結點爲當前結點
fathernode:=tempnode.Parent; //再取當前結點的父結點
end;
sibNode:=tree1.Items[0]; // 第一層結點的處理與其它層稍有差異
while sibNode<>nil do
begin
if sibNode<>tempNode then
sibNode.Collapse(true);
sibNode:=sibNode.getNextSibling;
end;
tree1.Selected.Collapse(true); //強制打開當前結點
tree1.Selected.Expand(false);
if tree1.Selected.HasChildren=false then //若當前結點無孩子,
begin
show_tree2(str); //說明已構成一個完整的類,調用show_tree2函數實現第二個樹表//的內容顯示,參數str爲完整的類名
hint.Caption:=' 提示: 數據成功調入';
Application.ProcessMessages;
end;
end;
參考文獻
1 徐新華,IDE和Object Pascal 語言,人民郵電出版社,1998.12
2 鄭城榮,曾凡奎等,Delphi 運行時間庫RTL和組件庫VCL技術參考,人民郵電出版社,1999.1
1 徐新華,GUI編程技術,人民郵電出版社,1998.12
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.