EternalWindows
WebBrowser コントロール / ホストの実装

今回は、WebBrowser コントロールをホストするオブジェクトについて説明します。 このオブジェクトの目的は、次のようにWebページを表示することです。

WebBrowser コントロールをホストするとは、WebBrowser コントロールをインプレースアクティベーションするということです。 インプレースアクティベーションとは、オブジェクト(WebBrowser コントロール)のウインドウをホストが用意したウインドウの子ウインドウとして作成し、 そのウインドウをホストのウインドウ上に表示することです。 WebBrowser コントロールはCOMオブジェクトである一方で、IOleObject(及びIOleInPlaceObject)を実装するOLEオブジェクトであるため、 インプレースアクティベーションをサポートしています。

WebBrowser コントロールをホストするオブジェクトはCWebBrowserHostで識別され、CRebarMgrによって使用されます。 CWebBrowserContainerもCWebBrowserHostを使用しますが、 これは現在アクティブになっているCWebBrowserHostに限ります。 CRebarMgrは新しいタブが作成される段階になると、CWebBrowserHostを作成してCreateを呼び出します。

BOOL CWebBrowserHost::Create(HWND hwndParent, HWND hwndRebar, LPWSTR lpszUrl, int nId)
{
	IOleObject *pOleObject;
	HRESULT    hr;
	MSG        msg;
	RECT       rc;

	hr = CoCreateInstance(CLSID_WebBrowser, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pWebBrowser2));
	if (FAILED(hr))
		return FALSE;

	m_nId = nId;

	m_hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, g_szHostClassName, NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndParent, (HMENU)m_nId, NULL, NULL);
	SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR)this);

	m_pWebBrowser2->QueryInterface(IID_PPV_ARGS(&pOleObject));
	pOleObject->SetClientSite(static_cast<IOleClientSite *>(this));

	SetRectEmpty(&rc);
	hr = pOleObject->DoVerb(OLEIVERB_INPLACEACTIVATE, &msg, static_cast<IOleClientSite *>(this), 0, m_hwnd, &rc);
	if (FAILED(hr)) {
		pOleObject->Release();
		m_pWebBrowser2->Release();
		m_pWebBrowser2 = NULL;
		return FALSE;
	}

	Navigate(lpszUrl);
	
	m_pBandSite = new CBandSite();
	m_pBandSite->Create(m_pWebBrowser2, hwndRebar);

	m_pEventSink = new CEventSink();
	m_pEventSink->Create(this, pOleObject);
	
	pOleObject->Release();

	return TRUE;
}

WebBrowser コントロールを作成するには、CoCreateInstanceにCLSID_WebBrowserを指定します。 コントロールをインプレースアクティベーションするためには、その対象となるウインドウが必要になるため、 CreateWindowExで作成するようにしています。 thisポインタを関連付けているのは、静的なWindowProcからクラスのWindowProcにアクセスできるようにするためです。 インプレースアクティベーションを行うためのメソッドはIOleObjectに含まれているため、 m_pWebBrowser2からこれを照会します。 SetClientSiteは、ホスト側のオブジェクトをコントロールに渡すために必ず呼び出します。 DoVerbにOLEIVERB_INPLACEACTIVATEを指定すれば、第5引数のウインドウに対してインプレースアクティベーションが実行されます。 現在のままではWebBrowser コントロールのウインドウには何も表示されませんが、 IWebBrowser2::Navigateを呼び出せば実際にページへアクセスできます。 Navigateという自作メソッドは、この呼び出しをラッピングしています。 CBandSiteは、バンドオブジェクト(IEツールバー)を管理するオブジェクトであり、 CEventSinkはWebBrowser コントロールからのイベントを取得するオブジェクトです。 これらの詳細ついては、後の節で取り上げます。

Navigateの実装は次のようになっています。

