EternalWindows
シェル拡張 / ショートカットメニュー ハンドラ
サンプルはこちら

ショートカットメニュー ハンドラは、特定の拡張子を持ったファイルのショートカットメニューに、 独自の項目を追加する場合に使用します。 たとえば次の図では、ショートカットメニューにAという名前とBという名前の項目を追加しています。

ショートカットメニュー ハンドラのオブジェクトは、IContextMenuとIShellExtInitを実装することになります。 IContextMenuはメニューの初期化と実行のために使用され、 IShellExtInitはメニューの表示対象となるファイルを伝えるために使用されます。

class CContextMenu : public IContextMenu, public IShellExtInit
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();

	STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
	STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax);
	STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici);

	STDMETHODIMP Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
	
	CContextMenu();
	~CContextMenu();

private:
	LONG m_cRef;
};

QueryContextMenuからInvokeCommandまでがIContextMenuのメソッドであり、 InitializeがIShellExtInitのメソッドになります。

ファイル上で右クリックが実行された場合は、最初にInitializeが呼ばれます。 このメソッドでS_OKを返した場合はQueryContextMenuが呼ばれます。

STDMETHODIMP CContextMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
{
	return S_OK;
}

第2引数のpdtobjからはメニューの表示対象となるファイル名を取得できますが、 今回は必要がないためS_OKだけを返しています。

QueryContextMenuでは、第1引数のメニューハンドルに独自の項目を追加します。 既定の項目(削除やプロパティなど)は既に追加されている点に注意してください。

STDMETHODIMP CContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
	MENUITEMINFO mii;
	
	if (uFlags & CMF_DEFAULTONLY)
		return MAKE_SCODE(SEVERITY_SUCCESS, FACILITY_NULL, 0);

	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = idCmdFirst;
	mii.dwTypeData = TEXT("A");
	InsertMenuItem(hmenu, indexMenu, TRUE, &mii);
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = idCmdFirst + 1;
	mii.dwTypeData = TEXT("B");
	InsertMenuItem(hmenu, indexMenu + 3, TRUE, &mii);

	return MAKE_SCODE(SEVERITY_SUCCESS, FACILITY_NULL, 2);
}

uFlagsにCMF_DEFAULTONLYが含まれる場合は、デフォルトの項目だけがメニューに存在することが望まれます。 よって、このときには独自の項目を追加するべきではありません。 InsertMenuItemで項目を追加する際、項目のIDはidCmdFirstからカウントするようにします。 つまり、2回目のmii.wIDにはidCmdFirst + 1を指定するべきです。 項目を追加する位置はindexMenu以降であれば何処でもよいため、 1つ目の項目はindexMenuの位置(通常はメニューの先頭)に、 2つ目の項目はindexMenuから3番目の位置に追加しています。 MAKE_SCODEはHRESULT型を作成するマクロであり、 QueryContextMenuの場合は追加した項目の数を第3引数に指定します。

QueryContextMenuでメニューを初期化させたシェルは、TrackPopupMenuなどでメニューを表示します。 そして、独自の項目が追加された場合はInvokeCommandを呼び出します。

STDMETHODIMP CContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
	UINT idCmd = LOWORD(pici->lpVerb);

	if (HIWORD(pici->lpVerb) != 0)
		return E_INVALIDARG;

	if (idCmd == 0)
		MessageBox(NULL , TEXT("A"), TEXT("OK"), MB_OK);
	else if (idCmd == 1)
		MessageBox(NULL , TEXT("B"), TEXT("OK"), MB_OK);
	else
		;

	return S_OK;
}

pici->lpVerbの下位ワードには、選択された項目のオフセットが格納されています。 具体的にはこれは、項目のIDからQueryContextMenuのidCmdFirstを引いた値であり、 この値を基に項目の内容を実行することになります。 pici->lpVerbの上位ワードに0でない値が格納される場合は、処理を続行する必要はありません。

GetCommandStringは、メニュー項目上にカーソルを乗せた場合などに呼ばれます。

STDMETHODIMP CContextMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax)
{
	if (idCmd == 0) {
		if (uFlags == GCS_HELPTEXTA)
			lstrcpyA(pszName, "説明文A");
		else if (uFlags == GCS_HELPTEXTW)
			lstrcpyW((LPWSTR)pszName, L"説明文A");
		else if (uFlags == GCS_VERBA)
			lstrcpyA(pszName, "A");
		else if (uFlags == GCS_VERBW)
			lstrcpyW((LPWSTR)pszName, L"A");
		else
			;
	}
	else if (idCmd == 1) {
		if (uFlags == GCS_HELPTEXTA)
			lstrcpyA(pszName, "説明文B");
		else if (uFlags == GCS_HELPTEXTW)
			lstrcpyW((LPWSTR)pszName, L"説明文B");
		else if (uFlags == GCS_VERBA)
			lstrcpyA(pszName, "B");
		else if (uFlags == GCS_VERBW)
			lstrcpyW((LPWSTR)pszName, L"B");
		else
			;
	}
	else
		return E_FAIL;

	return S_OK;
}

uFlagsにGCS_HELPTEXTAが格納されている場合は、項目の説明文をANSI文字列でpszNameに格納します。 逆に、GCS_HELPTEXTWが格納されている場合は、項目の説明文をUNICODE文字列でpszNameに格納します。 こうした説明文は、一般的にステータスバーへ表示されることになります。 GCS_VERBAまたはGCS_VERBWが格納されている場合は、項目の名前をpszNameに格納します。

ショートカットメニュー ハンドラを登録するには、次に示すレジストリキーを作成することになります。

HKEY_CLASSES_ROOT
 <特定の拡張子> (既定) = オブジェクトのProgID

HKEY_CLASSES_ROOT
  <オブジェクトのProgID>
    shellex
      ContextMenuHandlers
        独自のハンドラ名 (既定) = オブジェクトのCLSID

ショートカットメニュー ハンドラのDLLは、ファイル上で右クリックした場合にロードされ、 自動でアンロードされることはありません。 登録したDLLを置き換える場合は、登録を解除してからシェルを再起動する必要があります。


戻る