EternalWindows
シェル名前空間 / ショートカットメニュー

ファイルやフォルダをGUIとして表示するアプリケーションは、 そのファイルやフォルダのUI情報を取得したいことがあります。 たとえば、ファイルに関連付けられたアイコンやポップアップメニューがこれに当たります。 次に示すIShellFolder::GetUIObjectOfを呼び出せば、 UI情報を取得するためのインターフェースを取得することができます。

HRESULT IShellFolder::GetUIObjectOf(
  HWND hwndOwner,
  UINT cidl,
  PCUITEMID_CHILD_ARRAY apidl,
  REFIID riid,
  UINT *rgfReserved,
  void **ppv
);

hwndOwnerは、ダイアログの親ウインドウとするウインドウハンドルを指定します。 ただし、通常はダイアログが表示されることはないと思われます。 cidlは、apidlの要素数を指定します。 apidlは、PIDLの配列を指定します。 riidは、取得したいインターフェースのIIDを指定します。 rgfReservedは、予約されているためNULLを指定します。 ppvは、インターフェースを受け取る変数のアドレスを指定します。

IShellFolder::GetUIObjectOfにIID_IContextMenuを指定すると、 ポップアップメニューを操作するIContextMenuを取得することができます。 このインターフェースのQueryContextMenuを呼び出せば、 指定されたアイテムに関連するメニュー項目がポップアップメニューに追加されますから、 簡単にポップアップメニューの中身を完成させることができます。

HRESULT IContextMenu::QueryContextMenu(
  HMENU hmenu,
  UINT indexMenu,
  UINT idCmdFirst,
  UINT idCmdLast,
  UINT uFlags
);

hmenuは、ポップアップメニューのハンドルを指定します。 indexMenuは、新しい項目を追加する位置を指定します。 idCmdFirstは、項目の最小のIDを指定します。 通常は、1を指定します。 idCmdLastは、項目の最大のIDを指定します。 通常は、0x7fffを指定します。 uFlagsは、ポップアップメニューをどのように表示するかを示す定数を指定します。 通常は、CMF_NORMALを指定しますが、「名前の変更」という項目が必要な場合はCMF_CANRENAMEを指定します。

QueryContextMenuによってポップアップメニューの中身が完成したら、 TrackPopupMenuなどでポップアップメニューを表示することになります。 ここで選択された項目の内容は、InvokeCommandで実行することになります。

HRESULT IContextMenu::InvokeCommand(
  LPCMINVOKECOMMANDINFO pici
);

piciは、CMINVOKECOMMANDINFO構造体のアドレスを指定します。 この構造体は次のように定義されています。

typedef struct _CMINVOKECOMMANDINFO {
  DWORD cbSize;
  DWORD fMask;
  HWND hwnd;
  LPCSTR lpVerb;
  LPCSTR lpParameters;
  LPCSTR lpDirectory;
  int nShow;
  DWORD dwHotKey;
  HANDLE hIcon;
} CMINVOKECOMMANDINFO, *PCCMINVOKECOMMANDINFO;

cbSizeは、この構造体のサイズを指定します。 fMaskは、定義されている定数を指定しますが、通常は0で問題ありません。 hwndは、NULLまたはウインドウハンドルを指定します。 実行する項目によっては、このウインドウハンドルをダイアログの親とすることも有り得ます。 lpVerbは、実行したい項目のオフセットをMAKEINTRESOURCEマクロによって指定します。 lpParametersは、実行したい項目に対するパラメータを指定します。 ファイルを開く場合は、コマンドライン文字列として機能することになります。 lpDirectoryは、作業用のディレクトリを指定します。 ファイルを開く場合は、カレントディレクトリとして機能することになります。 nShowは、ウインドウの表示状態を示す定数を指定します。 ファイルを開く場合は、この定数の値を基にウインドウが表示されます。 dwHotKeyは、ホットキーの値を指定します。 このメンバは、fMaskにCMIC_MASK_HOTKEYを指定した場合に有効です。 hIconは、アイコンのハンドルを指定します。 このメンバは、fMaskにCMIC_MASK_ICONを指定した場合に有効です。

今回のプログラムは、ツリービューやリストビュー上のアイテムが右クリックされた場合に、 ポップアップメニューを表示します。

