EternalWindows
シェル名前空間 / サムネイルイメージ

サムネイルイメージとは、ファイルの中身を縮小して作成したイメージのことです。 ビットマップのような画像ファイルでは、こうしたイメージを表示するためのシェル拡張が関連付けられているため、 ファイルを開かなくてもファイルの中身を確認できるという利点があります。 サムネイルイメージを取得するためのインターフェースはIExtractImageであり、 これは前節で取り上げたIShellFolder::GetUIObjectOfで取得することができます。 IExtractImageを取得したら、まずはGetLocationを呼び出すことになります。 これにより、取得したいイメージのサイズやビット数をシェル拡張に伝えることができます。

HRESULT IExtractImage::GetLocation(
  LPWSTR pszPathBuffer,
  DWORD cchMax,
  DWORD *pdwPriority,
  const SIZE *prgSize,
  DWORD dwRecClrDepth,
  DWORD *pdwFlags
);

pszPathBufferは、イメージのフルパスを受け取るバッファを指定します。 cchMaxは、pszPathBufferのサイズを指定します。 pdwPriorityは、優先順位を受け取る変数のアドレスを指定します。 Windows Vista以降では、NULLを指定しても問題ありません。 prgSizeは、取得したいイメージのサイズを格納したSIZE構造体のアドレスを指定します。 dwRecClrDepthは、取得したいイメージのビット数を指定します。 pdwFlagsは、イメージの取得に関する定数を格納した変数のアドレスを指定します。

GetLocationを呼び出したら、Extractによってサムネイルイメージを取得することができます。

HRESULT IExtractImage::Extract(
  HBITMAP *phBmpImage
);

phBmpImageは、イメージを格納したビットマップのハンドルを受け取る変数のアドレスを指定します。

Windows Vistaからは、サムネイルイメージを取得するための新しいインターフェースとしてIThumbnailProviderが登場しました。 Windows Vistaでも依然としてIExtractImageは使用可能ですが、 シェル拡張がIExtractImageを実装していない可能性を考慮するならば、 IThumbnailProviderの使用方法も理解しておく必要があります。

HRESULT IThumbnailProvider::GetThumbnail(
  UINT cx,
  HBITMAP *phbmp,
  WTS_ALPHATYPE *pdwAlpha
);

cxは、取得したいイメージの幅を指定します。 イメージの高さは、この幅の値と同一になります。 phbmpは、イメージを格納したビットマップのハンドルを受け取る変数のアドレスを指定します。 pdwAlphaは、WTS_ALPHATYPE型の変数のアドレスを指定します。 メソッドが成功した場合は、イメージがアルファ値を含むかどうかを示す定数が格納されます。

サムネイルイメージは、テキストファイルのようなファイルからは取得することができないため、 こうしたファイルのイメージはアイコンで代用することが多いと思われます。 アイコンのハンドルを取得するには、まずIExtractIconのGetIconLocationを呼び出して、 アイコンのインデックスとパスを取得する必要があります。

HRESULT IExtractIcon::GetIconLocation(
 UINT uFlags,
  LPTSTR szIconFile,
  UINT cchMax,
  int *piIndex,
  UINT *pwFlags
);

uFlagsは、定義されている定数を指定します。 0またはGIL_FORSHELLを指定すればよいと思われます。 szIconFileは、アイコンのパスを受け取るバッファを指定します。 cchMaxは、szIconFileのサイズを指定します。 piIndexは、アイコンのインデックスを受け取る変数のアドレスを指定します。 pwFlagsは、取得したイメージに関する定数を受け取る変数のアドレスを指定します。

アイコンのインデックスとパスを取得したら、Extractを呼び出してアイコンのハンドルを取得できます。

HRESULT IExtractIcon::Extract(
  LPCTSTR pszFile,
  UINT nIconIndex,
  HICON *phiconLarge,
  HICON *phiconSmall,
  UINT nIconSize
);

pszFileは、アイコンのパスを格納したバッファを指定します。 nIconIndexは、取得したいアイコンのインデックスを指定します。 phiconLargeは、大きいアイコンのハンドルを受け取る変数のアドレスを指定します。 不要な場合は、NULLを指定しても問題ありません。 phiconSmallは、小さいアイコンのハンドルを受け取る変数のアドレスを指定します。 不要な場合は、NULLを指定しても問題ありません。 nIconSizeは、アイコンのサイズを指定します。 上位ワードが大きいアイコンのサイズであり、下位ワードが小さいアイコンのサイズになっていなければなりません。

