Chat
Ask me anything
Ithy Logo
Sign in to save

Windows視覺目錄樹實作

多語言實例:Delphi7, FPC, Python與B4J

windows folder structure tree

主要亮點

  • 根據絕對路徑或當前目錄初始化視覺目錄樹
  • 第一層目錄先行展示,各層節點支援展開與收合
  • 以色階顯示資料夾內資料量,提供全部展開及收合功能

概覽與需求說明

本文結合多種程式語言示範如何在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 基礎的跨平台解決方案。各方案皆提供參考依據,並可進一步作客製化調整以滿足實際業務需求。


參考資料


推薦探索相關查詢

embarcadero-delphi.softonic.cn
Delphi 版- 下载
allaboutdataanalysis.medium.com
資料視覺化配色八項原則. | Medium

Last updated February 21, 2025

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI
Download Article
Delete Article