Tablacus Explorerとアドオンの「アイコンオーバーレイ」で実現したアイコンオーバーレイの上限を超えたアイコンを表示する方法を紹介します。
#大雑把に言うと
レジストリからアイコンオーバーレイハンドラーを取得してカスタムドローでアイコンの上にアイコンオーバーレイを表示させています。
#一般的なアイコンオーバーレイの表示方法
- IShellIconOverlay::GetOverlayIndexでオーバーレイインデックスを取得
-
ImageList_Draw で fStyleに
INDEXTOOVERLAYMASK を付けてアイコンを表示
このイメージリスト の ImageList_SetOverlayImage で設定できるアイコンオーバーレイの数がVersion 4.70以前が4個、Version 4.71以降が15個となっていてここがアイコンオーバーレイの限界になっています。
最初からショートカットやファイル共有のアイコンオーバーレイもこの15個の中に入りますし、現在では一つのシェル拡張でも10近いアイコンオーバーレイが登録されるのであっという間に上限を超えます。
#解説
##1. レジストリからアイコンオーバーレイハンドラー(IShellIconOverlayIdentifier) を取得
ticonoverlay.cpp 700行付近
アイコンオーバーレイハンドラーのレジストリ(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers) をRegOpenKeyEx と RegEnumKeyEx で列挙する
RegQueryValueEx で個々のアイコンオーバーレイハンドラーのクラスIDの文字列を取得
CLSIDFromString で文字列からクラスIDに変換
CoCreateInstance でクラスIDを使用してアイコンオーバーレイハンドラー(IShellIconOverlayIdentifier) を取得
IShellIconOverlayIdentifier::GetOverlayInfo でアイコンが取得できることを確認してリストに積む
//Init
case 0x60010000:
if (nArg >= 0) {
DWORD dwIndex = GetIntFromVariant(&pDispParams->rgvarg[nArg]);
for (int i = g_pIconOverlayHandlers.size(); i-- > 0;) {
SafeRelease(&g_pIconOverlayHandlers[i]);
}
g_pIconOverlayHandlers.clear();
TCHAR pszName[MAX_PATH * 2], pszClsid[MAX_PATH];
DWORD dwNameSize = MAX_PATH;
DWORD dwSize = sizeof(pszClsid);
FILETIME ftLastWriteTime;
HKEY hKey, hKey1;
LPWSTR pszKey = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers";
lstrcpy(pszName, pszKey);
lstrcat(pszName, L"\\");
int nName = lstrlen(pszName);
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszKey, 0, KEY_ENUMERATE_SUB_KEYS, &hKey) == ERROR_SUCCESS) {
LONG lRet;
while ((lRet = RegEnumKeyEx(hKey, dwIndex++, &pszName[nName], &dwNameSize, NULL, NULL, NULL, &ftLastWriteTime)) != ERROR_NO_MORE_ITEMS) {
if (lRet == ERROR_SUCCESS) {
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszName, 0, KEY_READ, &hKey1) == ERROR_SUCCESS) {
if (RegQueryValueEx(hKey1, NULL, NULL, NULL, (LPBYTE)&pszClsid, &dwSize) == ERROR_SUCCESS) {
CLSID clsid;
if SUCCEEDED(CLSIDFromString(pszClsid, &clsid)) {
IShellIconOverlayIdentifier *psio;
if SUCCEEDED(CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psio))) {
TCHAR pszIconFile[MAX_PATH * 2];
int iIndex;
DWORD dwFlags;
if (psio->GetOverlayInfo(pszIconFile, sizeof(pszIconFile), &iIndex, &dwFlags) == S_OK) {
g_pIconOverlayHandlers.push_back(psio);
}
}
}
}
RegCloseKey(hKey1);
}
dwNameSize = MAX_PATH;
}
}
RegCloseKey(hKey);
}
}
return S_OK;
##2. アイコンオーバーレイハンドラーからアイコンを取得して、画像でリストに保存
script.js 25行付近、ticonoverlay.cpp 743行-772行
IShellIconOverlayIdentifier::GetOverlayInfo で個々のアイコン情報を取得して画像でリストに保存
for (var i = 0, o = {}; Addons.IconOverlay.DLL.GetOverlayInfo(i, o) == S_OK; i++) {
Addons.IconOverlay.Icon[i] = [MakeImgData("icon:" + o.IconFile + (o.dwFlags == 1 ? "" : "," + o.Index), 0, 16), o.dwFlags == 1 ? api.CreateObject("WICBitmap").FromFile(o.IconFile) : MakeImgData("icon:" + o.IconFile + "," + o.Index, 0, 32)];
}
//GetOverlayInfo
case 0x60010001:
HRESULT hr;
hr = E_FAIL;
if (nArg >= 1) {
DWORD dwIndex = GetIntFromVariant(&pDispParams->rgvarg[nArg]);
IUnknown *punk;
if (FindUnknown(&pDispParams->rgvarg[nArg - 1], &punk)) {
TCHAR pszIconFile[MAX_PATH * 2];
int iIndex;
DWORD dwFlags;
if (dwIndex < g_pIconOverlayHandlers.size()) {
hr = g_pIconOverlayHandlers[dwIndex]->GetOverlayInfo(pszIconFile, sizeof(pszIconFile), &iIndex, &dwFlags);
if SUCCEEDED(hr) {
VARIANT v;
teSetSZ(&v, pszIconFile);
tePutProperty(punk, L"IconFile", &v);
VariantClear(&v);
teSetLong(&v, iIndex);
tePutProperty(punk, L"Index", &v);
VariantClear(&v);
teSetLong(&v, dwFlags);
tePutProperty(punk, L"dwFlags", &v);
VariantClear(&v);
}
}
}
}
teSetLong(pVarResult, hr);
return S_OK;
##3. カスタムドローでアイコンオーバーレイを表示
script.js 33行付近、ticonoverlay.cpp 773行-788行
カスタムドローのCDDS_ITEMPOSTPAINT(既定の描画後)
積まれたリストのアイコンオーバーレイハンドラーを使い IShellIconOverlayIdentifier::IsMemberOf で表示項目がアイコン表示するかチェックし、表示するのであれば 2. で用意したアイコン画像を表示する
AddEvent("ItemPostPaint2", function (Ctrl, pid, nmcd, vcd)
{
var hList = Ctrl.hwndList;
if (hList && pid && Addons.IconOverlay.DLL) {
var db = Addons.IconOverlay.FV[Ctrl.Id];
if (!db) {
db = {};
Addons.IconOverlay.FV[Ctrl.Id] = db;
}
var i = db[pid.Path];
if (isNaN(i)) {
i = Addons.IconOverlay.DLL.GetOverlayIconIndex(pid.Path, api.GetAttributesOf(pid, SFGAO_STORAGECAPMASK));
db[pid.Path] = i;
}
var icon = Addons.IconOverlay.Icon[i];
if (icon) {
var image = icon[Ctrl.IconSize < 32 ? 0 : 1];
var rc = api.Memory("RECT");
rc.Left = LVIR_ICON;
api.SendMessage(hList, LVM_GETITEMRECT, nmcd.dwItemSpec, rc);
var w = Ctrl.IconSize * screen.logicalYDPI / 96;
image = GetThumbnail(image, Ctrl.IconSize < 32 ? w : (w * 5 / 16) + 16);
var x = api.SendMessage(hList, LVM_GETVIEW, 0, 0) == 2 ? rc.Right - w : rc.Left + (rc.Right - rc.Left - w) / 2;
var y = rc.Bottom - image.GetHeight() - 2;
if (image) {
image.DrawEx(nmcd.hdc, x, y, 0, 0, CLR_NONE, CLR_NONE, ILD_NORMAL);
}
}
}
});
//GetOverlayIconIndex
case 0x60010002:
int iResult;
iResult = -1;
if (nArg >= 1) {
BSTR bsPath = GetLPWSTRFromVariant(&pDispParams->rgvarg[nArg]);
DWORD dwAttrib = GetIntFromVariant(&pDispParams->rgvarg[nArg - 1]);
for (UINT i = 0; i < g_pIconOverlayHandlers.size(); i++) {
if (g_pIconOverlayHandlers[i]->IsMemberOf(bsPath, dwAttrib) == S_OK) {
iResult = i;
break;
}
}
}
teSetLong(pVarResult, iResult);
return S_OK;