今回のプログラムは、ウインドウの左上隅にアイテムのサムネイルイメージを表示します。 IThumbnailProviderを使用する場合は、thumbcache.hのインクルードが必要になります。

#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <commctrl.h>
#include <thumbcache.h>

#pragma comment (lib, "shlwapi.lib")
#pragma comment (lib, "comctl32.lib")

HWND             g_hwndListView = NULL;
HWND             g_hwndTreeView = NULL;
IShellFolder     *g_pDesktopFolder = NULL;
PIDLIST_ABSOLUTE g_pidlDesktop = NULL;

BOOL GetThumbnailImage(PIDLIST_ABSOLUTE pidlAbsolute, int nSize, HBITMAP *phbmp, HICON *phicon);
void ResetListViewColumn(IShellFolder *pShellFolder);
void EnumItem(PIDLIST_ABSOLUTE pidlAbsolute, HWND hwnd, BOOL bInsertList, HTREEITEM hitem);
void InsertListItem(PIDLIST_ABSOLUTE pidlAbsolute, IShellFolder *pShellFolder, PITEMID_CHILD pidlChild, SFGAOF attributes, int i);
void InsertTreeItem(PIDLIST_ABSOLUTE pidlAbsolute, HTREEITEM hitemParent, LPTSTR lpszName, ULONG uAttributes);
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListView = NULL;
	static HWND hwndTreeView = NULL;
	
	switch (uMsg) {
		
	case WM_CREATE: {
		HIMAGELIST           himl;
		SHFILEINFO           fileInfo;
		INITCOMMONCONTROLSEX ic;

		CoInitialize(NULL);
		
		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES;
		InitCommonControlsEx(&ic);

		hwndListView = CreateWindowEx(0, WC_LISTVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndTreeView = CreateWindowEx(0, WC_TREEVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		himl = (HIMAGELIST)SHGetFileInfo(TEXT("C:\\"), 0, &fileInfo, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
		ListView_SetImageList(hwndListView, himl, LVSIL_SMALL);
		TreeView_SetImageList(hwndTreeView, himl, TVSIL_NORMAL);
		
		SHGetDesktopFolder(&g_pDesktopFolder);
		SHGetSpecialFolderLocation(hwnd, CSIDL_DESKTOP, &g_pidlDesktop);
		g_hwndListView = hwndListView;
		g_hwndTreeView = hwndTreeView;
		
		ResetListViewColumn(g_pDesktopFolder);
		InsertTreeItem(g_pidlDesktop, TVI_ROOT, TEXT("デスクトップ"), SFGAO_HASSUBFOLDER);

		return 0;
	}

	case WM_NOTIFY: {
		LPNMHDR lpNmhdr = (LPNMHDR)lParam;
		if (lpNmhdr->code == TVN_ITEMEXPANDING) {
			LPTVITEM lp = &((LPNMTREEVIEW)lParam)->itemNew;
			if (!(lp->state & TVIS_EXPANDEDONCE))
				EnumItem((PIDLIST_ABSOLUTE)lp->lParam, hwnd, FALSE, lp->hItem);
		}
		else if (lpNmhdr->code == TVN_SELCHANGED) {
			LPTVITEM lp = &((LPNMTREEVIEW)lParam)->itemNew;
			EnumItem((PIDLIST_ABSOLUTE)lp->lParam, hwnd, TRUE, NULL);
		}
		else if (lpNmhdr->code == TVN_DELETEITEM) {
			LPTVITEM lp = &((LPNMTREEVIEW)lParam)->itemOld;
			CoTaskMemFree((LPVOID)lp->lParam);
		}
		else if (lpNmhdr->code == LVN_DELETEITEM) {
			LVITEM item;
			item.mask  = LVIF_PARAM;
			item.iItem = ((LPNMLISTVIEW)lParam)->iItem;
			ListView_GetItem(hwndListView, &item);
			CoTaskMemFree((LPVOID)item.lParam);
		}
		else if (lpNmhdr->code == LVN_ITEMCHANGED) {
			LVITEM  item;
			HDC     hdc, hdcMem;
			HBITMAP hbmp, hbmpPrev;
			HICON   hicon;
			RECT    rc;
			int     nSize = 32;

			if (((LPNMLISTVIEW)lParam)->iItem == -1)
				return 0;

			item.mask     = LVIF_PARAM;
			item.iItem    = ((LPNMITEMACTIVATE)lParam)->iItem;
			item.iSubItem = 0;
			ListView_GetItem(hwndListView, &item);

			hdc = GetDC(hwnd);

			SetRect(&rc, 0, 0, nSize, nSize);
			FillRect(hdc, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));

			if (!GetThumbnailImage((PIDLIST_ABSOLUTE)item.lParam, nSize, &hbmp, &hicon)) {
				ReleaseDC(hwnd, hdc);
				return 0;
			}
			
			if (hbmp != NULL) {
				hdcMem = CreateCompatibleDC(hdc);
				hbmpPrev = (HBITMAP)SelectObject(hdcMem, hbmp);
				BitBlt(hdc, 0, 0, nSize, nSize, hdcMem, 0, 0, SRCCOPY);
				SelectObject(hdcMem, hbmpPrev);
				DeleteDC(hdcMem);
			}
			else if (hicon != NULL)
				DrawIcon(hdc, 0, 0, hicon);
			else
				;

			ReleaseDC(hwnd, hdc);
		}
		else
			;
		return 0;
	}
	
	case WM_SIZE:{
		int y = 40;
		int nTreeViewWidth = LOWORD(lParam) / 3;
		int nListViewWidth = LOWORD(lParam) - nTreeViewWidth;
		MoveWindow(hwndTreeView, 0, y, nTreeViewWidth, HIWORD(lParam) - y, TRUE);
		MoveWindow(hwndListView, nTreeViewWidth, y, nListViewWidth, HIWORD(lParam) - y, TRUE);
		return 0;
	}

	case WM_DESTROY:
		if (g_pDesktopFolder != NULL)
			g_pDesktopFolder->Release();

		ListView_DeleteAllItems(hwndListView);
		TreeView_DeleteAllItems(hwndTreeView);

		CoUninitialize();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL GetThumbnailImage(PIDLIST_ABSOLUTE pidlAbsolute, int nSize, HBITMAP *phbmp, HICON *phicon)
{
	BOOL               bResult = FALSE;
	WCHAR              szPath[256];
	PITEMID_CHILD      pidlChild;
	IShellFolder       *pShellFolder;
	IExtractImage      *pExtractImage;
	IThumbnailProvider *pThumbnailProvider;
	IExtractIcon       *pExtractIcon;
	
	*phbmp = NULL;
	*phicon = NULL;

	SHBindToParent(pidlAbsolute, IID_PPV_ARGS(&pShellFolder), NULL);
	pidlChild = ILFindLastID(pidlAbsolute);

	if (pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IExtractImage, NULL, (void **)&pExtractImage) == S_OK) {
		SIZE size;
		DWORD dwFlags;
		
		size.cx = nSize;
		size.cy = nSize;
		dwFlags = IEIFLAG_ASPECT | IEIFLAG_SCREEN;
		if (pExtractImage->GetLocation(szPath, sizeof(szPath) / sizeof(WCHAR), NULL, &size, 32, &dwFlags) == S_OK) {
			if (pExtractImage->Extract(phbmp) == S_OK)
				bResult = TRUE;
		}

		pExtractImage->Release();
	}
	else if (pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IThumbnailProvider, NULL, (void **)&pThumbnailProvider) == S_OK) {
		WTS_ALPHATYPE alphaType;

		if (pThumbnailProvider->GetThumbnail(nSize, phbmp, &alphaType) == S_OK)
			bResult = TRUE;

		pThumbnailProvider->Release();
	}
	else
		;

	if (!bResult && pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IExtractIcon, NULL, (void **)&pExtractIcon) == S_OK) {
		UINT uFlags;
		int  nIndex;

		if (pExtractIcon->GetIconLocation(GIL_FORSHELL, szPath, sizeof(szPath) / sizeof(WCHAR), &nIndex, &uFlags) == S_OK) {
			if (pExtractIcon->Extract(szPath, nIndex, phicon, NULL, MAKELONG(nSize, 16)) == S_OK)
				bResult = TRUE;
			else {
				WORD wIndex = 0;

				*phicon = ExtractAssociatedIconW(NULL, szPath, &wIndex);
				if (*phicon != NULL)
					bResult = TRUE;
			}
		}

		pExtractIcon->Release();
	}

	pShellFolder->Release();

	return bResult;
}

