前言
實現檔案拖放功能的核心在於:取得檔案路徑、目標路徑、執行複製/移動、提取系統圖示並顯示於 UI。本文示範如何使用 SHGetFileInfo 取得檔案系統圖示索引,並配合 TListView 顯示圖示與檔名,打造類似總管的檔案列表介面。
關鍵 API:SHGetFileInfo
SHFILEINFO FileInfo;
HIMAGELIST hImgList;
// 1. 取得系統圖示清單控制代碼(大圖示)
hImgList = (HIMAGELIST)SHGetFileInfo(
"C:\\Windows\\Notepad.exe", // 任意存在檔案即可初始化系統圖示清單
0,
&FileInfo, sizeof(FileInfo),
SHGFI_SYSICONINDEX | SHGFI_LARGEICON
);
// 2. 將系統圖示清單關聯至 ListView
SendMessage(ListView1->Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, (LPARAM)hImgList);
新增項目並指定圖示索引
// 取得特定檔案的圖示索引
SHGetFileInfo(
"C:\\Windows\\Notepad.exe",
0, &FileInfo, sizeof(FileInfo),
SHGFI_ICON | SHGFI_LARGEICON // 注意:此處用 SHGFI_ICON 取得實際圖示控制代碼
);
// FileInfo.iIcon 即為系統圖示清單中的索引
TListItem* item = ListView1->Items->Add();
item->Caption = "Notepad.exe";
item->ImageIndex = FileInfo.iIcon;
// 另一個檔案
SHGetFileInfo(
"C:\\Windows\\System32\\calc.exe",
0, &FileInfo, sizeof(FileInfo),
SHGFI_ICON | SHGFI_LARGEICON
);
item = ListView1->Items->Add();
item->Caption = "Calc.exe";
item->ImageIndex = FileInfo.iIcon;
完整拖放流程實作架構
| 步驟 | 關鍵 API / 邏輯 | 說明 |
| 1. 來源檔案路徑 | 拖曳來源:WM_DROPFILES、DragQueryFile、或 OLE IDropTarget | 取得被拖曳檔案完整路徑陣列 |
| 2. 目標路徑 | Drop 目標:SHBrowseForFolder、或 ListView 滑鼠座標對應項目 | 決定檔案要放往何處 |
| 3. 執行複製/移動 | CopyFile、MoveFileEx、SHFileOperation | 實際檔案系統操作 |
| 4. 取得圖示 | SHGetFileInfo + SHGFI_SYSICONINDEX | 取得系統圖示清單索引,非載入實體圖示 |
| 5. 顯示於 UI | TListView + LVM_SETIMAGELIST | 將系統圖示清單綁定 ListView,項目指定 ImageIndex |
SHGetFileInfo 旗標組合速查
| 旗標 | 用途 | 備註 |
| SHGFI_ICON | 取得圖示控制代碼 (HICON) | 需自行 DestroyIcon |
| SHGFI_SYSICONINDEX | 取得系統圖示清單索引 (iIcon) | 效能最佳,適合 ListView 大量項目 |
| SHGFI_LARGEICON | 32x32 大圖示 | 配合 LVSIL_NORMAL |
| SHGFI_SMALLICON | 16x16 小圖示 | 配合 LVSIL_SMALL |
| SHGFI_OPENICON | 取得「開啟狀態」圖示(資料夾打開) | 較少用 |
| SHGFI_USEFILEATTRIBUTES | 不需檔案實體存在,僅依屬性/副檔名取得圖示 | 適合虛擬清單 |
ListView 圖示模式設定
// 表單建立時
ListView1->ViewStyle = vsIcon; // 或 vsSmallIcon, vsList, vsReport
ListView1->LargeImages = NULL; // 稍後動態指派
ListView1->SmallImages = NULL;
// 取得系統圖示清單(一次即可,全程複用)
HIMAGELIST hLarge = (HIMAGELIST)SHGetFileInfo("C:\\", 0, &FileInfo, sizeof(FileInfo),
SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
HIMAGELIST hSmall = (HIMAGELIST)SHGetFileInfo("C:\\", 0, &FileInfo, sizeof(FileInfo),
SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
SendMessage(ListView1->Handle, LVM_SETIMAGELIST, LVSIL_NORMAL, (LPARAM)hLarge);
SendMessage(ListView1->Handle, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)hSmall);
虛擬清單模式(大量檔案效能優化)
- 設定
ListView1->OwnerData = true。
- 處理
OnData 事件,動態提供 Caption、ImageIndex。
- 搭配
SHGFI_USEFILEATTRIBUTES 避免逐一 SHGetFileInfo 呼叫檔案系統。
常見問題與解決
| 現象 | 原因 | 解決 |
| 圖示顯示為空白 | 未正確關聯 ImageList 或 ImageIndex 超界 | 檢查 LVM_SETIMAGELIST 參數、確認 iIcon 有效 |
| 所有檔案顯示相同圖示 | 重複使用同一 FileInfo 變數未重新呼叫 SHGetFileInfo | 每個檔案單獨呼叫 SHGetFileInfo 取得各自 iIcon |
| 圖示模糊/尺寸錯誤 | Large/Small ImageList 對應錯誤 | vsIcon 用 LargeImages (LVSIL_NORMAL),vsSmallIcon 用 SmallImages (LVSIL_SMALL) |
| SHGetFileInfo 返回 0 | 路徑不存在、權限不足、旗標衝突 | 檢查 GetLastError,嘗試 SHGFI_USEFILEATTRIBUTES |
拖放實作補充:WM_DROPFILES 簡易版
// 表單啟用拖放
DragAcceptFiles(Handle, TRUE);
// 處理 WM_DROPFILES 訊息
void __fastcall TForm1::WMDropFiles(TMessage &Msg)
{
HDROP hDrop = (HDROP)Msg.wParam;
UINT fileCount = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
for (UINT i = 0; i < fileCount; ++i)
{
char path[MAX_PATH];
DragQueryFile(hDrop, i, path, MAX_PATH);
// 取得圖示索引並加入 ListView
SHFILEINFO fi;
SHGetFileInfo(path, 0, &fi, sizeof(fi), SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
TListItem* item = ListView1->Items->Add();
item->Caption = ExtractFileName(path);
item->ImageIndex = fi.iIcon;
item->Data = (void*)StrNew(path); // 保存完整路徑
}
DragFinish(hDrop);
}
掌握 SHGetFileInfo + 系統圖示清單 + TListView 的組合,即可輕鬆打造具備原生檔案圖示顯示的專業級檔案管理介面。