技術棧
- 虛擬清單模式 (LVS_OWNERDATA):按需獲取顯示資訊,支援大量檔案不卡頓。
- 自訂繪製 (NM_CUSTOMDRAW):在圖示區域繪製縮圖,替換系統預設圖示。
- Shell 介面:
IShellFolder、IShellIcon、IShellIconOverlay、ILCombine、OleLoadPicturePath。
核心實作步驟
1. 建立虛擬清單視圖
BOOL CThumbView::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style &= ~LVS_TYPEMASK;
cs.style |= LVS_ICON | LVS_OWNERDATA; // 圖示模式 + 虛擬模式
return CListView::PreCreateWindow(cs);
}
void CThumbView::OnInitialUpdate()
{
CListView::OnInitialUpdate();
GetListCtrl().SetCallbackMask(LVIS_OVERLAYMASK); // 動態 Overlay 圖示
}
2. 緩存顯示資訊 (LVN_ODCACHEHINT / LVN_GETDISPINFO)
// 預載入可視範圍前後各 5 項
void CThumbView::OnOdcachehint(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVCACHEHINT* pHint = (NMLVCACHEHINT*)pNMHDR;
PrepCache(0, min(5, m_items.GetSize()));
PrepCache(pHint->iFrom, pHint->iTo);
PrepCache(max(0, m_items.GetSize()-5), m_items.GetSize());
*pResult = 0;
}
// 提供單項顯示資訊 (文字、圖示、Overlay)
void CThumbView::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pInfo = (LV_DISPINFO*)pNMHDR;
if (pInfo->item.iItem == -1) return;
LPITEMIDLIST pidl = m_items[pInfo->item.iItem];
CFolderItemInfo* pCache = FindInCache(pidl);
bool bCached = true;
if (!pCache) { pCache = new CFolderItemInfo; GetItemInfo(pidl, pCache); bCached = false; }
if (pInfo->item.mask & LVIF_TEXT)
lstrcpyn(pInfo->item.pszText, pCache->szDisplayName, pInfo->item.cchTextMax);
if (pInfo->item.mask & LVIF_IMAGE)
pInfo->item.iImage = pCache->iIcon;
if (pInfo->item.mask & LVIF_STATE)
pInfo->item.state = pCache->state; // Overlay 狀態
if (!bCached) delete pCache;
*pResult = 0;
}
3. 取得檔案完整資訊 (系統圖示、Overlay、縮圖)
void CThumbView::GetItemInfo(LPCITEMIDLIST pidl, CFolderItemInfo* pInfo)
{
// 1. 顯示名稱
theApp.SHGetDisplayNameOf(pidl, pInfo->szDisplayName);
// 2. 系統圖示索引
IShellIcon* pIcon = NULL;
if (SUCCEEDED(m_pFolder->QueryInterface(IID_IShellIcon, (void**)&pIcon)))
{
pIcon->GetIconOf(pidl, 0, &pInfo->iIcon);
pIcon->Release();
}
// 3. Overlay 圖示 (捷徑箭頭、共享手掌等)
IShellIconOverlay* pOverlay = NULL;
if (SUCCEEDED(m_pFolder->QueryInterface(IID_IShellIconOverlay, (void**)&pOverlay)))
{
int nOverlay = 0;
pOverlay->GetOverlayIndex(pidl, &nOverlay);
pInfo->state = INDEXTOOVERLAYMASK(nOverlay);
pOverlay->Release();
}
// 4. 縮圖載入 (僅圖片檔)
LPITEMIDLIST pidlFull = ILCombine(m_pidlFolder, pidl);
if (pidlFull && SHGetPathFromIDList(pidlFull, pInfo->szPath))
{
HRESULT hr = OleLoadPicturePath(T2OLE(pInfo->szPath), NULL, 0, RGB(255,255,255),
IID_IPicture, (void**)&pInfo->pic.m_pPict);
if (FAILED(hr)) pInfo->bFailLoadPic = TRUE;
}
m_pMalloc->Free(pidlFull);
}
4. 自繪縮圖 (NM_CUSTOMDRAW - CDDS_ITEMPOSTPAINT)
void CThumbView::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMLVCUSTOMDRAW pDraw = (LPNMLVCUSTOMDRAW)pNMHDR;
switch (pDraw->nmcd.dwDrawStage)
{
case CDDS_PREPAINT: *pResult = CDRF_NOTIFYITEMDRAW; break;
case CDDS_ITEMPREPAINT: *pResult = CDRF_NOTIFYPOSTPAINT; break;
case CDDS_ITEMPOSTPAINT:
{
int iItem = (int)pDraw->nmcd.dwItemSpec;
CFolderItemInfo* pInfo = FindInCache(m_items[iItem]);
if (!pInfo || pInfo->bFailLoadPic || !pInfo->pic.m_pPict)
{ *pResult = CDRF_DODEFAULT; break; }
CRect rcIcon; GetListCtrl().GetItemRect(iItem, &rcIcon, LVIR_ICON);
CDC* pDC = CDC::FromHandle(pDraw->nmcd.hdc);
pInfo->pic.Render(pDC, rcIcon, rcIcon); // IPicture::Render 繪製
*pResult = CDRF_NEWFONT;
}
break;
}
*pResult = 0;
}
5. 目錄切換與 PIDL 管理
void CThumbView::EnterFolder(LPCITEMIDLIST pidl)
{
m_pidlFolder = ILClone(pidl);
m_pFolder->Release(); // 釋放舊介面
m_items.RemoveAll(); m_mapCache.RemoveAll();
m_pDesktop->BindToObject(m_pidlFolder, NULL, IID_IShellFolder, (void**)&m_pFolder);
IEnumIDList* pEnum; m_pFolder->EnumObjects(NULL, SHCONTF_FOLDERS|SHCONTF_NONFOLDERS, &pEnum);
LPITEMIDLIST pidlItem; ULONG fetched;
while (pEnum->Next(1, &pidlItem, &fetched) == S_OK && fetched == 1)
m_items.Add(pidlItem);
pEnum->Release();
GetListCtrl().SetItemCount(m_items.GetSize());
}
效能優化建議
- 自訂圖示尺寸,完全自繪項目避免拉伸開銷。
- 背景執行緒非同步載入圖片,載入完成發送
RedrawItems。
- 預先產生符合圖示尺寸的縮圖快取,繪製時直接 BitBlt。