void ResetListViewColumn(IShellFolder *pShellFolder)
{
	int           i, nCol;
	TCHAR         szBuf[256];
	LVCOLUMN      column;
	SHELLDETAILS  details;
	IShellFolder2 *pShellFolder2;

	nCol = Header_GetItemCount(ListView_GetHeader(g_hwndListView));
	for (i = 0; i < nCol; i++)
		ListView_DeleteColumn(g_hwndListView, 0);

	if (pShellFolder->QueryInterface(IID_PPV_ARGS(&pShellFolder2)) != S_OK)
		return;
	
	for (i = 0; i < 4; i++) {
		if (pShellFolder2->GetDetailsOf(NULL, i, &details) != S_OK)
			break;

		StrRetToBuf(&details.str, NULL, szBuf, sizeof(szBuf) / sizeof(TCHAR));

		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 150;
		column.pszText = szBuf;
		ListView_InsertColumn(g_hwndListView, i, &column);
	}

	pShellFolder2->Release();
}

void EnumItem(PIDLIST_ABSOLUTE pidlAbsolute, HWND hwnd, BOOL bInsertList, HTREEITEM hitem)
{
	int              i = 0;
	BOOL             bBind;
	TCHAR            szDisplayName[256];
	SFGAOF           attributes = 0;
	SHCONTF          flag;
	STRRET           strret;
	PIDLIST_ABSOLUTE pidlNew;
	PITEMID_CHILD    pidlChild;
	IShellFolder     *pShellFolder;
	IEnumIDList      *pEnumIdList;

	if (g_pDesktopFolder->CompareIDs(0, pidlAbsolute, g_pidlDesktop) == 0) {
		pShellFolder = g_pDesktopFolder;
		bBind = FALSE;
	}
	else {
		g_pDesktopFolder->BindToObject(pidlAbsolute, NULL, IID_PPV_ARGS(&pShellFolder));
		bBind = TRUE;
	}

	if (bInsertList) {
		ListView_DeleteAllItems(g_hwndListView);
		ResetListViewColumn(pShellFolder);
	}

	flag = bInsertList ? SHCONTF_FOLDERS | SHCONTF_NONFOLDERS : SHCONTF_FOLDERS;
	if (pShellFolder->EnumObjects(hwnd, flag | SHCONTF_INCLUDEHIDDEN, &pEnumIdList) != S_OK) {
		if (bBind)
			pShellFolder->Release();
		return;
	}

	while (pEnumIdList->Next(1, &pidlChild, NULL) == S_OK) {
		pidlNew = ILCombine(pidlAbsolute, pidlChild);
		if (bInsertList) {
			attributes = SFGAO_GHOSTED | SFGAO_SHARE | SFGAO_LINK;
			pShellFolder->GetAttributesOf(1, (LPCITEMIDLIST *)&pidlChild, &attributes);
			InsertListItem(pidlNew, pShellFolder, pidlChild, attributes, i);
		}
		else {
			attributes = SFGAO_HASSUBFOLDER | SFGAO_GHOSTED | SFGAO_SHARE | SFGAO_LINK;
			pShellFolder->GetAttributesOf(1, (LPCITEMIDLIST *)&pidlChild, &attributes);
			pShellFolder->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret);
			StrRetToBuf(&strret, pidlChild, szDisplayName, sizeof(szDisplayName) / sizeof(TCHAR));
			InsertTreeItem(pidlNew, hitem, szDisplayName, attributes);
		}
		
		i++;
		
		CoTaskMemFree(pidlChild);
	}

	if (bInsertList)
		ListView_SortItems(g_hwndListView, CompareFunc, (LPARAM)pShellFolder);

	if (bBind)
		pShellFolder->Release();

	pEnumIdList->Release();
}