BOOL CWebBrowserHost::Navigate(LPWSTR lpszUrl)
{
	HRESULT hr;
	BSTR    bstrUrl;
	VARIANT varFlags, varTargetFrameName, varPostData, varHeaders;

	if (lpszUrl == NULL)
		bstrUrl = SysAllocString(L"");
	else
		bstrUrl = SysAllocString(lpszUrl);

	VariantInit(&varFlags);
	VariantInit(&varTargetFrameName);
	VariantInit(&varPostData);
	VariantInit(&varHeaders);

	hr = m_pWebBrowser2->Navigate(bstrUrl, &varFlags, &varTargetFrameName, &varPostData, &varHeaders);
	
	SysFreeString(bstrUrl);

	return hr == S_OK;
}

IWebBrowser2::Navigateは、アクセスするURLをBSTR型で要求します。 よって、SysAllocStringでこれを作成します。 lpszUrlがNULLの場合はデフォルトのhtmlファイルを返すことができますが、 今回はそうしたファイルを用意していないため、空の文字列を指定しています。 IWebBrowser2::Navigateの第2引数以降は、0に初期化したVARIANT構造体を指定していますが、 NULLを指定しても問題ありません。

CWebBrowserContainerは、ホストのウインドウを自身のウインドウの適切な位置へ表示するために、SetWindowSizeを呼び出します。

void CWebBrowserHost::SetWindowSize(int x, int y, int nWidth, int nHeight)
{
	MoveWindow(m_hwnd, x, y, nWidth, nHeight, TRUE);
}

MoveWindowによってウインドウのサイズを調整します。 これによってWM_SIZEが生成され、次の処理が実行されます。

case WM_SIZE: {
	RECT              rc = {0, 0, LOWORD(lParam), HIWORD(lParam)};
	IOleInPlaceObject *pOleInPlaceObject;

	m_pWebBrowser2->QueryInterface(IID_PPV_ARGS(&pOleInPlaceObject));
	pOleInPlaceObject->SetObjectRects(&rc, &rc);
	pOleInPlaceObject->Release();
	return 0;
}

ウインドウのサイズ調整に伴って、WebBrowser コントロールのサイズも調整しなければならないため、 IOleInPlaceObject::SetObjectRectsを呼び出すようにしています。 rcの中身から分かるように、WebBrowser コントロールはウインドウ全体を覆います。

WebBrowser コントロールがフォーカスを持っている状態で何らかのアクセラレータキーが押下されたら、 そのアクセラレータキーはWebBrowser コントロールによって処理されるべきといえます。 このような機会を与えるために、CWebBrowserContainerはTranslateAcceleratorを呼び出します。

HRESULT CWebBrowserHost::TranslateAccelerator(LPMSG lpMsg)
{
	HRESULT hr;

	if (m_pBandSite != NULL)
		hr = m_pBandSite->TranslateAccelerator(lpMsg);

	if (hr != S_OK) {
		IOleInPlaceActiveObject *pOleInPlaceActiveObject;
		m_pWebBrowser2->QueryInterface(IID_PPV_ARGS(&pOleInPlaceActiveObject));
		hr = pOleInPlaceActiveObject->TranslateAcceleratorW(lpMsg);
		pOleInPlaceActiveObject->Release();
	}

	return hr;
}

CWebBrowserHostはバンドオブジェクトも管理しているため、 まずバンドオブジェクトがアクセラレータキーを処理できるかを調べます。 この結果がS_OKでない場合は、アクセラレータキーを処理しなかったということなので、 WebBrowser コントロールにアクセラレータキーを処理させるようにします。 これには、IOleInPlaceActiveObject::TranslateAcceleratorWを呼び出します。

WebBrowser コントロールのホスト側は、コントロールに対してアンビエントプロパティを公開できます。 アンビエントプロパティとはホストの環境情報であり、コントロールがこれを取得できるようになると、 ホストが望んでいる環境を再現しようとします。 アンビエントプロパティは、IDispatch::Invokeを通じて公開することができます。

