第 ⑤ 章 · 图标处理器与图标叠加
用 IExtractIcon 让同类文件逐个显示不同图标(GIL_PERINSTANCE 是关键),并讲 IShellIconOverlayIdentifier 图标叠加与它著名的“15 个槽位”限制。
注册表能给一整类文件配一个固定图标,但做不到逐文件不同。要“这个 .txt 一个图标、那个 .txt 另一个图标”,就得写图标处理器。本章还捎带讲图标叠加(就是同步盘、Git 客户端在文件角上加的那个小箭头/对勾)。
配套代码:05-icon。
IExtractIcon:逐文件图标
两个接口配合:IPersistFile::Load 拿到文件路径,IExtractIconW 决定图标。
IFACEMETHODIMP IconHandler::GetIconLocation(UINT, PWSTR pszIconFile, UINT cchMax,
int* piIndex, UINT* pwFlags) {
ULONGLONG size = /* 用 GetFileAttributesEx 取 m_szFile 大小 */;
wchar_t sysdir[MAX_PATH]; GetSystemDirectoryW(sysdir, ARRAYSIZE(sysdir));
StringCchPrintfW(pszIconFile, cchMax, L"%s\\shell32.dll", sysdir);
*piIndex = (size < 1024) ? 71 : (size < 1024*1024) ? 70 : 47; // 按大小选图标
*pwFlags = GIL_PERINSTANCE; // ★ 关键
return S_OK;
}
IFACEMETHODIMP IconHandler::Extract(PCWSTR, UINT, HICON* l, HICON* s, UINT) {
if (l) *l = nullptr; if (s) *s = nullptr;
return S_FALSE; // 让 Shell 按上面给的“文件+索引”自己加载
}GIL_PERINSTANCE 不能漏。不置它,Shell 会认为“同类型文件图标都一样”,把图标按类型缓存成一个——逐文件就白做了。
GetIconLocation 返回一个“文件+索引”,Extract 返回 S_FALSE 让 Shell 自行加载即可;只有当图标需要动态生成(不是现成的资源)时,才在 Extract 里亲自返回 HICON。
注册在 ProgID 的 shellex\IconHandler 下:
RegWriteString(HKEY_CLASSES_ROOT, L"txtfile\\shellex\\IconHandler", nullptr, clsidStr);IExtractIcon 现在也不再强制 IPersistFile——现代可改用 IInitializeWithItem 拿 IShellItem。本例用 IPersistFile 因为它对 IconHandler 最通用、最省事。
图标叠加:IShellIconOverlayIdentifier
叠加图标不针对某类文件,而是全局注册、由系统对每个图标询问“你要不要在这个文件上叠个小图标?”。接口只有三个方法,且不需要初始化接口(路径直接给到 IsMemberOf):
class OverlayHandler : public IShellIconOverlayIdentifier {
// 这个文件要不要叠加?(本例:只读文件才叠)
IFACEMETHODIMP IsMemberOf(PCWSTR pwszPath, DWORD dwAttrib) {
return (dwAttrib & FILE_ATTRIBUTE_READONLY) ? S_OK : S_FALSE;
}
// 叠加用哪个图标
IFACEMETHODIMP GetOverlayInfo(PWSTR pwszIconFile, int cchMax, int* pIndex, DWORD* pdwFlags) {
wchar_t sysdir[MAX_PATH]; GetSystemDirectoryW(sysdir, ARRAYSIZE(sysdir));
StringCchPrintfW(pwszIconFile, cchMax, L"%s\\shell32.dll", sysdir);
*pIndex = 47;
*pdwFlags = ISIOI_ICONFILE | ISIOI_ICONINDEX;
return S_OK;
}
IFACEMETHODIMP GetPriority(int* pPriority) { *pPriority = 50; return S_OK; }
};注册在 HKLM(不是 HKCR)的 overlay 列表下:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\
<名字> (默认) = {CLSID}著名的“15 个槽位”限制:整个系统的图标叠加总共只有 15 个名额,超出的直接被忽略。而且这些项按名字的字母序排队——这就是为什么 OneDrive、Dropbox、TortoiseGit 的叠加名都带一堆前导空格( OneDrive)来抢排在前面。改动 overlay 注册后必须重启资源管理器才生效。
叠加处理器的代码骨架(类工厂、DLL 导出、注册)和本章的图标处理器完全一样,只是实现的接口和注册位置不同——照着
05-icon改即可。