void InsertListItem(PIDLIST_ABSOLUTE pidlAbsolute, IShellFolder *pShellFolder, PITEMID_CHILD pidlChild, SFGAOF attributes, int i)
{
	int           j;
	TCHAR         szBuf[256];
	LVITEM        item;
	SHFILEINFO    fileInfo;
	SHELLDETAILS  details;
	IShellFolder2 *pShellFolder2;

	if (pShellFolder->QueryInterface(IID_PPV_ARGS(&pShellFolder2)) != S_OK)
		return;

	for (j = 0; j < 4; j++) {
		pShellFolder2->GetDetailsOf(pidlChild, j, &details);
		StrRetToBuf(&details.str, pidlChild, szBuf, sizeof(szBuf) / sizeof(TCHAR));
		
		item.mask     = LVIF_TEXT;
		item.iItem    = i;
		item.iSubItem = j;
		item.pszText  = szBuf;

		if (j == 0) {
			SHGetFileInfo((LPTSTR)pidlAbsolute, 0, &fileInfo, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_PIDL);

			item.mask   |= LVIF_IMAGE | LVIF_PARAM;
			item.iImage = fileInfo.iIcon;
			item.lParam = (LPARAM)pidlAbsolute;

			if (attributes & SFGAO_GHOSTED) {
				item.mask     |= LVIF_STATE;
				item.state     = LVIS_CUT;
				item.stateMask = LVIS_CUT;
			}
			
			if (attributes & SFGAO_SHARE) {
				item.mask     |= LVIF_STATE;
				item.state     = INDEXTOOVERLAYMASK(1);
				item.stateMask = LVIS_OVERLAYMASK;
			}
			
			if (attributes & SFGAO_LINK) {
				item.mask     |= LVIF_STATE;
				item.state     = INDEXTOOVERLAYMASK(2);
				item.stateMask = LVIS_OVERLAYMASK;
			}

			ListView_InsertItem(g_hwndListView, &item);
		}
		else
			ListView_SetItem(g_hwndListView, &item);
	}

	pShellFolder2->Release();
}

