Windows視覺目錄樹實作
多語言實例:Delphi7, FPC, Python與B4J
主要亮點
- 根據絕對路徑或當前目錄初始化視覺目錄樹
- 第一層目錄先行展示,各層節點支援展開與收合
- 以色階顯示資料夾內資料量,提供全部展開及收合功能
概覽與需求說明
本文結合多種程式語言示範如何在Windows環境下以絕對路徑作為命令參數建立一個視覺化目錄樹。此目錄樹具有以下功能:
- 當無參數傳入時,預設採用目前目錄
- 在首次載入時展示第一層目錄
- 使用者可展開或收合各層節點
- 依據各資料夾內含資料的大小,以從淺至深的色階表示,提供直覺上的視覺反饋
- 提供全部展開以及全部收合功能,方便使用者瀏覽或統一收起節點
- 採用的語言有 Delphi 7、Free Pascal (FPC)、Python 及 B4J,並儘量避免使用第三方庫
下文將詳細說明這四種實作方式的程式碼範例及其實現方式,包含目錄遍歷、資料夾大小計算與色階設定,並在介面上提供展開/收合所有節點的功能。
Delphi 7 與 Free Pascal (FPC) 實作
Delphi 7 和 FPC 都能藉由Windows原生控制項 TTreeView 來實現目錄樹。此方式透過遞迴呼叫在第一層先展示目錄節點,再進一步遞迴建立子目錄。使用者可以根據需要展開或收合節點,另外透過色階來根據資料夾大小調整節點的呈現風格。
Delphi 7 程式碼示例
{-----------------------------------------------------------------------------
Delphi 7 視覺目錄樹示例
根據絕對路徑或內定當前目錄展示資料夾
使用 TTreeView 控制項,並利用 StateIndex 或 ImageIndex 表示色階
-----------------------------------------------------------------------------}
unit DirectoryTree;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls, ExtCtrls, ShellAPI;
type
TForm1 = class(TForm)
TreeView1: TTreeView;
ButtonExpand: TButton;
ButtonCollapse: TButton;
EditPath: TEdit;
procedure FormCreate(Sender: TObject);
procedure ButtonExpandClick(Sender: TObject);
procedure ButtonCollapseClick(Sender: TObject);
private
procedure PopulateTreeView(const Path: string; ParentNode: TTreeNode);
procedure ExpandAllNodes(Node: TTreeNode);
procedure CollapseAllNodes(Node: TTreeNode);
procedure GetFolderSize(const Path: string; var Size: Int64);
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
if EditPath.Text = '' then
EditPath.Text := GetCurrentDir;
PopulateTreeView(EditPath.Text, nil);
end;
procedure TForm1.PopulateTreeView(const Path: string; ParentNode: TTreeNode);
var
SearchRec: TSearchRec;
NewNode: TTreeNode;
FolderSize: Int64;
begin
if FindFirst(Path + '\*', faAnyFile, SearchRec) = 0 then
begin
repeat
if ((SearchRec.Attr and faDirectory) = faDirectory) and (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
begin
NewNode := TreeView1.Items.AddChild(ParentNode, SearchRec.Name);
NewNode.ImageIndex := 0;
NewNode.SelectedIndex := 0;
FolderSize := 0;
GetFolderSize(Path + '\' + SearchRec.Name, FolderSize);
{ 根據 FolderSize 以 StateIndex 表示色階:
小於 1MB 使用淺色,介於 1MB 至 100MB 使用中間色,大於 100MB 則使用深色 }
if FolderSize < 1024 * 1024 then
NewNode.StateIndex := 1
else if FolderSize < 1024 * 1024 * 100 then
NewNode.StateIndex := 2
else
NewNode.StateIndex := 3;
PopulateTreeView(Path + '\' + SearchRec.Name, NewNode);
end;
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
end;
procedure TForm1.GetFolderSize(const Path: string; var Size: Int64);
var
SearchRec: TSearchRec;
begin
if FindFirst(Path + '\*', faAnyFile, SearchRec) = 0 then
begin
repeat
if (SearchRec.Attr and faDirectory) = faDirectory then
begin
if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
GetFolderSize(Path + '\' + SearchRec.Name, Size)
end
else
Inc(Size, SearchRec.Size);
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
end;
procedure TForm1.ButtonExpandClick(Sender: TObject);
begin
ExpandAllNodes(TreeView1.Items.GetFirstNode);
end;
procedure TForm1.ExpandAllNodes(Node: TTreeNode);
begin
if Node <> nil then
begin
Node.Expand(True);
ExpandAllNodes(Node.GetFirstChild);
ExpandAllNodes(Node.GetNextSibling);
end;
end;
procedure TForm1.ButtonCollapseClick(Sender: TObject);
begin
CollapseAllNodes(TreeView1.Items.GetFirstNode);
end;
procedure TForm1.CollapseAllNodes(Node: TTreeNode);
begin
if Node <> nil then
begin
Node.Collapse(True);
CollapseAllNodes(Node.GetFirstChild);
CollapseAllNodes(Node.GetNextSibling);
end;
end;
end.
FPC 程式碼示例
{-----------------------------------------------------------------------------
FPC 視覺目錄樹示例
此程式碼利用 Lazarus 提供的 TTreeView 控制項
-----------------------------------------------------------------------------}
program DirectoryTree;
uses
Classes, SysUtils, Forms, ComCtrls, StdCtrls, ExtCtrls, FileUtil;
type
TForm1 = class(TForm)
TreeView1: TTreeView;
ButtonExpand: TButton;
ButtonCollapse: TButton;
EditPath: TEdit;
procedure FormCreate(Sender: TObject);
procedure ButtonExpandClick(Sender: TObject);
procedure ButtonCollapseClick(Sender: TObject);
private
procedure PopulateTreeView(const Path: string; ParentNode: TTreeNode);
procedure ExpandAllNodes(Node: TTreeNode);
procedure CollapseAllNodes(Node: TTreeNode);
procedure GetFolderSize(const Path: string; var Size: Int64);
public
end;
var
Form1: TForm1;
procedure TForm1.FormCreate(Sender: TObject);
begin
if EditPath.Text = '' then
EditPath.Text := GetCurrentDir;
PopulateTreeView(EditPath.Text, nil);
end;
procedure TForm1.PopulateTreeView(const Path: string; ParentNode: TTreeNode);
var
SearchRec: TSearchRec;
NewNode: TTreeNode;
FolderSize: Int64;
begin
if FindFirst(Path + PathDelim + '*', faAnyFile, SearchRec) = 0 then
begin
repeat
if ((SearchRec.Attr and faDirectory) = faDirectory) and
(SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
begin
NewNode := TreeView1.Items.AddChild(ParentNode, SearchRec.Name);
NewNode.ImageIndex := 0;
NewNode.SelectedIndex := 0;
FolderSize := 0;
GetFolderSize(Path + PathDelim + SearchRec.Name, FolderSize);
{ 設定色階:依據資料夾大小調整節點外觀 }
if FolderSize < 1024 * 1024 then
NewNode.StateIndex := 1
else if FolderSize < 1024 * 1024 * 100 then
NewNode.StateIndex := 2
else
NewNode.StateIndex := 3;
PopulateTreeView(Path + PathDelim + SearchRec.Name, NewNode);
end;
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
end;
procedure TForm1.GetFolderSize(const Path: string; var Size: Int64);
var
SearchRec: TSearchRec;
begin
if FindFirst(Path + PathDelim + '*', faAnyFile, SearchRec) = 0 then
begin
repeat
if (SearchRec.Attr and faDirectory) = faDirectory then
begin
if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
GetFolderSize(Path + PathDelim + SearchRec.Name, Size)
end
else
Inc(Size, SearchRec.Size);
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
end;
procedure TForm1.ButtonExpandClick(Sender: TObject);
begin
ExpandAllNodes(TreeView1.Items.GetFirstNode);
end;
procedure TForm1.ExpandAllNodes(Node: TTreeNode);
begin
if Node <> nil then
begin
Node.Expand(True);
ExpandAllNodes(Node.GetFirstChild);
ExpandAllNodes(Node.GetNextSibling);
end;
end;
procedure TForm1.ButtonCollapseClick(Sender: TObject);
begin
CollapseAllNodes(TreeView1.Items.GetFirstNode);
end;
procedure TForm1.CollapseAllNodes(Node: TTreeNode);
begin
if Node <> nil then
begin
Node.Collapse(True);
CollapseAllNodes(Node.GetFirstChild);
CollapseAllNodes(Node.GetNextSibling);
end;
end;
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Python 實作
Python 範例使用內建的 tkinter 與 ttk 模組以建立 GUI,其中 Treeview 控制項支援節點展開與收合。程式碼中定義了如何遞迴遍歷目錄、計算資料夾大小並根據大小決定節點的背景色。當資料夾大小較大時,使用較深的色調表示;尺寸越小則呈現較淺的顏色。
Python 程式碼示例
# -*- coding: utf-8 -*-
"""
Python 視覺目錄樹示例
以絕對路徑作為參數(若無則使用當前目錄)
使用 tkinter 與 ttk 建立 GUI,並以色階顯示資料夾大小
"""
import os
import sys
import tkinter as tk
from tkinter import ttk
import colorsys
class DirectoryTreeApp:
def __init__(self, root_path='.'):
self.root = tk.Tk()
self.root.title("視覺目錄樹")
# 路徑輸入框
self.path_entry = tk.Entry(self.root, width=50)
self.path_entry.pack(pady=10)
self.path_entry.insert(0, os.path.abspath(root_path))
# 建立 Treeview 控制項
self.tree = ttk.Treeview(self.root)
self.tree.pack(expand=True, fill='both', padx=10, pady=10)
# 按鈕:全部展開與全部收合
btn_frame = tk.Frame(self.root)
btn_frame.pack(pady=5)
tk.Button(btn_frame, text="全部展開", command=self.expand_all).pack(side=tk.LEFT, padx=5)
tk.Button(btn_frame, text="全部收合", command=self.collapse_all).pack(side=tk.LEFT, padx=5)
self.populate_tree()
self.tree.bind('<<TreeviewOpen>>', self.on_tree_open)
def populate_tree(self):
path = self.path_entry.get()
if not os.path.isdir(path):
path = os.getcwd()
self.path_entry.delete(0, tk.END)
self.path_entry.insert(0, path)
self.tree.delete(*self.tree.get_children())
root_node = self.tree.insert('', 'end', text=os.path.basename(path), open=True, values=(self.get_folder_size(path),))
self.add_directory_to_tree(os.path.abspath(path), root_node)
def add_directory_to_tree(self, path, parent):
try:
entries = os.listdir(path)
except PermissionError:
return
# 先計算此層中各子目錄的大小,用以決定色階
dir_sizes = {}
max_size = 0
for entry in entries:
full_path = os.path.join(path, entry)
if os.path.isdir(full_path):
size = self.get_folder_size(full_path)
dir_sizes[entry] = size
max_size = max(max_size, size)
for entry in sorted(entries):
full_path = os.path.join(path, entry)
if os.path.isdir(full_path):
size = dir_sizes.get(entry, 0)
color = self.get_color_by_size(size, max_size)
node_id = self.tree.insert(parent, 'end', text=entry, open=False, values=(size,))
self.tree.tag_configure(color, background=color)
self.tree.item(node_id, tags=(color,))
# 預先在節點下加入空白占位,以支援展開事件
self.tree.insert(node_id, 'end', text='Loading...')
def get_folder_size(self, path):
total_size = 0
for dirpath, dirnames, filenames in os.walk(path):
for f in filenames:
fp = os.path.join(dirpath, f)
if os.path.exists(fp):
total_size += os.path.getsize(fp)
return total_size
def get_color_by_size(self, size, max_size):
if max_size == 0:
return '#FFFFFF'
# HSV 色彩轉換:以綠色作為基調,value 由 1 減少代表深色
hue = 0.4
saturation = 1
value = 1 - (size / max_size)
r, g, b = colorsys.hsv_to_rgb(hue, saturation, value)
return f'#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}'
def on_tree_open(self, event):
item = self.tree.focus()
# 若首次展開時發現只有個預設占位,就刪除占位並重新加載
children = self.tree.get_children(item)
if children and self.tree.item(children[0], 'text') == 'Loading...':
self.tree.delete(children[0])
# 重新加載子目錄
parent_path = self.compute_node_path(item)
self.add_directory_to_tree(parent_path, item)
def compute_node_path(self, item):
parts = []
while item:
parts.insert(0, self.tree.item(item, 'text'))
item = self.tree.parent(item)
return os.path.join(*parts)
def expand_all(self):
for item in self.tree.get_children(''):
self.tree.item(item, open=True)
self._expand_children(item)
def _expand_children(self, parent):
for child in self.tree.get_children(parent):
self.tree.item(child, open=True)
self._expand_children(child)
def collapse_all(self):
for item in self.tree.get_children(''):
self.tree.item(item, open=False)
self._collapse_children(item)
def _collapse_children(self, parent):
for child in self.tree.get_children(parent):
self.tree.item(child, open=False)
self._collapse_children(child)
def run(self):
self.root.mainloop()
if __name__ == '__main__':
path = sys.argv[1] if len(sys.argv) > 1 else '.'
app = DirectoryTreeApp(path)
app.run()
B4J 實作
B4J 為 Basic for Java 的跨平台版本,其應用程式同樣能使用樹狀結構元件(例如 B4XTree)來實現目錄樹。透過 B4J,可以將節點加入色階資訊(存放於 Tag 屬性中),並利用事件方法來展開與收合節點。
B4J 程式碼示例
// B4J 視覺目錄樹示例
Sub Process_Globals
Public App As B4XMainPage
Private xui As XUI
Private TreeView1 As B4XTree
Private TextFieldPath As B4XTextField
Private ButtonExpand, ButtonCollapse As B4XButton
End Sub
Sub App_Start (Args() As String)
App.Initialize("App")
End Sub
Sub App_Initialize (BA As B4AClass)
App.SetLayout("Layout1")
TreeView1.Initialize("TreeView1")
TreeView1.SetLayoutAnimated(0, 10dip, 10dip, App.Width - 20dip, App.Height - 100dip)
TextFieldPath.Initialize("TextFieldPath")
TextFieldPath.SetLayoutAnimated(0, 10dip, App.Height - 80dip, App.Width - 20dip, 40dip)
ButtonExpand.Initialize("ExpandAll")
ButtonExpand.SetLayoutAnimated(0, 10dip, App.Height - 30dip, 100dip, 40dip)
ButtonCollapse.Initialize("CollapseAll")
ButtonCollapse.SetLayoutAnimated(0, 120dip, App.Height - 30dip, 100dip, 40dip)
PopulateTreeView
End Sub
Sub PopulateTreeView
Dim path As String = TextFieldPath.Text
If path = "" Then path = File.DirApp
TextFieldPath.Text = path
TreeView1.Root.ClearChildren
AddDirectoryToTree(path, TreeView1.Root)
End Sub
Sub AddDirectoryToTree(path As String, parent As B4XTreeItem)
Dim items As List = File.ListFiles(path)
For Each item As String In items
Dim itemPath As String = File.Combine(path, item)
If File.IsDirectory(itemPath) Then
Dim folderSize As Long = GetFolderSize(itemPath)
Dim color As Int = GetColor(folderSize)
Dim node As B4XTreeItem = parent.AddItem(item)
node.Tag = color
AddDirectoryToTree(itemPath, node)
End If
Next
End Sub
Sub GetFolderSize(path As String) As Long
Dim totalSize As Long = 0
Dim files As List = File.ListFiles(path)
For Each file As String In files
Dim filePath As String = File.Combine(path, file)
If File.IsDirectory(filePath) Then
totalSize = totalSize + GetFolderSize(filePath)
Else
totalSize = totalSize + File.Size(filePath)
End If
Next
Return totalSize
End Sub
Sub GetColor(size As Long) As Int
If size < 1024 * 1024 Then
Return xui.Color_LightGray
Else If size < 1024 * 1024 * 100 Then
Return xui.Color_Gray
Else
Return xui.Color_DarkGray
End If
End Sub
Sub ExpandAll_Click
ExpandAllNodes(TreeView1.Root)
End Sub
Sub ExpandAllNodes(node As B4XTreeItem)
node.Expanded = True
For Each child As B4XTreeItem In node.Children
ExpandAllNodes(child)
Next
End Sub
Sub CollapseAll_Click
CollapseAllNodes(TreeView1.Root)
End Sub
Sub CollapseAllNodes(node As B4XTreeItem)
node.Expanded = False
For Each child As B4XTreeItem In node.Children
CollapseAllNodes(child)
Next
End Sub
系統整合與測試說明
四種語言的實作皆遵循下列流程:
1. 初始目錄設定
程式進入點先判斷是否有提供絕對路徑作為參數,若無則預設使用當前工作目錄。此部分為統一入口,可以根據環境變數或命令列引數取得初始路徑,並填入對應的 UI 元件(例如 Delphi 與 FPC 中的 TEdit,Python 中的 Entry)。
2. 遞迴建立目錄樹
透過遞迴函數,依據各層目錄遍歷建立 TreeView 節點。每個節點代表一個資料夾,並預先附加一個空白節點(或標記)以支援之後的展開操作。此方法不僅將第一層目錄呈現,也為後續展開操作保留彈性。
3. 資料夾大小與色階計算
節點的色階設定則根據遞迴計算得到的資料夾大小進行。透過簡單運算(例如在 Delphi 與 FPC 中小於 1MB、1MB~100MB、大於100MB三個區間;在 Python 中依據最大值產生色彩漸層),以視覺上反映該目錄內資料的豐富程度。
4. 全部展開與收合功能
各實作均提供展開與收合所有節點的按鈕。事件處理函數藉由遞迴呼叫將所有子節點設定為展開(或收合)狀態,使用者即可快速掌握整體結構或簡化視圖。
整合比較表
| 語言 | 主要元件 | 特色功能 |
|---|---|---|
| Delphi 7 | TTreeView, TEdit, TButton | 利用 StateIndex 表示色階;內建 API 遍歷目錄 |
| FPC | TTreeView, TEdit, TButton | 與 Delphi 類似,使用 Lazarus 框架實現 |
| Python | tkinter Entry, ttk.Treeview, Button | 使用顏色漸層表示大小,支援動態展開節點 |
| B4J | B4XTree, B4XTextField, B4XButton | 跨平台解決方案,利用 Tag 屬性作色階記錄 |
結論與最終建議
上述多語言實作範例展示了如何在 Windows 平台以絕對路徑或預設當前目錄生成一個視覺化的目錄樹。無論是 Delphi 7、FPC、Python 還是 B4J,每種語言均能滿足以下需求:
- 初始化時展示第一層目錄,並支持各層節點展開與收合
- 根據資料夾內資料量採用色階表示,提供直觀的視覺反饋
- 具備一鍵全部展開與全部收合的功能,便於整體瀏覽
- 均未依賴第三方庫,只利用各自內建的 GUI 控件與系統函式
根據開發需求與目標平台的不同,您可以選擇其中一種或多個實作方式。 Delphi 7 與 FPC 適合 Windows 原生桌面應用程式,Python 則提供跨平台與快速開發的優勢,而 B4J 則更適合 Java 基礎的跨平台解決方案。各方案皆提供參考依據,並可進一步作客製化調整以滿足實際業務需求。
參考資料
推薦探索相關查詢
Last updated February 21, 2025