#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <commctrl.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 ShowContextMenu(PIDLIST_ABSOLUTE pidlAbsolute, HWND hwnd);
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 == NM_RCLICK) {
			PIDLIST_ABSOLUTE pidlAbsolute;
			
			if (lpNmhdr->hwndFrom == hwndListView) {
				LVITEM item;
				
				if (((LPNMITEMACTIVATE)lParam)->iItem == -1)
					return 0;

				item.mask     = LVIF_PARAM;
				item.iItem    = ((LPNMITEMACTIVATE)lParam)->iItem;
				item.iSubItem = 0;
				ListView_GetItem(hwndListView, &item);
				
				pidlAbsolute = (PIDLIST_ABSOLUTE)item.lParam;
			}
			else if (lpNmhdr->hwndFrom == hwndTreeView) {
				HTREEITEM     hitem;
				TVHITTESTINFO info;
				TVITEMEX      item;

				GetCursorPos(&info.pt);
				ScreenToClient(hwndTreeView, &info.pt);
				hitem = TreeView_HitTest(hwndTreeView, &info);
				if (hitem == NULL && info.hItem == NULL)
					return 0;
				
				item.mask  = TVIF_PARAM;
				item.hItem = hitem;
				TreeView_GetItem(hwndTreeView, &item);
				
				pidlAbsolute = (PIDLIST_ABSOLUTE)item.lParam;
			}
			else
				return 0;
			
			ShowContextMenu(pidlAbsolute, hwnd);
		}
		else
			;
		return 0;
	}
	
	case WM_SIZE:{
		int nTreeViewWidth = LOWORD(lParam) / 3;
		int nListViewWidth = LOWORD(lParam) - nTreeViewWidth;
		MoveWindow(hwndTreeView, 0, 0, nTreeViewWidth, HIWORD(lParam), TRUE);
		MoveWindow(hwndListView, nTreeViewWidth, 0, nListViewWidth, HIWORD(lParam), 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 ShowContextMenu(PIDLIST_ABSOLUTE pidlAbsolute, HWND hwnd)
{
	int                 nId;
	HRESULT             hr;
	POINT               pt;
	HMENU               hmenuPopup;
	IContextMenu        *pContextMenu;
	IShellFolder        *pShellFolder;
	PITEMID_CHILD       pidlChild;
	CMINVOKECOMMANDINFO ici;
	
	SHBindToParent(pidlAbsolute, IID_PPV_ARGS(&pShellFolder), NULL);
	pidlChild = ILFindLastID(pidlAbsolute);

	hr = pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IContextMenu, NULL, (void **)&pContextMenu);
	if (hr != S_OK) {
		pShellFolder->Release();
		return FALSE;
	}

	hmenuPopup = CreatePopupMenu();
	pContextMenu->QueryContextMenu(hmenuPopup, 0, 1, 0x7fff, CMF_NORMAL);

	GetCursorPos(&pt);
	nId = TrackPopupMenu(hmenuPopup, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, NULL);
	if (nId == 0) {
		pContextMenu->Release();
		pShellFolder->Release();
		return FALSE;
	}

	ici.cbSize       = sizeof(CMINVOKECOMMANDINFO);
	ici.fMask        = 0;
	ici.hwnd         = hwnd;
	ici.lpVerb       = (LPCSTR)MAKEINTRESOURCE(nId - 1);
	ici.lpParameters = NULL;
	ici.lpDirectory  = NULL;
	ici.nShow        = SW_SHOW;
	
	hr = pContextMenu->InvokeCommand(&ici);

	pContextMenu->Release();
	pShellFolder->Release();

	return hr == S_OK;
}

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;
}

今回表示されるポップアップメニューで「削除」を選択した場合、 シェル名前空間上におけるアイテムは実際に削除されますが、 リストビュー上やツリービュー上のアイテムは削除されません。 理由は、メニューコマンドの実行に合してListView_DeleteItemやTreeView_DeleteItemを呼び出していないからです。 実際の開発ではこうした問題についても対応する必要があります。

ポップアップメニューを表示すべき段階というのは、 リストビューまたはツリービューのアイテムを右クリックした際です。 このときには、通知コードがNM_RCLICKであるWM_NOTIFYが送られるため、 ここでアイテムに関連付けられたLPARAMを取得することになります。 このLPARAMには、シェル名前空間上におけるアイテムIDリストを格納していますから、 ポップアップメニューを表示する際に使用できます。