void InsertTreeItem(PIDLIST_ABSOLUTE pidlAbsolute, HTREEITEM hitemParent, LPTSTR lpszName, SFGAOF attributes)
{
	SHFILEINFO     fileInfo;
	TVINSERTSTRUCT is;

	SHGetFileInfo((LPTSTR)pidlAbsolute, 0, &fileInfo, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_PIDL);
	is.item.iImage = fileInfo.iIcon;

	SHGetFileInfo((LPTSTR)pidlAbsolute, 0, &fileInfo, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_PIDL | SHGFI_OPENICON);
	is.item.iSelectedImage = fileInfo.iIcon;

	is.hParent      = hitemParent;
	is.item.mask    = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
	is.item.pszText = lpszName;
	is.item.lParam  = (LPARAM)pidlAbsolute;
	
	if (attributes & SFGAO_GHOSTED) {
		is.item.mask     |= LVIF_STATE;
		is.item.state     = LVIS_CUT;
		is.item.stateMask = LVIS_CUT;
	}
	
	if (attributes & SFGAO_SHARE) {
		is.item.mask     |= LVIF_STATE;
		is.item.state     = INDEXTOOVERLAYMASK(1);
		is.item.stateMask = LVIS_OVERLAYMASK;
	}
	
	if (attributes & SFGAO_LINK) {
		is.item.mask     |= LVIF_STATE;
		is.item.state     = INDEXTOOVERLAYMASK(2);
		is.item.stateMask = LVIS_OVERLAYMASK;
	}

	if (attributes & SFGAO_HASSUBFOLDER) {
		is.item.mask     |= TVIF_CHILDREN;
		is.item.cChildren = 1;
	}
	
	TreeView_InsertItem(g_hwndTreeView, &is);
}

int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	IShellFolder     *pShellFolder = (IShellFolder *)lParamSort;
	PIDLIST_ABSOLUTE pidlAbsolute1 = (PIDLIST_ABSOLUTE)lParam1;
	PIDLIST_ABSOLUTE pidlAbsolute2 = (PIDLIST_ABSOLUTE)lParam2;
	PITEMID_CHILD    pidlChild1 = ILFindLastID(pidlAbsolute1);
	PITEMID_CHILD    pidlChild2 = ILFindLastID(pidlAbsolute2);
	SFGAOF           fileAttributes = SFGAO_FILESYSTEM;
	SFGAOF           folderAttributes = SFGAO_FOLDER | SFGAO_FILESYSTEM;
	SFGAOF           virtualAttributes = SFGAO_FOLDER;
	SFGAOF           attributes1 = SFGAO_FILESYSTEM | SFGAO_FOLDER;
	SFGAOF           attributes2 = SFGAO_FILESYSTEM | SFGAO_FOLDER;
	int              nResult = -1;

	pShellFolder->GetAttributesOf(1, (LPCITEMIDLIST *)&pidlChild1, &attributes1);
	pShellFolder->GetAttributesOf(1, (LPCITEMIDLIST *)&pidlChild2, &attributes2);
	attributes1 &= SFGAO_FILESYSTEM | SFGAO_FOLDER;
	attributes2 &= SFGAO_FILESYSTEM | SFGAO_FOLDER;

	if (attributes1 == attributes2)
		return pShellFolder->CompareIDs(0, pidlChild1, pidlChild2) * -1;

	if (attributes1 == virtualAttributes)
		nResult = -1;
	else if (attributes1 == folderAttributes) {
		if (attributes2 == virtualAttributes)
			nResult = 1;
		else
			nResult = -1;
	}
	else if (attributes1 == fileAttributes)
		nResult = 1;
	else
		nResult = -1;
	
	return nResult;
}