STDMETHODIMP CWebBrowserHost::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
	if (dispIdMember == DISPID_AMBIENT_DLCONTROL) {
		pVarResult->vt = VT_I4;
		pVarResult->lVal = m_dwAmbientDLControl;
		return S_OK;
	}

	return DISP_E_MEMBERNOTFOUND;
}

今回処理しているアンビエントプロパティは、DISPID_AMBIENT_DLCONTROLのみです。 このアンビエントプロパティは、「画像のダウンロード」や「スクリプトの実行禁止」などを制御するためのもので、 たとえばm_dwAmbientDLControlにDLCTL_DLIMAGESが含まれていた場合は、 コントロールは「画像のダウンロード」を有効にします。 また、DLCTL_NO_SCRIPTSが含まれる場合は、「スクリプトの実行禁止」を有効にします。 m_dwAmbientDLControlの値は、AmbientPropertyChangeという自作メソッドで変更されることがあります。

void CWebBrowserHost::AmbientPropertyChange(int nId)
{
	DWORD dwAmbientDLControl[] = {DLCTL_DLIMAGES, DLCTL_VIDEOS, DLCTL_BGSOUNDS,
		DLCTL_NO_SCRIPTS, DLCTL_NO_JAVA, DLCTL_NO_RUNACTIVEXCTLS, DLCTL_NO_DLACTIVEXCTLS
	};
	IOleControl *pOleControl;

	m_dwAmbientDLControl ^= dwAmbientDLControl[nId];

	m_pWebBrowser2->QueryInterface(IID_PPV_ARGS(&pOleControl));
	pOleControl->OnAmbientPropertyChange(DISPID_AMBIENT_DLCONTROL);
	pOleControl->Release();
}

トップメニューの「ツール」から「画像のダウンロード」などが選択された場合は、 それに関連する定数を反転することになります。 これにより、プロパティが有効だったときは無効になり、無効だった場合は有効になります。 ただし、この変更したアンビエントプロパティをコントロールに通知しなければ変更結果が反映されませんから、 IOleControl::OnAmbientPropertyChangeでこれを通知します。 これにより、コントロールはIDispatch::Invokeを呼び出してm_dwAmbientDLControlの値を取得します。

既定のWebBrowser コントロールでは、ページでスクリプトエラーが発生した場合にダイアログを表示します。 これを回避する場合はIOleCommandTargetを実装し、OLECMDID_SHOWSCRIPTERRORという定数を処理します。

STDMETHODIMP CWebBrowserHost::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
{
	if (nCmdID == OLECMDID_SHOWSCRIPTERROR && IsEqualGUID(*pguidCmdGroup, CGID_DocHostCommandHandler)) {
		pvaOut->vt = VT_BOOL;
		pvaOut->boolVal = VARIANT_TRUE;
		return S_OK;
	}

	return OLECMDERR_E_NOTSUPPORTED;
}

IOleCommandTarget::ExecにOLECMDID_SHOWSCRIPTERRORが渡された場合は、 スクリプトエラーを表示しないかどうかを示す値を返すことができます。 vtメンバにVT_BOOLを指定した場合はboolValメンバを初期化することができ、 VARIANT_TRUEの場合はエラーを表示しないことを意味します。 なお、ここで返す値はE_NOTIMPLではなく、OLECMDERR_E_NOTSUPPORTEDを返すようにします。 E_NOTIMPLを返した場合、ステータスバーのズームパートが不正になることを確認しています。

先のアンビエントプロパティやスクリプトエラーの処理で、 WebBrowser コントロールの動作をいくつか変更できましたが、 IDocHostUIHandlerを実装するようになれば、さらに変更できる範囲が増えます。 各メソッドを順に見ていきます。

STDMETHODIMP CWebBrowserHost::ShowContextMenu(DWORD dwID, POINT *ppt, IUnknown *pcmdtReserved, IDispatch *pdispReserved)
{
	return S_FALSE;
}