else if (lpNmhdr->code == NM_RCLICK) {
		PIDLIST_ABSOLUTE pidlAbsolute;
		
		if (lpNmhdr->hwndFrom == hwndListView) {
			LVITEM item;
			
			if (((LPNMITEMACTIVATE)lParam)->iItem == -1)
				return 0;

			item.mask     = LVIF_PARAM;
			item.iItem    = ((LPNMITEMACTIVATE)lParam)->iItem;
			item.iSubItem = 0;
			ListView_GetItem(hwndListView, &item);
			
			pidlAbsolute = (PIDLIST_ABSOLUTE)item.lParam;
		}
		else if (lpNmhdr->hwndFrom == hwndTreeView) {
			HTREEITEM     hitem;
			TVHITTESTINFO info;
			TVITEMEX      item;

			GetCursorPos(&info.pt);
			ScreenToClient(hwndTreeView, &info.pt);
			hitem = TreeView_HitTest(hwndTreeView, &info);
			if (hitem == NULL && info.hItem == NULL)
				return 0;
			
			item.mask  = TVIF_PARAM;
			item.hItem = hitem;
			TreeView_GetItem(hwndTreeView, &item);
			
			pidlAbsolute = (PIDLIST_ABSOLUTE)item.lParam;
		}
		else
			return 0;
		
		ShowContextMenu(pidlAbsolute, hwnd);
	}

リストビューの場合は、NMITEMACTIVATE構造体のiItemにアイテムのインデックスが格納されているため、 これを指定してListView_GetItemを呼び出せばアイテムの情報を取得することができます。 maskにLVIF_PARAMを指定していることからlParamが初期化されることになり、 アイテムのアイテムIDリストを取得できたことになります。 ツリービューでは、まずクリックされたアイテムを特定するためにTreeView_HitTestを呼び出し、 これが成功すればTreeView_GetItemを呼び出してアイテムの情報を取得します。 maskにLVIF_PARAMを指定すればlParamが初期化されますから、 アイテムIDリストを取得できたことになります。

ShowContextMenuという自作関数は、アイテムIDリストで識別されるアイテムのポップアップメニューを表示します。 まず、IContextMenuを取得するコードを確認します。

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

hr = pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IContextMenu, NULL, (void **)&pContextMenu);
if (hr != S_OK) {
	pShellFolder->Release();
	return FALSE;
}

SHBindToParentは第1引数のアイテムIDリストから親のアイテムを求め、その親のIShellFolderを返します。 IShellFolderを取得しなければGetUIObjectOfでIContextMenuを取得できないため、 これは必要な処理です。 ILFindLastIDは、アイテムIDリストから最後尾のIDを返します。 これつまり、フルパスの中からファイル名やフォルダ名の部分を取得するということです。 GetUIObjectOfの第3引数は、pShellFolderの子であるアイテムのIDを指定しなければならないため、 先に取得したpidlChildを指定することになります。 第4引数にIID_IContextMenuを指定することにより、第6引数からIContextMenuを取得することができます。 取得したIContextMenuを使用するコードは、次のようになっています。

hmenuPopup = CreatePopupMenu();
pContextMenu->QueryContextMenu(hmenuPopup, 0, 1, 0x7fff, CMF_NORMAL);

GetCursorPos(&pt);
nId = TrackPopupMenu(hmenuPopup, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, NULL);
if (nId == 0) {
	pContextMenu->Release();
	pShellFolder->Release();
	return FALSE;
}

ici.cbSize       = sizeof(CMINVOKECOMMANDINFO);
ici.fMask        = 0;
ici.hwnd         = hwnd;
ici.lpVerb       = (LPCSTR)MAKEINTRESOURCE(nId - 1);
ici.lpParameters = NULL;
ici.lpDirectory  = NULL;
ici.nShow        = SW_SHOW;

hr = pContextMenu->InvokeCommand(&ici);