サムネイルイメージは、リストビューのアイテムが選択された際に表示するため、 まずはこのタイミングを検出しなければなりません。 アイテムの状態が変更された場合は、通知コードがLVN_ITEMCHANGEDであるWM_NOTIFYが送られます。

else if (lpNmhdr->code == LVN_ITEMCHANGED) {
	LVITEM  item;
	HDC     hdc, hdcMem;
	HBITMAP hbmp, hbmpPrev;
	HICON   hicon;
	RECT    rc;
	int     nSize = 32;

	if (((LPNMLISTVIEW)lParam)->iItem == -1)
		return 0;

	item.mask     = LVIF_PARAM;
	item.iItem    = ((LPNMITEMACTIVATE)lParam)->iItem;
	item.iSubItem = 0;
	ListView_GetItem(hwndListView, &item);

	hdc = GetDC(hwnd);

	SetRect(&rc, 0, 0, nSize, nSize);
	FillRect(hdc, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));

	if (!GetThumbnailImage((PIDLIST_ABSOLUTE)item.lParam, nSize, &hbmp, &hicon)) {
		ReleaseDC(hwnd, hdc);
		return 0;
	}
	
	if (hbmp != NULL) {
		hdcMem = CreateCompatibleDC(hdc);
		hbmpPrev = (HBITMAP)SelectObject(hdcMem, hbmp);
		BitBlt(hdc, 0, 0, nSize, nSize, hdcMem, 0, 0, SRCCOPY);
		SelectObject(hdcMem, hbmpPrev);
		DeleteDC(hdcMem);
	}
	else if (hicon != NULL)
		DrawIcon(hdc, 0, 0, hicon);
	else
		;

	ReleaseDC(hwnd, hdc);
}

LVN_ITEMCHANGEDのlParamは、NMLISTVIEW構造体へのアドレスが格納されています。 この構造体のiItemに-1でない値が格納された場合は、何らかのアイテムの状態が変更されたことを意味するため、 ListView_GetItemでアイテムのLPARAMを取得することになります。 このLPARAMにはアイテムのPIDLが格納されているため、 GetThumbnailImageという自作関数でアイテムのサムネイルイメージを取得することができます。 関数が成功した場合は、hbmpにサムネイルイメージか、hiconにファイルのアイコンが格納されているため、 hbmpの場合はメモリデバイスコンテキストに選択してから描画、 hiconの場合はデバイスコンテキストに対してそのまま描画します。 描画処理をWM_PAINTで行っていない関係上、クライアント領域が無効化された際に描画内容が復元されませんが、 それについては許容することにします。

GetThumbnailImageでは、最初にIExtractImageの取得を試み、 これが失敗した場合はIThumbnailProviderの取得を試みます。 どちらのインターフェースも取得できなかった場合や、 サムネイルイメージの取得に至らなかった場合はIExtractIconの取得を試み、 サムネイルイメージの代わりにアイコンを返すようにします。 まず、IExtractImageに関するコードを確認します。

if (pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IExtractImage, NULL, (void **)&pExtractImage) == S_OK) {
	SIZE size;
	DWORD dwFlags;
	
	size.cx = nSize;
	size.cy = nSize;
	dwFlags = IEIFLAG_ASPECT | IEIFLAG_SCREEN;
	if (pExtractImage->GetLocation(szPath, sizeof(szPath) / sizeof(WCHAR), NULL, &size, 32, &dwFlags) == S_OK) {
		if (pExtractImage->Extract(phbmp) == S_OK)
			bResult = TRUE;
	}

	pExtractImage->Release();
}