ShowContextMenuは、コンテキストメニューが表示されようとしているときに呼ばれます。 dwIDは表示されようとしているコンテキストメニューの種類を表す定数が格納され、 通常のページ上であればCONTEXT_MENU_DEFAULT、リンク上であればCONTEXT_MENU_ANCHORになります。 pptは、コンテキストメニューを表示すべきスクリーン座標が格納されます。 pcmdtReservedは、IOleCommandTargetやIOleWindowを実装したインターフェースが格納されます。 pdispReservedは、IHTMLElementを実装したインターフェースが格納されます。 戻り値としてS_OKを返した場合は既定のコンテキストメニューが表示されないため、 このときにはTrackPopupMenuで自作のコンテキストメニューを表示できます。 既定のコンテキストメニューが表示されてほしい場合は、S_FALSEを返すようにします。

STDMETHODIMP CWebBrowserHost::GetHostInfo(DOCHOSTUIINFO *pInfo)
{
	pInfo->cbSize        = sizeof(DOCHOSTUIINFO);
	pInfo->dwFlags       = DOCHOSTUIFLAG_NO3DBORDER;
	pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
	pInfo->pchHostCss    = NULL;
	pInfo->pchHostNS     = NULL;

	return S_OK;
}

GetHostInfoは、ホストの情報を取得する目的で呼ばれます。 実装せずにE_NOTIMPLを返すこともできますが、 上記ではDOCHOSTUIINFO構造体を初期化してS_OKを返すようにしています。 cbSizeには構造体のサイズを指定します。 dwFlagsは、MSHTMLのUIに関する定数を指定します。 数多くの定数が定義されていますが、たとえばDOCHOSTUIFLAG_FLAT_SCROLLBARを指定するとスクロールバーがフラットになります。 DOCHOSTUIFLAG_NO3DBORDERは、ページにボーダーが表示されるのを防ぎます。 dwDoubleClickは、ダブルクリックの際に行うべき操作を示す定数を指定します。 通常は、デフォルトの操作を示すDOCHOSTUIDBLCLK_DEFAULTを指定します。 pchHostCssは、スタイルシートのルールを示す文字列を指定します。 不要な場合はNULLで問題ありません。 pchHostNSは、セミコロンで区切られた名前空間のリストを指定します。 不要な場合はNULLで問題ありません。 pchHostCssとpchHostNSに指定する文字列は、CoTaskMemAllocで確保されている必要があります。

STDMETHODIMP CWebBrowserHost::ShowUI(DWORD dwID, IOleInPlaceActiveObject *pActiveObject, IOleCommandTarget *pCommandTarget, IOleInPlaceFrame *pFrame, IOleInPlaceUIWindow *pDoc)
{
	return S_FALSE;
}

ShowUIは、ホストにMSHTMLのメニューやツールバーを置き換えるタイミングを与えるために呼ばれますが、詳しくはよく分かりません。 S_FALSEを返した場合はMSHTMLがメニューやツールバーを表示するとされていますが、 実際にはこれを返してもそれらの表示は確認できません。 S_OKを返した場合は、ホストがMSHTMLの代わりにUIを表示するべきなようですが、 それを具体的にどう行えばよいのかがよく分かりません。 たとえば、IOleInPlaceFrame::SetMenuはコンテナにメニューを設定させるメソッドですが、 これを呼び出しても何も起こることはありません。 具体的な実装方法が分からないということで、E_NOTIMPLを返しています。

STDMETHODIMP CWebBrowserHost::HideUI(VOID)
{
	return E_NOTIMPL;
}

HideUIは、ShowUIで表示したUIを消去する段階になると呼ばれます。 ShowUIを実装していない場合は、E_NOTIMPLを返すだけでよいと思われます。

STDMETHODIMP CWebBrowserHost::UpdateUI(VOID)
{
	return E_NOTIMPL;
}

UpdateUIは、コマンドの状態が変化した場合に呼び出されるとされていますが、詳しくは分かりません。 ShowUIを実装していない場合は、E_NOTIMPLを返すだけでよいと思われます。

