在 C++ Builder 開發中,目錄 (資料夾) 操作是高頻需求。VCL 雖提供 ForceDirectories、RemoveDir 等封裝,但缺乏「判斷目錄存在」、「圖形化選擇目錄」、「多級目錄一次建立/刪除」的直觀方法。本文系統整理四大核心場景,提供基於 Windows API 的完整實作程式碼,並解析關鍵細節與遞歸邏輯。
VCL 的 FileExists 僅適用檔案。目錄判斷需用 FindFirstFile 搭配 FILE_ATTRIBUTE_DIRECTORY 屬性檢查。
bool DirectoryExistsEx(const char* Dir) {
if (!Dir || !*Dir) return false;
// 移除結尾反斜線 (根目錄除外)
int len = strlen(Dir);
if (len > 3 && Dir[len-1] == '\\') len--;
WIN32_FIND_DATA wfd;
HANDLE hFind = FindFirstFile(Dir, &wfd);
if (hFind == INVALID_HANDLE_VALUE) return false;
FindClose(hFind);
return (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
關鍵點:根目錄 (如 C:\\) 不可移除反斜線,否則 FindFirstFile 會失敗。
專業軟體常在編輯框旁放置「瀏覽」按鈕,彈出樹狀目錄選擇對話框。核心 API 為 SHBrowseForFolder,需填寫 BROWSEINFO 結構。
#include <shlobj.h>
AnsiString SelectDirectory(const AnsiString& title = "請選擇目錄") {
char szSelected[MAX_PATH] = {0};
char szDisplayName[MAX_PATH] = {0};
BROWSEINFO bi = {0};
bi.hwndOwner = Application->Handle;
bi.pszDisplayName = szDisplayName;
bi.lpszTitle = title.c_str();
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; // 現代樣式、僅檔案系統目錄
LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
if (pidl) {
SHGetPathFromIDList(pidl, szSelected);
CoTaskMemFree(pidl); // 必須釋放 PIDL 記憶體
return AnsiString(szSelected);
}
return "";
}
旗標說明:BIF_NEWDIALOGSTYLE (IE5+) 提供可調整大小、支援拖放的現代對話框;BIF_EDITBOX 允許直接輸入路徑。
Windows CreateDirectory 要求父目錄存在。遞歸函式 MakeDirectoryEx 逐層檢查並建立父目錄。
bool MakeDirectoryEx(const AnsiString& Path) {
if (Path.IsEmpty()) return false;
AnsiString dir = Path;
// 移除結尾反斜線
if (!dir.IsEmpty() && dir[dir.Length()] == '\\') dir = dir.SubString(1, dir.Length()-1);
// 尋找父目錄
AnsiString parent;
for (int i = dir.Length(); i > 0; i--) {
if (dir[i] == '\\' || dir[i] == '/') {
parent = dir.SubString(1, i-1);
break;
}
}
if (parent.IsEmpty()) return false; // 根目錄或錯誤路徑
// 遞歸建立父目錄
if (parent.Length() > 3) { // 非磁碟根目錄
if (!DirectoryExistsEx(parent.c_str())) {
if (!MakeDirectoryEx(parent)) return false;
}
}
// 建立當前目錄
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
return CreateDirectory(dir.c_str(), &sa);
}
現代替代:VCL 實際上已提供 ForceDirectories(AnsiString) 一鍵完成此功能,本程式碼主要供理解遞歸邏輯或無 VCL 環境時參考。
類似 DOS deltree,需遞歸枚舉子目錄/檔案並刪除,最後刪除空目錄。
bool DeleteDirectoryEx(const AnsiString& Path) {
if (Path.IsEmpty() || Path.Length() < 4) return false; // 保護根目錄
AnsiString dir = Path;
if (dir[dir.Length()] != '\\') dir += '\\';
AnsiString search = dir + "*.*";
WIN32_FIND_DATA wfd;
HANDLE hFind = FindFirstFile(search.c_str(), &wfd);
bool success = true;
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (strcmp(wfd.cFileName, ".") == 0 || strcmp(wfd.cFileName, "..") == 0) continue;
AnsiString fullPath = dir + wfd.cFileName;
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
// 遞歸刪除子目錄
success = success && DeleteDirectoryEx(fullPath);
} else {
// 強制刪除檔案 (移除唯讀/隱藏/系統屬性)
SetFileAttributes(fullPath.c_str(), FILE_ATTRIBUTE_NORMAL);
success = success && DeleteFile(fullPath.c_str());
}
} while (FindNextFile(hFind, &wfd) && success);
FindClose(hFind);
}
if (success) return RemoveDirectory(Path.c_str());
return false;
}
MAX_PATH 限制。使用 \\?\C:\Very\Long\Path 前綴可繞過 (API 需支援)。FILE_ATTRIBUTE_REPARSE_POINT 並跳過或特殊處理。DeleteFile 會失敗。可嘗試 MoveFileEx(..., MOVEFILE_DELAY_UNTIL_REBOOT) 排程重啟刪除。HANDLE,務必在所有返回路徑呼叫 FindClose。掌握 FindFirstFile/FindNextFile 枚舉模式與遞歸思想,是處理目錄樹操作的關鍵。C++ Builder 開發者日常建議優先使用 VCL 封裝 (ForceDirectories, TDirectory 等),但在需要精細控制 (權屬、過濾、進度回呼) 或無 VCL 環境時,本文提供的 Windows API 直接實作是可靠的基礎。