IExtractImageを使用する場合は、最初にGetLocationを呼び出す必要があります。 第4引数は、イメージをどれくらいのサイズで取得するかを表すSIZE構造体のアドレスを指定します。 この構造体の初期化には、GetThumbnailImageの引数であるnSizeを使用しています。 第5引数はイメージのビット数であり、32で特定に問題ないと思われます。 第6引数はイメージの取得に関する定数であり、IEIFLAG_ASPECTを指定すれば特に問題ありません。 GetLocationが成功したら、Extractを呼び出してビットマップのハンドルを取得することになります。 対象となるアイテムがショートカットの場合は、Extractが失敗するかもしれません。

else if (pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IThumbnailProvider, NULL, (void **)&pThumbnailProvider) == S_OK) {
	WTS_ALPHATYPE alphaType;

	if (pThumbnailProvider->GetThumbnail(nSize, phbmp, &alphaType) == S_OK)
		bResult = TRUE;

	pThumbnailProvider->Release();
}

IThumbnailProviderを使用する場合は、GetThumbnailを呼び出してビットマップのハンドルを取得することになります。 ちなみに、IThumbnailProviderを実装するオブジェクトは、初期化用のメソッドを持つIInitializeWithStreamやIInitializeWithFileも実装しているはずですが、 これらのインターフェースは扱わなくても問題ないようです。 おそらく、GetUIObjectOf内部で適切な処理が行われていると予想されます。

if (!bResult && pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IExtractIcon, NULL, (void **)&pExtractIcon) == S_OK) {
	UINT uFlags;
	int  nIndex;

	if (pExtractIcon->GetIconLocation(GIL_FORSHELL, szPath, sizeof(szPath) / sizeof(WCHAR), &nIndex, &uFlags) == S_OK) {
		if (pExtractIcon->Extract(szPath, nIndex, phicon, NULL, MAKELONG(nSize, 16)) == S_OK)
			bResult = TRUE;
		else {
			WORD wIndex = 0;

			*phicon = ExtractAssociatedIconW(NULL, szPath, &wIndex);
			if (*phicon != NULL)
				bResult = TRUE;
		}
	}

	pExtractIcon->Release();
}

bResultがFALSEである場合は、サムネイルイメージを取得できなかったことを意味するため、 IExtractIconを使用してアイコンのハンドルを取得することになります。 GetIconLocationを呼び出せば、アイコンのインデックスを取得することができるため、 これをExtractに指定すればアイコンのハンドルを取得することになります。 第4引数は小さいアイコンのハンドルを指定できますが、今回は特に必要がないためNULLを指定します。 Extractが失敗した場合は、ExtractAssociatedIconを呼び出してファイルに関連付けられたアイコンのハンドルを取得します。 このとき、第3引数に指定する変数のインデックスには0を格納しておきます。 Extractの失敗は、htmlファイルやデフォルトアイコンのexeファイルにて確認しています。

今回のプログラムでは、コントロールパネル内に含まれるアイテムのアイコンを取得できませんが、 Windows Vistaから登場したIShellItemImageFactoryを使用すればこれは可能です。 具体的には、SHCreateItemFromIDListでアイテムのPIDLからIShellItemImageFactoryを取得し、 このインターフェースのGetImageを呼び出します。 アイコンの背景色は黒色に設定されるようです。

IThumbnailCacheについて

Windows Vistaにおけるフォルダの表示形式で中アイコンや大アイコンを選択している場合、 ファイルによってはアイコンの代わりにサムネイルイメージが表示されることがあります。 実はこのサムネイルイメージは、ファイルとしてキャッシュされることになっており、 次に示すフォルダの中から確認できるようになっています。

C:\Users\\AppData\Local\Microsoft\Windows\Explorer

このフォルダの中には、thumbcache_32.dbなどのファイルが存在しており、 そこにはキャッシュされた複数のイメージが格納されています。 イメージをキャッシュする理由は、ファイルからイメージを取得するよりも キャッシュされたイメージを参照する方が高速になるからであり、 大量のイメージをまとめて表示する際には欠かせない機能と言えます。

アプリケーションからキャッシュされたイメージへアクセスするには、 Windows Vistaから登場したIThumbnailCacheを使用することになります。 次に示すGetThumbnailImageは、IThumbnailCacheを使用するように書き換えたものです。