STDMETHODIMP CWebBrowserHost::GetOptionKeyPath(LPOLESTR *pchKey, DWORD dw)
{
	return E_NOTIMPL;
}

GetOptionKeyPathは、登録情報を格納したレジストリキーを取得する目的で呼ばれます。 E_NOTIMPLを返せば、既定のSoftware/Microsoft/Internet Explorerが参照されることになりますが、 S_OKを返した場合はpchKeyに格納したキーが参照されるようになります。 キーは事前にHKEY_CURRENT_USER以下に作成しておかなければならず、 文字列を返しただけではキーが作成されることはない点に注意してください。 既定のキーが参照されない場合はいくつかの情報が失われるため、 たとえばShowContextMenuで表示されるコンテキストメニューには、 拡張メニュー項目(MenuExtキー以下)が表示されないことになります。

STDMETHODIMP CWebBrowserHost::GetDropTarget(IDropTarget *pDropTarget, IDropTarget **ppDropTarget)
{
	return E_NOTIMPL;
}

GetDropTargetは、ホストが提供するIDropTargetを取得する目的で呼ばれます。 もし、ホストがppDropTargetに自作のIDropTargetを設定してS_OKを返せば、 D&Dに関する通知を受け取れるようになります。 pDropTargetは既定のIDropTargetであり、これを自作のIDropTargetで使用することもできるでしょう。 D&Dを独自に処理するつもりがない場合は、E_NOTIMPLを返すだけで問題ありません。

STDMETHODIMP CWebBrowserHost::TranslateUrl(DWORD dwTranslate, OLECHAR *pchURLIn, OLECHAR **ppchURLOut)
{
	return S_FALSE;
}

TranslateUrlは、新しいページにアクセスしようとした場合に呼ばれます。 初めてページにアクセスする場合は呼ばれないことに注意してください。 dwTranslateは予約されているため常に0となり、pchURLInはアクセス先となるURLが格納されます。 S_FALSEを返した場合は、通常通りpchURLInのURLにアクセスされることになりますが、 S_OKを返した場合はppchURLOutに格納されたURLにアクセスされることになります。 pchURLInではなく、任意のページにアクセスさせたい場合は、 CoTaskMemAllocで確保したメモリにアクセス先のURLを格納し、 このメモリをppchURLOutに指定します。

STDMETHODIMP CWebBrowserHost::FilterDataObject(IDataObject *pDO, IDataObject **ppDORet)
{
	return E_NOTIMPL;
}

FilterDataObjectは、ホストがMSHTMLのデータオブジェクトを置き換えられるとされていますが、 実際に呼び出しを確認することはできませんでした。 実装しないということでE_NOTIMPLを返しています。

STDMETHODIMP CWebBrowserHost::EnableModeless(BOOL fEnable)
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::OnDocWindowActivate(BOOL fActivate)
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::OnFrameWindowActivate(BOOL fActivate)
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::ResizeBorder(LPCRECT prcBorder, IOleInPlaceUIWindow *pUIWindow, BOOL fFrameWindow)
{
	return E_NOTIMPL;
}

STDMETHODIMP CWebBrowserHost::TranslateAccelerator(LPMSG lpMsg, const GUID *pguidCmdGroup, DWORD nCmdID)
{
	return E_NOTIMPL;
}

これらのメソッドについてはよく分かりません。 IOleInPlaceActiveObjectの各メソッドと同じ意味を持つようですが、 実際に呼び出しを確認できたのはEnableModelessのみでした。 IOleInPlaceActiveObjectは、埋め込みオブジェクトが実装することになるインターフェースですが、 何故このインターフェースに含まれるメソッドがIDocHostUIHandlerにも含まれているのかがよく分かりません。

STDMETHODIMP CWebBrowserHost::GetExternal(IDispatch **ppDispatch)
{
	*ppDispatch = NULL;

	return E_NOTIMPL;
}

GetExternalは、スクリプト言語などがホストの機能を使用するために呼ばれます。 今回は特別な処理を行っていませんが、次のような処理に対応したい場合は実装する必要があります。

