前言
利用 TTreeView 顯示目錄階層、TListView 顯示檔案清單,是檔案管理器的經典 UI 模式。本文提供完整的 C++Builder 實作:遞歸掃描磁碟機建構樹狀結構、節點點擊動態載入檔案清單、圖示關聯與日期格式化。
核心資料結構與變數
TTreeNode* currNode; // 遞歸過程中暫存當前父節點
int DirCount, FileCount, DriveCount;
遞歸掃描目錄樹:TreeInit
void TForm1::TreeInit(String path, TTreeNode* myNode)
{
TSearchRec sr;
// FindFirst 返回 0 表成功
if (FindFirst(path + "\\*.*", faAnyFile, sr) == 0)
{
do
{
// 只處理目錄,跳過 "." ".."
if ((sr.Attr & faDirectory) == faDirectory)
{
if (sr.Name != "." && sr.Name != "..")
{
currNode = TreeView1->Items->AddChild(myNode, sr.Name);
TreeInit(path + "\\" + sr.Name, currNode); // 遞歸
DirCount++;
}
}
// 檔案不在樹中顯示,由 ListView 處理
}
while (FindNext(sr) == 0);
FindClose(sr); // 必須關閉搜尋控制代碼
}
}
表單建立:掛載磁碟機根節點
void __fastcall TForm1::FormCreate(TObject *Sender)
{
String Drive, path;
DirCount = FileCount = 0;
DriveCount = DriveComboBox1->Items->Count;
TreeView1->Items->BeginUpdate(); // 凍結 UI 更新,大幅提升效能
TreeView1->Items->Clear();
// 根節點:「我的電腦」
currNode = TreeView1->Items->Add(TreeView1->Selected, "我的電腦");
currNode->Selected = true;
// 遍歷 DriveComboBox 取得所有磁碟機
for (int i = 0; i < DriveCount; i++)
{
Drive = DriveComboBox1->Items->Strings[i].SubString(1, 1); // 取得 "C" "D" ...
path = Drive + ":";
currNode = TreeView1->Items->AddChild(TreeView1->Selected,
DriveComboBox1->Items->Strings[i]); // 顯示完整磁碟機名稱
TreeInit(path, currNode); // 遞歸掃描該磁碟機
}
TreeView1->Items->EndUpdate(); // 解除凍結,一次性重繪
}
樹節點點擊:動態載入檔案清單至 ListView
void __fastcall TForm1::TreeView1Click(TObject *Sender)
{
TTreeNode* node = TreeView1->Selected;
String path;
// 1. 由節點向上遞迴組合完整路徑
if (node->Level > 0) // 非根節點
{
do
{
if (node->Level == 1) // 磁碟機節點:文字如 "C: [標籤]"
path = node->Text.SubString(1, 2) + (path.IsEmpty() ? "" : "\\") + path;
else
path = node->Text + (path.IsEmpty() ? "" : "\\") + path;
node = node->Parent;
}
while (node->Level > 0);
}
else
{
// 點擊根節點「我的電腦」:顯示磁碟機清單
ListView1->Items->BeginUpdate();
ListView1->Items->Clear();
for (int i = 0; i < TreeView1->Selected->Count; i++)
{
TListItem* li = ListView1->Items->Add();
li->Caption = TreeView1->Selected->Item[i]->Text;
}
ListView1->Items->EndUpdate();
return;
}
// 2. 掃描路徑下檔案填充 ListView
ListView1->Items->BeginUpdate();
ListView1->Items->Clear();
TSearchRec sr;
if (FindFirst(path + "\\*.*", faAnyFile, sr) == 0)
{
do
{
if (sr.Name != "." && sr.Name != "..")
{
TListItem* li = ListView1->Items->Add();
li->Caption = sr.Name;
if (sr.Attr == faDirectory)
{
li->SubItems->Add(""); // 大小欄留白
li->SubItems->Add("資料夾"); // 類型欄
}
else
{
li->SubItems->Add(IntToStr(sr.Size)); // 大小
li->SubItems->Add("檔案"); // 類型
}
// 日期時間格式化
li->SubItems->Add(FileDateToDateTime(sr.Time).DateTimeString());
}
}
while (FindNext(sr) == 0);
FindClose(sr);
}
ListView1->AlphaSort(); // 依名稱排序
ListView1->Items->EndUpdate();
}
關鍵技術點總結
| 技術 | 用途 | 備註 |
| TDriveComboBox | 自動列舉系統磁碟機 | 無需手動 GetLogicalDrives |
| BeginUpdate/EndUpdate | 凍結/解除 UI 重繪 | 大量節點/項目操作時必加,否則極慢 |
| FindFirst/FindNext/FindClose | VCL 封裝 FindFirstFile 等 API | 跨平台相容,自動處理 ANSI/Unicode |
| TSearchRec.Attr | 檔案屬性位元組 | faDirectory, faHidden, faSysFile, faReadOnly 等 |
| FileDateToDateTime | DOS 日期時間轉 TDateTime | 再用 DateTimeString() 格式化顯示 |
| AlphaSort | ListView 依 Caption 字母排序 | 報表模式可用 CustomSort 自訂比較 |
效能優化建議
- 虛擬模式:
ListView1->OwnerData = true,處理 OnData 事件按需提供資料,支援百萬級項目。
- 背景執行緒掃描:大型磁碟機掃描會阻塞 UI,移至
TThread 執行,完成後 Synchronize 更新 TreeView。
- 圖示快取:使用
SHGetFileInfo + SHGFI_SYSICONINDEX 取得系統圖示索引,避免重複提取 HICON。
- 延遲載入:樹節點展開時 (
OnExpanding) 再掃描子目錄,而非啟動時全掃。
常見問題排查
| 現象 | 原因 | 解決 |
| 樹節點重複/無限遞歸 | 未正確過濾 "." ".." 或符號連結 | 加入 sr.Name != "." && sr.Name != "..",檢查 faSymLink |
| ListView 不顯示檔案 | 路徑組合錯誤(雙反斜線、缺少磁碟機代號) | 除錯輸出 path 變數,確認格式 "C:\\Folder\\Sub" |
| 日期顯示 1601/1/1 | FileDateToDateTime 失敗(sr.Time 為 0 或無效) | 檢查 FindFirst 返回值,確認 sr.Time 有效 |
| 網路磁碟機掃描卡死 | 同步阻塞、逾時無回應 | 移至背景執行緒,設定網路逾時 |
這套 TTreeView + TListView + 遞歸 FindFirst 的架構,是 C++Builder 開發檔案管理類應用的標準範本,掌握後可輕鬆擴充為多欄位報表、縮圖預覽、檔案搜尋等進階功能。
- 上一篇文章: C++ 開發者轉型 C# 必知的 10 大陷阱(上):解構子、Finalize 與 IDisposable
- 下一篇文章: PE 可執行檔格式深度解析與修改實戰
Copyright ©
程式碼之家 All Rights Reserved