none
コンテキストメニューに複数のファイルを渡したい

    質問

  • 以前間違えて質問したのですが、以下のコードで、一応単一ファイルを渡してコンテキストメニューを開くところまでできました。

    質問は、表題のとおり、これを、エクスプローラーのように、複数のファイルを渡してメニューを開くようにするにはどうしたら良いかということです。

    色々調べたのですが、ITEMIDLISTは難しくて未だ理解できていません。
    このコードも、あちこちのサンプルから引っ張ってきて、なんとか動いていますが、理解できていないところが沢山あります。

    ITEMIDLISTは、複数のファイルも格納できるようですが、実際の手順がわかりません。

    apidlFull = ILCreateFromPath(sFname);

    これを複数のファイルに対して実行して、それを連ねれば良い気もしますが、そういうのを作れる関数とかが見つかりません。

    最終的には、

    if SUCCEEDED(pSF->GetUIObjectOf(hwnd, 1, &apidl, IID_IContextMenu, NULL, (LPVOID*)&pCM)) {

    ここに渡すapidlがその連なったITEMIDLISTのアドレスで良いのでしょうか?

    複数のファイルの場合、

    SHBindToParent(apidlFull, IID_PPV_ARGS(&pSF), &apidl);
    pidlParent = ILClone(apidlFull);
    ILRemoveLastID(pidlParent);

    この辺りがどうなるのかもわかりません。

    実は、以下のコードも、エクスプローラーで右クリックした場合のメニューよりは若干項目が少なく、本来はエクスプローラーの右クリックと同じメニューを出したかったのですが、そのあたりもわかっていません。

    色々書きましたが、先ずは複数ファイルを渡してメニューを開きたいです。

    よろしくお願いします。

    BOOL CFileView::PopCntMenu(HWND hwnd, CPoint pt, CString sFname)
    {
    
    	ClientToScreen(&pt);
    
    	IShellFolder *pSF = NULL;
    
    	IShellFolder *pSF2 = NULL;
    	LPCITEMIDLIST pidl = NULL;
    	LPITEMIDLIST pidlFull = NULL;
    	LPITEMIDLIST pidlParent = NULL;
    
    	BOOL Result = false;
    
    	int g_nCount = 1;
    	BSTR g_bsInvoke = NULL;
    	IDropTarget *g_pDropTarget = NULL;
    	DWORD g_grfKeyState = MK_RBUTTON;
    
    	const ITEMIDLIST *apidl;
    	ITEMIDLIST *apidlFull;
    
    	CMINVOKECOMMANDINFO cmi;
    	ZeroMemory(&cmi, sizeof(CMINVOKECOMMANDINFO));
    
    	cmi.nShow = SW_SHOWNORMAL;
    	UINT uFlags = CMF_NORMAL | CMF_CANRENAME;
    
    	g_pCCM = NULL;
    
    
    	apidlFull = ILCreateFromPath(sFname);
    	if (apidlFull) {
    		SHBindToParent(apidlFull, IID_PPV_ARGS(&pSF), &apidl);
    		pidlParent = ILClone(apidlFull);
    		ILRemoveLastID(pidlParent);
    	}
    
    	if (pSF) {
    		IContextMenu *pCM;
    		if SUCCEEDED(pSF->GetUIObjectOf(hwnd, 1, &apidl, IID_IContextMenu, NULL, (LPVOID*)&pCM)) {
    			g_pCCM = new CContextMenu(pCM, hwnd);
    			pCM->Release();
    
    			HMENU hMenu;
    			hMenu = CreatePopupMenu();
    			char szVerbA[MAX_PATH];
    			if SUCCEEDED(g_pCCM->QueryContextMenu(hMenu, 0, 1, 0x7fff, uFlags)) {
    				Result = true;
    				SetForegroundWindow();
    				int nVerb = 0;
    				LPCSTR lpVerb = NULL;
    				if (g_bsInvoke) {
    					if (lstrcmpi(g_bsInvoke, L"Default") == 0) {
    						nVerb = GetMenuDefaultItem(hMenu, 0, 0);
    					}
    					else {
    						nVerb = g_pCCM->TCMFindVerb(hMenu);
    						if (nVerb == 0) {
    							ZeroMemory(szVerbA, sizeof(szVerbA));
    							if (WideCharToMultiByte(CP_ACP, NULL, g_bsInvoke, -1, szVerbA, sizeof(szVerbA), NULL, NULL)) {
    								lpVerb = (LPCSTR)szVerbA;
    							}
    						}
    					}
    				}
    				else {
    					nVerb = TrackPopupMenuEx(hMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD,
    						pt.x, pt.y, hwnd, NULL);
    				}
    				if (nVerb) {
    					lpVerb = (LPCSTR)MAKEINTRESOURCE(nVerb - 1);
    				}
    				if (lpVerb) {
    					cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
    					cmi.hwnd = hwnd;
    					cmi.lpVerb = lpVerb;
    					g_pCCM->InvokeCommand(&cmi);
    				}
    				if (g_bsInvoke) {
    					::SysFreeString(g_bsInvoke);
    					g_bsInvoke = NULL;
    				}
    			}
    			DestroyMenu(hMenu);
    			g_pCCM->Release();
    		}
    		pSF->Release();
    	}
    
    
    	CoTaskMemFree(apidlFull);
    
    	if (pidlParent) {
    		::CoTaskMemFree(pidlParent);
    		pidlParent = NULL;
    	}
    
    	return Result;
    }
    
    

    2018年3月30日 4:54

回答

  • 質問するときは最低限のマナーとして、環境に関する情報(OSおよびIDEの詳細なバージョン、フレームワークなど)を毎回詳しく書くようにしてください。

    技術系メーリングリストで質問するときのパターン・ランゲージ

    提示されたソースコードは、一部の関数の実装や変数の型が明らかでない不完全・不明瞭な状態であり、また実際に使われていない変数の宣言などの余計なコードが含まれています。説明する際は過不足のないようにしてください。もし仮に自分自身が同じような質問を受けたときに、何が問題となっているのかを支障なく読み解くのに必要十分な情報を提供できているか、常に吟味するようにしてください。いわゆる説明力を磨いてください。

    おそらく

    https://github.com/tablacus/TablacusContextMenu/tree/master/TCM

    を改変したものだと推測しますが、もとにしたものがあるのであればその旨説明するべきです。ソースコードは著作物です。派生コードであることを説明しなかった場合、場合によってはライセンス違反に問われることもあります。なお、このコードはC++の作法がなっていない、行儀の悪いコードであり、参考にするのはやめたほうがいいです。

    要約すると、「Windows標準エクスプローラー上で、複数のアイテムを選択した状態で右クリックしたときに表示されるコンテキストメニューと同じものを表示したい」

    ということでよいですか?

    ひとまず、Visual Studio 2015以降の環境で、MFCのMDI/SDIアプリケーションプロジェクトテンプレートをベースにしたものであり、CFileViewというのはFileView.h/.cppに宣言・定義されている、CDockablePaneの派生クラスであると仮定して話を進めます。

    stdafx.hに以下のコードを追加します。

    #include <vector>
    #include <memory>

    FileView.hのCFileViewクラスに、以下のメンバーを追加します。

    private:
    	IContextMenu3* m_pContextMenu = nullptr;
    private:
    #if 0
    	virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam) override;
    #else
    	virtual BOOL OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) override;
    #endif

    FileView.cppの先頭あたり、各種ヘッダーインクルードの後に以下のヘルパーを追加します。

    namespace MyShellHelpers
    {
    	bool ShowShellContextMenuImpl(IShellFolder* pShellFolder, CWnd* pWnd, CPoint point, LPCITEMIDLIST itemIdListPtrs[], UINT itemIdListCount, IContextMenu3*& pContextMenuExtra)
    	{
    		if (pShellFolder && itemIdListPtrs)
    		{
    			ATLASSERT(pContextMenuExtra == nullptr);
    			ATL::CComPtr<IContextMenu> pContextMenu;
    			// IShellFolder::GetUIObjectOf() の第3引数の型は PCUITEMID_CHILD_ARRAY になっている。LPCITEMIDLIST* 相当。
    			// LPCITEMIDLIST 配列先頭要素のアドレスを指定する。
    			// 第2引数で配列の要素数を指定する。
    			if (SUCCEEDED(pShellFolder->GetUIObjectOf(pWnd->GetSafeHwnd(), itemIdListCount, itemIdListPtrs, IID_IContextMenu, nullptr, reinterpret_cast<void**>(&pContextMenu))) &&
    				SUCCEEDED(pContextMenu->QueryInterface(IID_PPV_ARGS(&pContextMenuExtra))))
    			{
    				CMenu menu;
    				menu.CreatePopupMenu();
    				const UINT uFlags = CMF_NORMAL | CMF_CANRENAME;
    				if (SUCCEEDED(pContextMenuExtra->QueryContextMenu(menu.GetSafeHmenu(), 0, 1, 0x7fff, uFlags)))
    				{
    					pWnd->ClientToScreen(&point);
    					pWnd->SetForegroundWindow();
    					const int selectedMenuItemId = menu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, pWnd, nullptr);
    					if (selectedMenuItemId > 0)
    					{
    						CMINVOKECOMMANDINFO cmi = {};
    						cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
    						cmi.nShow = SW_SHOWNORMAL;
    						cmi.hwnd = pWnd->GetSafeHwnd();
    						cmi.lpVerb = MAKEINTRESOURCEA(selectedMenuItemId - 1);
    						pContextMenuExtra->InvokeCommand(&cmi);
    						return true;
    					}
    				}
    			}
    		}
    
    		return false;
    	}
    
    	template<typename T> ULONG SafeReleaseAndGetLastRefCount(T*& ptr)
    	{
    		if (ptr)
    		{
    			const auto refCount = ptr->Release();
    			ptr = nullptr;
    			return refCount;
    		}
    		return 0;
    	}
    
    	struct CoTaskMemDeleter
    	{
    		void operator ()(void* ptr)
    		{
    			::CoTaskMemFree(ptr);
    		}
    	};
    
    	using TItemIdListUniquePtr = std::unique_ptr<ITEMIDLIST, CoTaskMemDeleter>;
    
    	// 単一のファイルパスのみに対応する例。配列バージョンがスーパーセットになるので、実際は必要ない。
    	bool ShowShellContextMenuImpl(CWnd* pWnd, CPoint point, const CString& strFilePath, IContextMenu3*& pContextMenuExtra)
    	{
    		ATL::CComPtr<IShellFolder> pShellFolder;
    
    		LPCITEMIDLIST pidlLast = nullptr;
    		// 古い OS では ITEMIDLIST の解放に ILFree() を使う必要があったが、Windows 2000 以降は CoTaskMemFree() を使ってよいとのこと。
    		// https://msdn.microsoft.com/en-us/library/windows/desktop/bb776441.aspx
    		ATL::CComHeapPtr<ITEMIDLIST> pidlFull(::ILCreateFromPath(strFilePath));
    		if (pidlFull)
    		{
    			ATLVERIFY(SUCCEEDED(::SHBindToParent(pidlFull, IID_PPV_ARGS(&pShellFolder), &pidlLast)));
    			//ATL::CComHeapPtr<ITEMIDLIST> pidlParent(::ILClone(pidlFull));
    			//::ILRemoveLastID(pidlParent);
    		}
    
    		if (pidlLast)
    		{
    			return ShowShellContextMenuImpl(pShellFolder, pWnd, point, &pidlLast, 1, pContextMenuExtra);
    		}
    		else
    		{
    			return false;
    		}
    	}
    
    	bool ShowShellContextMenuImpl(CWnd* pWnd, CPoint point, const std::vector<CString>& strFilePaths, IContextMenu3*& pContextMenuExtra)
    	{
    		ATL::CComPtr<IShellFolder> pShellFolder;
    
    		// 従来からある ATL/MFC の CStringArray や CArray<CString> を使う方法もあるが、C++11 の便利な構文や機能が使えないので却下。
    
    		std::vector<LPCITEMIDLIST> pidlLasts;
    		std::vector<TItemIdListUniquePtr> pidlFulls;
    		for (const auto& strFilePath : strFilePaths)
    		{
    			TItemIdListUniquePtr pidlFull(::ILCreateFromPath(strFilePath));
    			if (pidlFull)
    			{
    				LPCITEMIDLIST pidlLast = nullptr;
    				if (pShellFolder)
    				{
    					pidlLast = ::ILFindLastID(pidlFull.get());
    				}
    				else
    				{
    					ATLVERIFY(SUCCEEDED(::SHBindToParent(pidlFull.get(), IID_PPV_ARGS(&pShellFolder), &pidlLast)));
    				}
    				ATLASSERT(pidlLast);
    				if (pidlLast)
    				{
    					pidlLasts.push_back(pidlLast);
    				}
    				pidlFulls.push_back(std::move(pidlFull));
    			}
    		}
    
    		if (pidlLasts.empty())
    		{
    			return false;
    		}
    		else
    		{
    			return ShowShellContextMenuImpl(pShellFolder, pWnd, point, pidlLasts.data(), static_cast<UINT>(pidlLasts.size()), pContextMenuExtra);
    		}
    	}
    
    	bool ShowShellContextMenu(CWnd* pWnd, CPoint point, const CString& strFilePath, IContextMenu3*& pContextMenuExtra)
    	{
    		const bool ret = ShowShellContextMenuImpl(pWnd, point, strFilePath, pContextMenuExtra);
    		ATLVERIFY(SafeReleaseAndGetLastRefCount(pContextMenuExtra) == 0);
    		return ret;
    	}
    
    	bool ShowShellContextMenu(CWnd* pWnd, CPoint point, const std::vector<CString>& strFilePaths, IContextMenu3*& pContextMenuExtra)
    	{
    		const bool ret = ShowShellContextMenuImpl(pWnd, point, strFilePaths, pContextMenuExtra);
    		ATLVERIFY(SafeReleaseAndGetLastRefCount(pContextMenuExtra) == 0);
    		return ret;
    	}
    }

    FileView.cppの末尾あたりに、以下のメンバー関数オーバーライド実装部を追加します。例ではCWnd::OnWndMsg()をオーバーライドしていますが、CWnd::WindowProc()のオーバーライドでも可です。

    #if 0
    LRESULT CFileView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
    {
    	// TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。
    
    	switch (message)
    	{
    	case WM_INITMENUPOPUP:
    	case WM_DRAWITEM:
    	case WM_MENUCHAR:
    	case WM_MEASUREITEM:
    		if (m_pContextMenu)
    		{
    			LRESULT ret = 0;
    			const HRESULT hr = m_pContextMenu->HandleMenuMsg2(message, wParam, lParam, &ret);
    			ATLTRACE(_T("Msg = 0x%08x, Result of IContextMenu3::HandleMenuMsg2() = 0x%08lx\n"), message, hr);
    			return ret;
    		}
    		break;
    	default:
    		break;
    	}
    
    	return __super::WindowProc(message, wParam, lParam);
    }
    #else
    BOOL CFileView::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
    	// TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。
    
    	switch (message)
    	{
    	case WM_INITMENUPOPUP:
    	case WM_DRAWITEM:
    	case WM_MENUCHAR:
    	case WM_MEASUREITEM:
    		if (m_pContextMenu)
    		{
    			const HRESULT hr = m_pContextMenu->HandleMenuMsg2(message, wParam, lParam, pResult);
    			ATLTRACE(_T("Msg = 0x%08x, Result of IContextMenu3::HandleMenuMsg2() = 0x%08lx\n"), message, hr);
    			return true;
    		}
    		break;
    	default:
    		break;
    	}
    
    	return __super::OnWndMsg(message, wParam, lParam, pResult);
    }
    #endif

    最後に、コンテキストメニューを表示するトリガーとなるイベントのハンドラーに、以下のようなコードを追加します。例としてWM_RBUTTONUPメッセージハンドラーを定義しています。

    void CFileView::OnRButtonUp(UINT nFlags, CPoint point)
    {
    	// TODO: ここにメッセージ ハンドラー コードを追加するか、既定の処理を呼び出します。
    
    	std::vector<CString> selectedFilePaths;
    
    	...
    	selectedFilePaths.push_back(...);
    	...
    
    	MyShellHelpers::ShowShellContextMenu(this, point, selectedFilePaths, m_pContextMenu);
    	ATLASSERT(m_pContextMenu == nullptr);
    
    	__super::OnRButtonUp(nFlags, point);
    }

    おそらくCFileViewとやらで、ファイル一覧あるいはディレクトリ階層などを表示するためのリストビューもしくはツリービューか何かを保持しているのだと思われますが、それらのコントロール上にて選択された複数のアイテムに対応するファイルパスを取得して、動的配列 (vector) に追加する部分は、自力で実装してください。まず標準C++とMFCの基本について学習することを推奨します。

    余談ですが、g_およびm_というプレフィックスは、それぞれグローバル変数およびメンバー変数のスコープを意味する、ハンガリアン記法の一種です。混乱を避けるため、それらのプレフィックスをローカル変数名に使ってはいけません。また、Windows APIでは関数の引数にaプレフィックスが使われていることがありますが、たいてい配列 (array) を意味します。配列あるいは配列先頭要素へのポインタでないものに、aプレフィックスは付けません。

    なお、Contextの意味でCntという略語を使われたのだと推察しますが、cntはcuntという卑語を連想させるため、使わないほうがいいです。変数名はできるだけ省略せず、分かりやすい名前を付けるようにします。

    • 編集済み sygh 2018年4月6日 18:27
    • 回答としてマーク annkn1957 2018年4月7日 15:29
    2018年4月6日 15:25