<script type = "text/javascript">
window.external.yourMethod();
</script>

window.externalオブジェクトを使用すれば、ホストが公開しているメソッドを呼び出すことができます。 このとき、最初にGetExternalが呼び出されるため、IDispatchを実装したオブジェクトを返すことになります。

STDMETHODIMP CWebBrowserHost::GetExternal(IDispatch **ppDispatch)
{
	return QueryInterface(IID_PPV_ARGS(ppDispatch));
}

ホストがIDispatchを実装している場合は、QueryInterfaceを呼び出すだけでIDispatchを返すことができます。 ただし、今回のホストがIDispatchを実装しているのはアンビエントプロパティを公開するためであり、 メソッドの処理にも同じIDispatchを使用するのは、適切であるか難しいところです。 IDispatchを返したら、次にIDispatch::GetIDsOfNamesが呼ばれます。

STDMETHODIMP CWebBrowserHost::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
	if (lstrcmpW(rgszNames[0], L"yourMethod") == 0) {
		*rgDispId = 100;
		return S_OK;
	}

	return DISP_E_UNKNOWNNAME;
}

指定されたメソッド名をホストがサポートする場合は、そのメソッドのDISPIDを返すようにします。 DISPIDを取得した呼び出し側は、IDispatch::Invokeを呼び出してメソッドを実行します。

STDMETHODIMP CWebBrowserHost::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
	if (dispIdMember == 100) {
		MessageBox(NULL, TEXT("メソッドが呼ばれました。"), TEXT("yourMethod"), MB_OK);
		return S_OK;
	}
	
	return DISP_E_MEMBERNOTFOUND;
}

サポートするメソッドのDISPIDが指定されたら、if文の中でメソッドの処理を実行します。 上記ではMessageBoxを呼び出しているだけですから、スクリプト言語からすれば、 yourMethodの呼び出しはメッセージボックスの表示というように見えます。

既定のコンテキストメニューの取得

IDocHostUIHandler::ShowContextMenuを独自に処理するような場合は、 全く独自のコンテキストメニューを表示するよりも、 既定のコンテキストメニューをベースに独自の項目を追加したメニューを表示するほうが多いと思われます。 このような場合は、既定のコンテキストメニューを事前に取得しておく必要がありますが、 これは以下の手順で可能になります。

#define IDR_BROWSE_CONTEXT_MENU  24641
#define SHDVID_GETMIMECSETMENU   27
#define SHDVID_ADDMENUEXTENSIONS 53

HMENU GetDefaultContextMenu(DWORD dwID, IUnknown *pcmdtReserved)
{
	HMODULE           hmod;
	HMENU             hmenu, hmenuPopup;
	IOleCommandTarget *pOleCommandTarget;
	BOOL              bLanguageMenu = TRUE;
	BOOL              bExtensions = TRUE;

	hmod = LoadLibrary(TEXT("shdoclc.dll"));
	if (hmod == NULL) {
		hmod = LoadLibrary(TEXT("ieframe.dll"));
		if (hmod == NULL)
			return NULL;
	}
	
	pcmdtReserved->QueryInterface(IID_PPV_ARGS(&pOleCommandTarget));
	
	hmenu = LoadMenu(hmod, MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));
	hmenuPopup = GetSubMenu(hmenu, dwID);

	if (bLanguageMenu) {
		VARIANT      var;
		MENUITEMINFO mii;

		VariantInit(&var);
		pOleCommandTarget->Exec(&CGID_ShellDocView, SHDVID_GETMIMECSETMENU, 0, NULL, &var);

		mii.cbSize   = sizeof(mii);
		mii.fMask    = MIIM_SUBMENU;
		mii.hSubMenu = (HMENU)var.byref;
		SetMenuItemInfo(hmenuPopup, IDM_LANGUAGE, FALSE, &mii);
	}

	if (bExtensions) {
		VARIANT var1, var2;

		var1.vt = VT_INT_PTR;
		var1.byref = hmenuPopup;
		var2.vt = VT_I4;
		var2.iVal = dwID;
		pOleCommandTarget->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);
	}

	pOleCommandTarget->Release();
	FreeLibrary(hmod);

	return hmenu;
}