BOOL GetThumbnailImage(PIDLIST_ABSOLUTE pidlAbsolute, int nSize, HBITMAP *phbmp, HICON *phicon)
{
	BOOL          bResult = FALSE;
	WCHAR         szPath[256];
	PITEMID_CHILD pidlChild;
	IShellFolder  *pShellFolder;
	IShellItem    *pShellItem;
	IExtractIcon  *pExtractIcon;

	*phbmp = NULL;
	*phicon = NULL;

	SHBindToParent(pidlAbsolute, IID_PPV_ARGS(&pShellFolder), NULL);
	pidlChild = ILFindLastID(pidlAbsolute);

	if (SHCreateItemFromIDList(pidlAbsolute, IID_PPV_ARGS(&pShellItem)) == S_OK) {
		IThumbnailCache *pThumbnailCache;
		ISharedBitmap   *pSharedBitmap;
		WTS_CACHEFLAGS  cacheFlags;
		WTS_THUMBNAILID thumbnailId;
		HRESULT         hr;

		hr = CoCreateInstance(CLSID_LocalThumbnailCache, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&pThumbnailCache));
		if (SUCCEEDED(hr)) {
			if (pThumbnailCache->GetThumbnail(pShellItem, nSize, WTS_EXTRACTDONOTCACHE, &pSharedBitmap, &cacheFlags, &thumbnailId) == S_OK) {
				pSharedBitmap->Detach(phbmp);
				pSharedBitmap->Release();
			}

			pThumbnailCache->Release();
		}

		pShellItem->Release();
	}

	if (!bResult && pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IExtractIcon, NULL, (void **)&pExtractIcon) == S_OK) {
		UINT uFlags;
		int  nIndex;

		if (pExtractIcon->GetIconLocation(0, szPath, sizeof(szPath) / sizeof(WCHAR), &nIndex, &uFlags) == S_OK) {
			if (pExtractIcon->Extract(szPath, nIndex, phicon, NULL, MAKELONG(nSize, 16)) == S_OK)
				bResult = TRUE;
			else {
				WORD wIndex = 0;

				*phicon = ExtractAssociatedIconW(NULL, szPath, &wIndex);
				if (*phicon != NULL)
					bResult = TRUE;
			}
		}

		pExtractIcon->Release();
	}

	pShellFolder->Release();

	return bResult;
}

IThumbnailCacheのGetThumbnailを呼び出すには、IShellItemによってアイテムを表しておく必要があるため、 まずはSHCreateItemFromIDListで IShellItemを取得することになります。 このとき、第1引数には完全なPIDLを取得します。 これが成功した場合は、CoCreateInstanceにCLSID_LocalThumbnailCacheを指定してIThumbnailCacheを実装するオブジェクトを作成し、 GetThumbnailを呼び出します。 第2引数は取得したいイメージのサイズであり、第3引数はイメージの取得に関する定数を指定します。 WTS_INCACHEONLYを指定した場合はキャッシュされたイメージを取得することになるため、 イメージがキャッシュされていない場合はメソッドが失敗することになります。 第4引数は、キャッシュされたイメージを受け取る変数のアドレスであり、 このイメージはISharedBitmapで表すことになります。 第5引数は、取得したイメージに関する情報を受け取る変数のアドレスであり、 そのイメージが既にキャッシュされている場合はWTS_CACHEDが格納されます。 逆に、キャッシュされていない場合はWTS_DEFAULTが格納されます。 第6引数は、キャッシュされたイメージを識別するためのIDを受け取る変数のアドレスであり、 このIDを保存しておけば、次回からはGetThumbnailByIDによってイメージを取得できるようになります。 ISharedBitmap::Detachを呼び出せば、キャッシュされたイメージをビットマップハンドルとして取得することができます。

GetThumbnailの機能は、キャッシュされたイメージを取得することだけではありません。 たとえば、第3引数にWTS_EXTRACTを指定するとキャッシュからではなくファイルからイメージを取得し、 さらにそのイメージをファイルにキャッシュします。 つまり、GetThumbnailはイメージをキャッシュする機能も持っていることになります。 また、WTS_EXTRACTDONOTCACHEを指定すれば、 ファイルからイメージを取得するだけでキャッシュは行いませんから、 動作内容としてはIExtractImageやIThumbnailProviderと遜色ないことになります。 言い換えれば、IThumbnailCacheはIExtractImageやIThumbnailProviderの呼び出しをラッピングしているのです。



戻る