QueryContextMenuを呼び出すと、メソッドが第1引数のポップアップメニューに適切な項目を追加してくれます。 このため、ポップアップメニュー自体はアプリケーションが作成しておくことになります。 ポップアップメニューが初期化されたら、GetCursorPosで現在のカーソルの位置を取得し、 TrackPopupMenuでポップアップメニューを表示します。 第2引数にTPM_RETURNCMDを指定していることから、戻り値は選択された項目のIDになります。 戻り値が0でない場合は何らかの項目が選択されたことを意味するため、 InvokeCommandを呼び出すことで項目の内容を実行します。 このとき、CMINVOKECOMMANDINFO構造体のlpVerbには、項目のIDからQueryContextMenuの第3引数の値を引いた値を指定します。

IContextMenu2について

IContextMenu::QueryContextMenuで追加されるメニュー項目には、 「プログラムから開く」や「送る」などが含まれますが、 既定ではこれらの項目は正しく実装されていません。 これらの項目を使用する場合は、IContextMenuではなくIContextMenu2を使用するようにし、 メニューメッセージをウインドウプロシージャで検出する必要があります。 次に示すShowContextMenuは、IContextMenu2を使用するように書き換えたものです。

BOOL ShowContextMenu(PIDLIST_ABSOLUTE pidlAbsolute, HWND hwnd)
{
	int                 nId;
	HRESULT             hr;
	POINT               pt;
	HMENU               hmenuPopup;
	IContextMenu        *pContextMenu;
	IShellFolder        *pShellFolder;
	PITEMID_CHILD       pidlChild;
	CMINVOKECOMMANDINFO ici;
	
	SHBindToParent(pidlAbsolute, IID_PPV_ARGS(&pShellFolder), NULL);
	pidlChild = ILFindLastID(pidlAbsolute);

	hr = pShellFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidlChild, IID_IContextMenu, NULL, (void **)&pContextMenu);
	if (hr != S_OK) {
		pShellFolder->Release();
		return FALSE;
	}
	
	hr = pContextMenu->QueryInterface(IID_PPV_ARGS(&g_pContextMenu2));
	if (FAILED(hr)) {
		pContextMenu->Release();
		pShellFolder->Release();
		return FALSE;
	}
	
	pContextMenu->Release();

	hmenuPopup = CreatePopupMenu();
	g_pContextMenu2->QueryContextMenu(hmenuPopup, 0, 1, 0x7fff, CMF_NORMAL);

	GetCursorPos(&pt);
	nId = TrackPopupMenu(hmenuPopup, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, NULL);
	if (nId == 0) {
		g_pContextMenu2->Release();
		g_pContextMenu2 = NULL;
		pShellFolder->Release();
		return FALSE;
	}

	ici.cbSize       = sizeof(CMINVOKECOMMANDINFO);
	ici.fMask        = 0;
	ici.hwnd         = hwnd;
	ici.lpVerb       = (LPCSTR)MAKEINTRESOURCE(nId - 1);
	ici.lpParameters = NULL;
	ici.lpDirectory  = NULL;
	ici.nShow        = SW_SHOW;
	
	hr = g_pContextMenu2->InvokeCommand(&ici);

	g_pContextMenu2->Release();
	g_pContextMenu2 = NULL;
	pShellFolder->Release();

	return SUCCEEDED(hr);
}

IContextMenu2を取得するには、IContextMenu::QueryInterfaceにIID_IContextMenu2を指定します。 一度、IContextMenuを取得しなければならないのが煩わしいところですが、 IShellFolder::GetUIObjectOfにIID_IContextMenu2を指定できないため仕方ありません。 IContextMenu2の使い方はIContextMenuとほぼ同様ですが、 ウインドウプロシージャ内にメニューメッセージが送られた場合は、 そのメッセージの情報をIContextMenu2::HandleMenuMsgに指定しなければなりません。

case WM_INITMENUPOPUP:
	if (g_pContextMenu2 != NULL)
		g_pContextMenu2->HandleMenuMsg(uMsg, wParam, lParam);
	return 0;

WM_INITMENUPOPUPは、TrackPopupMenuでポップアップメニューを表示する際に送られます。 g_pContextMenu2がNULLでない場合は初期化されていることを意味するため、 HandleMenuMsgを呼び出してメッセージの情報を指定することになります。 TrackPopupMenuの呼び出しを終えたShowContextMenuは、 使い終えたg_pContextMenu2にNULLを代入しておきます。



戻る