IEで使用されているコンテキストメニューは、shdoclc.dllからロードできるとされています。 しかし、このDLLはIE7から存在しなくなっていると思われるため、 そのような場合はieframe.dllをロードするようにします。 モジュール(DLL)のハンドルを取得したら、それをLoadMenuに指定してメニューをロードします。 このときに指定するIDは、24641という値でなければなりません。 ロードしたメニューは複数のサブメニューを持っており、 アプリケーションがどれを必要としているかは、dwIDを頼りにGetSubMenuを呼び出せば分かります。 bLanguageMenuがTRUEである場合は、コンテキストメニューの「エンコード」という項目に言語メニューを追加します。 これを行うにはIOleCommandTarget::ExecでSHDVID_GETMIMECSETMENUを実行し、 戻り値として返されたメニュー(var.byref)をコンテキストメニューに設定します。 bExtensionsがTRUEである場合は、コンテキストメニューに拡張メニュー項目を追加します。 これを行うには、コンテキストメニューとIDを引数としてSHDVID_ADDMENUEXTENSIONSを実行するようにします。 拡張メニュー項目は、HKEY_CURRENT_USER/Software/Microsoft/Internet Explorer/MenuExtから確認できます。

上記したGetDefaultContextMenuを使用する例を次に示します。

STDMETHODIMP CWebBrowserHost::ShowContextMenu(DWORD dwID, POINT *ppt, IUnknown *pcmdtReserved, IDispatch *pdispReserved)
{
	int          nId;
	HWND         hwnd;
	HMENU        hmenu, hmenuPopup;
	MENUITEMINFO mii;
	IOleWindow   *pOleWindow;

	pcmdtReserved->QueryInterface(IID_PPV_ARGS(&pOleWindow));
	pOleWindow->GetWindow(&hwnd);

	hmenu = GetDefaultContextMenu(dwID, pcmdtReserved);
	hmenuPopup = GetSubMenu(hmenu, dwID);

	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = IDM_MENUEXT_FIRST__;
	mii.dwTypeData = TEXT("sample");
	InsertMenuItem(hmenuPopup, IDM_MENUEXT_PLACEHOLDER, FALSE, &mii);

	nId = TrackPopupMenu(hmenuPopup, TPM_RETURNCMD, ppt->x, ppt->y, 0, hwnd, NULL);
	if (nId != 0) {
		if (nId == IDM_MENUEXT_FIRST__)
			MessageBox(NULL, TEXT("独自の項目が選択されました。"), TEXT("OK"), MB_OK);
		else
			SendMessage(hwnd, WM_COMMAND, nId, NULL);
	}

	DestroyMenu(hmenu);
	pOleWindow->Release();

	return S_OK;
}

GetDefaultContextMenuが返したメニューに対してGetSubMenuを呼び出せば、目的のコンテキストメニューを取得できます。 InsertMenuItemで独自の項目を追加する際には、その項目のIDがIDM_MENUEXT_FIRST__からIDM_MENUEXT_LAST__の間でなければならないことに注意してください。 項目をメニューの最後に追加する場合は、InsertMenuItemの第2引数にIDM_MENUEXT_PLACEHOLDERを指定するようにしますが、 任意の位置に追加したい場合はそのインデックスを第2引数に指定し、第3引数にTRUEを指定します。 TrackPopupMenuを呼び出せば実際にコンテキストメニューが表示されますが、 第2引数にTPM_RETURNCMDを指定している場合は、戻り値として項目のIDが返ることになります。 0でない場合は何らかの項目が選択されたことを意味し、 これがIDM_MENUEXT_FIRST__である場合は今回追加した項目が選択されたことを意味します。 それ以外の場合は既定の項目ということで、ウインドウに処理を任しています。



戻る