第 ⑤ 章 · 图标处理器与图标叠加

用 IExtractIcon 让同类文件逐个显示不同图标(GIL_PERINSTANCE 是关键),并讲 IShellIconOverlayIdentifier 图标叠加与它著名的“15 个槽位”限制。

作者 David更新于

注册表能给一整类文件配一个固定图标,但做不到逐文件不同。要“这个 .txt 一个图标、那个 .txt 另一个图标”,就得写图标处理器。本章还捎带讲图标叠加(就是同步盘、Git 客户端在文件角上加的那个小箭头/对勾)。

配套代码:05-icon。

IExtractIcon:逐文件图标

两个接口配合:IPersistFile::Load 拿到文件路径,IExtractIconW 决定图标。

IconHandler.cpp(节选)
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 改即可。

参考

本页目录