すべての返信

  • 質問するときは最低限のマナーとして、環境に関する情報(OSおよびIDEの詳細なバージョン、フレームワークなど)を毎回詳しく書くようにしてください。

    技術系メーリングリストで質問するときのパターン・ランゲージ

    提示されたソースコードは、一部の関数の実装や変数の型が明らかでない不完全・不明瞭な状態であり、また実際に使われていない変数の宣言などの余計なコードが含まれています。説明する際は過不足のないようにしてください。もし仮に自分自身が同じような質問を受けたときに、何が問題となっているのかを支障なく読み解くのに必要十分な情報を提供できているか、常に吟味するようにしてください。いわゆる説明力を磨いてください。

    おそらく

    https://github.com/tablacus/TablacusContextMenu/tree/master/TCM

    を改変したものだと推測しますが、もとにしたものがあるのであればその旨説明するべきです。ソースコードは著作物です。派生コードであることを説明しなかった場合、場合によってはライセンス違反に問われることもあります。なお、このコードはC++の作法がなっていない、行儀の悪いコードであり、参考にするのはやめたほうがいいです。

    要約すると、「Windows標準エクスプローラー上で、複数のアイテムを選択した状態で右クリックしたときに表示されるコンテキストメニューと同じものを表示したい」

    ということでよいですか?

    ひとまず、Visual Studio 2015以降の環境で、MFCのMDI/SDIアプリケーションプロジェクトテンプレートをベースにしたものであり、CFileViewというのはFileView.h/.cppに宣言・定義されている、CDockablePaneの派生クラスであると仮定して話を進めます。

    stdafx.hに以下のコードを追加します。

    #include <vector>
    #include <memory>

    FileView.hのCFileViewクラスに、以下のメンバーを追加します。

    private:
    	IContextMenu3* m_pContextMenu = nullptr;
    private:
    #if 0
    	virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam) override;
    #else
    	virtual BOOL OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) override;
    #endif

    FileView.cppの先頭あたり、各種ヘッダーインクルードの後に以下のヘルパーを追加します。

    namespace MyShellHelpers
    {
    	bool ShowShellContextMenuImpl(IShellFolder* pShellFolder, CWnd* pWnd, CPoint point, LPCITEMIDLIST itemIdListPtrs[], UINT itemIdListCount, IContextMenu3*& pContextMenuExtra)
    	{
    		if (pShellFolder && itemIdListPtrs)
    		{
    			ATLASSERT(pContextMenuExtra == nullptr);
    			ATL::CComPtr<IContextMenu> pContextMenu;
    			// IShellFolder::GetUIObjectOf() の第3引数の型は PCUITEMID_CHILD_ARRAY になっている。LPCITEMIDLIST* 相当。
    			// LPCITEMIDLIST 配列先頭要素のアドレスを指定する。
    			// 第2引数で配列の要素数を指定する。
    			if (SUCCEEDED(pShellFolder->GetUIObjectOf(pWnd->GetSafeHwnd(), itemIdListCount, itemIdListPtrs, IID_IContextMenu, nullptr, reinterpret_cast<void**>(&pContextMenu))) &&
    				SUCCEEDED(pContextMenu->QueryInterface(IID_PPV_ARGS(&pContextMenuExtra))))
    			{
    				CMenu menu;
    				menu.CreatePopupMenu();
    				const UINT uFlags = CMF_NORMAL | CMF_CANRENAME;
    				if (SUCCEEDED(pContextMenuExtra->QueryContextMenu(menu.GetSafeHmenu(), 0, 1, 0x7fff, uFlags)))
    				{
    					pWnd->ClientToScreen(&point);
    					pWnd->SetForegroundWindow();
    					const int selectedMenuItemId = menu.TrackPopupMenuEx(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, pWnd, nullptr);
    					if (selectedMenuItemId > 0)
    					{
    						CMINVOKECOMMANDINFO cmi = {};
    						cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
    						cmi.nShow = SW_SHOWNORMAL;
    						cmi.hwnd = pWnd->GetSafeHwnd();
    						cmi.lpVerb = MAKEINTRESOURCEA(selectedMenuItemId - 1);
    						pContextMenuExtra->InvokeCommand(&cmi);
    						return true;
    					}
    				}
    			}
    		}
    
    		return false;
    	}
    
    	template<typename T> ULONG SafeReleaseAndGetLastRefCount(T*& ptr)
    	{
    		if (ptr)
    		{
    			const auto refCount = ptr->Release();
    			ptr = nullptr;
    			return refCount;
    		}
    		return 0;
    	}
    
    	struct CoTaskMemDeleter
    	{
    		void operator ()(void* ptr)
    		{
    			::CoTaskMemFree(ptr);
    		}
    	};
    
    	using TItemIdListUniquePtr = std::unique_ptr<ITEMIDLIST, CoTaskMemDeleter>;
    
    	// 単一のファイルパスのみに対応する例。配列バージョンがスーパーセットになるので、実際は必要ない。
    	bool ShowShellContextMenuImpl(CWnd* pWnd, CPoint point, const CString& strFilePath, IContextMenu3*& pContextMenuExtra)
    	{
    		ATL::CComPtr<IShellFolder> pShellFolder;
    
    		LPCITEMIDLIST pidlLast = nullptr;
    		// 古い OS では ITEMIDLIST の解放に ILFree() を使う必要があったが、Windows 2000 以降は CoTaskMemFree() を使ってよいとのこと。
    		// https://msdn.microsoft.com/en-us/library/windows/desktop/bb776441.aspx
    		ATL::CComHeapPtr<ITEMIDLIST> pidlFull(::ILCreateFromPath(strFilePath));
    		if (pidlFull)
    		{
    			ATLVERIFY(SUCCEEDED(::SHBindToParent(pidlFull, IID_PPV_ARGS(&pShellFolder), &pidlLast)));
    			//ATL::CComHeapPtr<ITEMIDLIST> pidlParent(::ILClone(pidlFull));
    			//::ILRemoveLastID(pidlParent);
    		}
    
    		if (pidlLast)
    		{
    			return ShowShellContextMenuImpl(pShellFolder, pWnd, point, &pidlLast, 1, pContextMenuExtra);
    		}
    		else
    		{
    			return false;
    		}
    	}
    
    	bool ShowShellContextMenuImpl(CWnd* pWnd, CPoint point, const std::vector<CString>& strFilePaths, IContextMenu3*& pContextMenuExtra)
    	{
    		ATL::CComPtr<IShellFolder> pShellFolder;
    
    		// 従来からある ATL/MFC の CStringArray や CArray<CString> を使う方法もあるが、C++11 の便利な構文や機能が使えないので却下。
    
    		std::vector<LPCITEMIDLIST> pidlLasts;
    		std::vector<TItemIdListUniquePtr> pidlFulls;
    		for (const auto& strFilePath : strFilePaths)
    		{
    			TItemIdListUniquePtr pidlFull(::ILCreateFromPath(strFilePath));
    			if (pidlFull)
    			{
    				LPCITEMIDLIST pidlLast = nullptr;
    				if (pShellFolder)
    				{
    					pidlLast = ::ILFindLastID(pidlFull.get());
    				}
    				else
    				{
    					ATLVERIFY(SUCCEEDED(::SHBindToParent(pidlFull.get(), IID_PPV_ARGS(&pShellFolder), &pidlLast)));
    				}
    				ATLASSERT(pidlLast);
    				if (pidlLast)
    				{
    					pidlLasts.push_back(pidlLast);
    				}
    				pidlFulls.push_back(std::move(pidlFull));
    			}
    		}
    
    		if (pidlLasts.empty())
    		{
    			return false;
    		}
    		else
    		{
    			return ShowShellContextMenuImpl(pShellFolder, pWnd, point, pidlLasts.data(), static_cast<UINT>(pidlLasts.size()), pContextMenuExtra);
    		}
    	}
    
    	bool ShowShellContextMenu(CWnd* pWnd, CPoint point, const CString& strFilePath, IContextMenu3*& pContextMenuExtra)
    	{
    		const bool ret = ShowShellContextMenuImpl(pWnd, point, strFilePath, pContextMenuExtra);
    		ATLVERIFY(SafeReleaseAndGetLastRefCount(pContextMenuExtra) == 0);
    		return ret;
    	}
    
    	bool ShowShellContextMenu(CWnd* pWnd, CPoint point, const std::vector<CString>& strFilePaths, IContextMenu3*& pContextMenuExtra)
    	{
    		const bool ret = ShowShellContextMenuImpl(pWnd, point, strFilePaths, pContextMenuExtra);
    		ATLVERIFY(SafeReleaseAndGetLastRefCount(pContextMenuExtra) == 0);
    		return ret;
    	}
    }

    FileView.cppの末尾あたりに、以下のメンバー関数オーバーライド実装部を追加します。例ではCWnd::OnWndMsg()をオーバーライドしていますが、CWnd::WindowProc()のオーバーライドでも可です。

    #if 0
    LRESULT CFileView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
    {
    	// TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。
    
    	switch (message)
    	{
    	case WM_INITMENUPOPUP:
    	case WM_DRAWITEM:
    	case WM_MENUCHAR:
    	case WM_MEASUREITEM:
    		if (m_pContextMenu)
    		{
    			LRESULT ret = 0;
    			const HRESULT hr = m_pContextMenu->HandleMenuMsg2(message, wParam, lParam, &ret);
    			ATLTRACE(_T("Msg = 0x%08x, Result of IContextMenu3::HandleMenuMsg2() = 0x%08lx\n"), message, hr);
    			return ret;
    		}
    		break;
    	default:
    		break;
    	}
    
    	return __super::WindowProc(message, wParam, lParam);
    }
    #else
    BOOL CFileView::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
    	// TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。
    
    	switch (message)
    	{
    	case WM_INITMENUPOPUP:
    	case WM_DRAWITEM:
    	case WM_MENUCHAR:
    	case WM_MEASUREITEM:
    		if (m_pContextMenu)
    		{
    			const HRESULT hr = m_pContextMenu->HandleMenuMsg2(message, wParam, lParam, pResult);
    			ATLTRACE(_T("Msg = 0x%08x, Result of IContextMenu3::HandleMenuMsg2() = 0x%08lx\n"), message, hr);
    			return true;
    		}
    		break;
    	default:
    		break;
    	}
    
    	return __super::OnWndMsg(message, wParam, lParam, pResult);
    }
    #endif

    最後に、コンテキストメニューを表示するトリガーとなるイベントのハンドラーに、以下のようなコードを追加します。例としてWM_RBUTTONUPメッセージハンドラーを定義しています。

    void CFileView::OnRButtonUp(UINT nFlags, CPoint point)
    {
    	// TODO: ここにメッセージ ハンドラー コードを追加するか、既定の処理を呼び出します。
    
    	std::vector<CString> selectedFilePaths;
    
    	...
    	selectedFilePaths.push_back(...);
    	...
    
    	MyShellHelpers::ShowShellContextMenu(this, point, selectedFilePaths, m_pContextMenu);
    	ATLASSERT(m_pContextMenu == nullptr);
    
    	__super::OnRButtonUp(nFlags, point);
    }

    おそらくCFileViewとやらで、ファイル一覧あるいはディレクトリ階層などを表示するためのリストビューもしくはツリービューか何かを保持しているのだと思われますが、それらのコントロール上にて選択された複数のアイテムに対応するファイルパスを取得して、動的配列 (vector) に追加する部分は、自力で実装してください。まず標準C++とMFCの基本について学習することを推奨します。

    余談ですが、g_およびm_というプレフィックスは、それぞれグローバル変数およびメンバー変数のスコープを意味する、ハンガリアン記法の一種です。混乱を避けるため、それらのプレフィックスをローカル変数名に使ってはいけません。また、Windows APIでは関数の引数にaプレフィックスが使われていることがありますが、たいてい配列 (array) を意味します。配列あるいは配列先頭要素へのポインタでないものに、aプレフィックスは付けません。

    なお、Contextの意味でCntという略語を使われたのだと推察しますが、cntはcuntという卑語を連想させるため、使わないほうがいいです。変数名はできるだけ省略せず、分かりやすい名前を付けるようにします。

    • 編集済み sygh 2018年4月6日 18:27
    • 回答としてマーク annkn1957 2018年4月7日 15:29
    2018年4月6日 15:25
  • sygh さま


    返信ありがとうございます。

    開発環境情報及び参照元ソースの記載ができておらず、大変申し訳ありません。

    また、不完全なソースについても重ねて申し訳ありません。
    色々な他のソースも含めて、ご指摘の通り私のレベルではどうにも理解が足りず、試行錯誤してようやくここまで来ましたが、不要なソースが含まれている事も気づいていませんでした。

    さて、詳細なソースの提示大変ありがとうございます。
    ITEMIDLISTを複数用意して、渡すような形でもう少し簡単にできるかと考えていましたが、甘かったようです。
    よって、上記のようなスキルしか持ち合わせておりませんので、このソースを使わせて頂いて、うまく動作させることができるかどうかまだ不明ですが、頑張って勉強させていただきます。

    そんなわけですので、まだ動作はできておりませんが、使えるようになるのにまだ時間がかかると思いますので、とりあえず現時点で回答としてマークさせていただきます。
    詳細なアドバイス及びソースの提示に感謝いたします。
    ありがとうございました。

    2018年4月7日 15:29