EternalWindows
WebBrowser コントロール / メニューの実装

メニューはCMenuMgrで識別され、CWebBrowserContainerによって使用されます。 CWebBrowserContainerはメニューを作成したくなった段階でCMenuMgr::Createを呼び出し、 ここではメニュー項目を一つずつ作成していきます。 ただし、お気に入りのメニューについては、選択された際に作成することになっているため、 この関数では作成していません。 メニューの作成が完了すればSetMenuが呼ばれ、ウインドウにメニューが割り当てられます。

メニュー項目が選択された場合は、CMenuMgr::OnMenuCommandが呼ばれます。 各項目の処理を順に見ていきます。

if (nId == ID_FILE_NEWTAB)
	g_pWebBrowserContainer->Navigate(NULL, TRUE);

ID_FILE_NEWTABは、「新規タブ」という項目を識別します。 CWebBrowserContainer::Navigateの第2引数にTRUEを指定すれば、 第1引数のページが新しいタブで開かれます。 タブの実装方法については次節で説明します。

else if (nId == ID_FILE_SAVEAS)
	pWebBrowserHost->Exec(OLECMDID_SAVEAS, OLECMDEXECOPT_DODEFAULT, NULL, NULL);

ID_FILE_SAVEASは、「名前を付けて保存」という項目を識別します。 CWebBrowserHost::Execは内部でIWebBrowser::ExecWBを呼び出すことになっており、 これに対してOLECMDID_SAVEASを指定すれば、保存のためのダイアログが表示されます。

else if (nId == ID_FILE_OFFLINE) {
	INTERNET_CONNECTED_INFO connectedInfo;
	DWORD                   dwState, dwLength;

	dwLength = sizeof(DWORD);
	InternetQueryOption(NULL, INTERNET_OPTION_CONNECTED_STATE, &dwState, &dwLength);

	if (dwState & INTERNET_STATE_CONNECTED) {
		connectedInfo.dwConnectedState = INTERNET_STATE_DISCONNECTED_BY_USER;
		connectedInfo.dwFlags          = ISO_FORCE_DISCONNECTED;
	}
	else {
		connectedInfo.dwConnectedState = INTERNET_STATE_CONNECTED;
		connectedInfo.dwFlags          = 0;
	}

	InternetSetOption(NULL, INTERNET_OPTION_CONNECTED_STATE, &connectedInfo, sizeof(INTERNET_CONNECTED_INFO));
}

ID_FILE_OFFLINEは、「オフライン作業」という項目を識別します。 InternetQueryOptionにINTERNET_OPTION_CONNECTED_STATEを指定すれば現在の状態を取得でき、 これにINTERNET_STATE_CONNECTEDが含まれている場合は、現在オンライン状態であることを意味しています。 よって、この場合はオフライン状態に変更するということで、 INTERNET_CONNECTED_INFO構造体のdwConnectedStateにINTERNET_STATE_DISCONNECTED_BY_USERを指定します。 また、dwFlagsにはISO_FORCE_DISCONNECTEDを指定します。 現在の状態がオフラインである場合は、オンラインにするためにINTERNET_STATE_CONNECTEDを指定します。 このときには、dwFlagsは0で問題ありません。 INTERNET_CONNECTED_INFO構造体の初期化が完了したら、 INTERNET_OPTION_CONNECTED_STATEを指定してInternetSetOptionを呼び出します。 これで、現在の状態が変化します。

else if (nId == ID_FILE_PROPERTY)
	pWebBrowserHost->Exec(OLECMDID_PROPERTIES, OLECMDEXECOPT_DODEFAULT, NULL, NULL);

ID_FILE_PROPERTYは、「プロパティ」という項目を識別します。 OLECMDID_PROPERTIESを指定すれば、ファイルのプロパティのダイアログが表示されます。

else if (nId == ID_EDIT_CUT)
	pWebBrowserHost->Exec(OLECMDID_COPY, OLECMDEXECOPT_DODEFAULT, NULL, NULL);
else if (nId == ID_EDIT_COPY)
	pWebBrowserHost->Exec(OLECMDID_CUT, OLECMDEXECOPT_DODEFAULT, NULL, NULL);
else if (nId == ID_EDIT_PASTE)
	pWebBrowserHost->Exec(OLECMDID_PASTE, OLECMDEXECOPT_DODEFAULT, NULL, NULL);
else if (nId == ID_EDIT_SELECTALL)
	pWebBrowserHost->Exec(OLECMDID_SELECTALL, OLECMDEXECOPT_DODEFAULT, NULL, NULL);
else if (nId == ID_EDIT_FIND)
	pWebBrowserHost->Exec(OLECMDID_FIND, OLECMDEXECOPT_DODEFAULT, NULL, NULL);

これらの項目は、編集メニューの中に含まれています。 各項目の動作を行うために必要な定数は、Execに指定された第1引数の通りです。

else if (nId == ID_VIEW_SIDEBAR)
	g_pWebBrowserContainer->ShowSidebar(!g_pWebBrowserContainer->IsShowSidebar());

ID_VIEW_SIDEBARは、「サイドバーの表示」という項目を識別します。 現在のサイドバーの表示状態はCWebBrowserContainer::IsShowSidebarで取得でき、 これを反転さしてShowSidebarを呼び出せば、サイドバーの表示状態が変更されます。

else if (nId == ID_VIEW_SOURCE) {
	const GUID CGID_IWebBrowser = {0xED016940L, 0xBD5B, 0x11cf, {0xBA, 0x4E, 0x00, 0xC0, 0x4F, 0xD7, 0x08, 0x16}};
	const int HTMLID_VIEWSOURCE = 2;
	pWebBrowserHost->ExecDocument(&CGID_IWebBrowser, HTMLID_VIEWSOURCE, 0, NULL, NULL);
}

ID_VIEW_SOURCEは、「ソース」という項目を識別します。 ExecDocumentというメソッドは、内部でIWebBrowser2::ExecWBを呼び出すのではなく、 IOleCommandTarget::Execを呼び出すようになっています。 そして、そのときに必要になるGUIDがCGID_IWebBrowserです。 これに対して2という値を指定した場合は、現在開いているページのソースが表示されます。

else if (nId == ID_FONT_LARGEST
|| nId == ID_FONT_LEARGE
|| nId == ID_FONT_MIDDLE
|| nId == ID_FONT_SMALL
|| nId == ID_FONT_SMALLEST) {
	VARIANT var;
	var.vt = VT_I4;
	var.lVal = ID_FONT_SMALLEST - nId;
	pWebBrowserHost->Exec(OLECMDID_ZOOM, OLECMDEXECOPT_DODEFAULT, &var, NULL);
}

これらの項目は、「文字のサイズ」という項目の中に含まれています。 文字のサイズはOLECMDID_ZOOMで変更することが可能であり、 第3引数にはそのサイズをVARIANT構造体で指定します。 サイズの単位は最大が4であり、最小が0になります。

else if (nId == ID_VIEW_UPDATE) {
	IWebBrowser2 *pWebBrowser2;
	pWebBrowserHost->QueryBrowserInterface(IID_PPV_ARGS(&pWebBrowser2));
	pWebBrowser2->Refresh();
	pWebBrowser2->Release();
}

ID_VIEW_UPDATEは、「更新」という項目を識別します。 QueryBrowserInterfaceからIWebBrowser2を取得し、Refreshを呼び出せば更新が行われます。

else if (nId == ID_VIEW_PRIVACY) {
	IWebBrowser2 *pWebBrowser2;
	pWebBrowserHost->QueryBrowserInterface(IID_PPV_ARGS(&pWebBrowser2));
	ShowPrivacyDlg(m_hwndParent, pWebBrowser2);
	pWebBrowser2->Release();
}

void ShowPrivacyDlg(HWND hwndParent, IWebBrowser2 *pWebBrowser2)
{
	typedef HRESULT (WINAPI *LPFNDOPRIVACYDLG)(HWND, LPCWSTR, IEnumPrivacyRecords *, BOOL);

	HMODULE             hmod;
	LPFNDOPRIVACYDLG    lpfnDoPrivacyDlg;
	BSTR                URL;
	IDispatch           *pDispatch;
	IServiceProvider    *pServiceProvider;
	IEnumPrivacyRecords *pEnumPrivacyRecords;
	BOOL                bPrivacyImpacted;
	
	hmod = LoadLibrary(TEXT("shdocvw.dll"));
	if (hmod == NULL)
		return;

	lpfnDoPrivacyDlg = (LPFNDOPRIVACYDLG)GetProcAddress(hmod, "DoPrivacyDlg");
	if (lpfnDoPrivacyDlg == NULL) {
		FreeLibrary(hmod);
		return;
	}

	pWebBrowser2->get_Document(&pDispatch);
	pDispatch->QueryInterface(IID_PPV_ARGS(&pServiceProvider));
	pServiceProvider->QueryService(IID_IEnumPrivacyRecords, IID_PPV_ARGS(&pEnumPrivacyRecords));
	pServiceProvider->Release();
	pDispatch->Release();

	pWebBrowser2->get_LocationURL(&URL);
	pEnumPrivacyRecords->GetPrivacyImpacted(&bPrivacyImpacted);
	lpfnDoPrivacyDlg(hwndParent, URL, pEnumPrivacyRecords, !bPrivacyImpacted);

	SysFreeString(URL);
	pEnumPrivacyRecords->Release();
	FreeLibrary(hmod);
}

ID_VIEW_PRIVACYは、「プライバシーレポート」という項目を識別します。 プライバシーレポートには、ページからCookieの要求があったものの、 それを遮断したリストが含まれています。 DoPrivacyDlgという関数を呼び出せば、そのようなリストをダイアログとして表示できるため、 この関数の呼び出しをラッピングしたShowPrivacyDlgというメソッドを呼び出しています。 DoPrivacyDlgはヘッダーファイルには定義されていますが、 リンクに必要なインポートライブラリ(shdocvw.lib)は最新のSDKにしか含まれていないため、 関数に明示的リンクするようにしています。 DoPrivacyDlgの第1引数はダイアログの親ウインドウとするハンドルであり、 第2引数は確認の対象とするURLを指定します。 第3引数はIEnumPrivacyRecordsを指定しなければなりませんが、 これはドキュメントのQueryServiceに対してIID_IEnumPrivacyRecordsを指定すれば取得できます。 第4引数は要求されたCookieを全て表示する場合にTRUEを指定し、 遮断されたCookieを表示する場合にFALSEを指定します。 IEnumPrivacyRecords::GetPrivacyImpactedがTRUEを返す場合は、 ステータスバーにプライバシーのアイコンを表示できることを意味し、 そのようなときは遮断されたCookieのみを表示するのが適切です。 よって、GetPrivacyImpactedの結果を反転させています。

else if (
	nId == ID_AMBIENT_PICDOWNLOAD ||
	nId == ID_AMBIENT_PLAYSOUND ||
	nId == ID_AMBIENT_PLAYVIDEO ||
	nId == ID_AMBIENT_NOSCRIPT ||
	nId == ID_AMBIENT_NOJAVA ||
	nId == ID_AMBIENT_NOACTIVEX ||
	nId == ID_AMBIENT_NOACTIVEXDOWNLOAD
	) {
		pWebBrowserHost->AmbientPropertyChange(nId - ID_AMBIENT_PICDOWNLOAD);
}

これらの項目は、「アンビエントプロパティ」という項目の中に含まれています。 nId - ID_AMBIENT_PICDOWNLOADによって、選択された項目のインデックスが求められ、 これをAmbientPropertyChangeに指定することで、 インデックスに関連するアンビエントプロパティの有効/無効が変化します。 アンビエントプロパティがどのようなものであるかは、ホストの実装を参照してください。

else if (
	nId == ID_FEATURE_POPUPMANAGEMENT ||
	nId == ID_FEATURE_FILEDOWNLOAD ||
	nId == ID_FEATURE_SECURITYBAND
	) {
		HRESULT             hr;
		INTERNETFEATURELIST featueEntry;

		if (nId == ID_FEATURE_POPUPMANAGEMENT)
			featueEntry = FEATURE_WEBOC_POPUPMANAGEMENT;
		else if (nId == ID_FEATURE_FILEDOWNLOAD)
			featueEntry = FEATURE_RESTRICT_FILEDOWNLOAD;
		else
			featueEntry = FEATURE_SECURITYBAND;

		hr = CoInternetIsFeatureEnabled(featueEntry, GET_FEATURE_FROM_PROCESS);
		CoInternetSetFeatureEnabled(featueEntry, SET_FEATURE_ON_PROCESS, hr == S_OK ? FALSE : TRUE);
}

これらの項目は、「フィーチャ」という項目の中に含まれています。 各IDに関連するフィーチャの定数を求めたら、 CoInternetIsFeatureEnabledを呼び出してフィーチャが有効であるかを確認します。 そして、この結果を反転させてCoInternetSetFeatureEnabledを呼び出すことにより、 フィーチャの設定が切り替わります。 フィーチャの定数の詳細については、コンテナの実装を参照してください。

else if (
	nId == ID_COOKIE_NO ||
	nId == ID_COOKIE_HIGH ||
	nId == ID_COOKIE_MEDIUMHIGH ||
	nId == ID_COOKIE_MEDIUM ||
	nId == ID_COOKIE_MEDIUMLOW ||
	nId == ID_COOKIE_LOW
	) {
		DWORD dwTemplate[] = {
			PRIVACY_TEMPLATE_NO_COOKIES, PRIVACY_TEMPLATE_HIGH, PRIVACY_TEMPLATE_MEDIUM_HIGH,
			PRIVACY_TEMPLATE_MEDIUM, PRIVACY_TEMPLATE_MEDIUM_LOW, PRIVACY_TEMPLATE_LOW
		};

		PrivacySetZonePreferenceW(URLZONE_INTERNET, PRIVACY_TYPE_FIRST_PARTY, dwTemplate[nId - ID_COOKIE_NO], NULL);
		PrivacySetZonePreferenceW(URLZONE_INTERNET, PRIVACY_TYPE_THIRD_PARTY, dwTemplate[nId - ID_COOKIE_NO], NULL);
}

これらの項目は、「Cookie」という項目の中に含まれています。 Cookieの設定はPrivacySetZonePreferenceWで可能であり、 インターネットのCookieを設定する場合は、第1引数にURLZONE_INTERNETを指定します。 第3引数はCookieのレベルを示す定数を指定でき、 これらはPRIVACY_TEMPLATE_XXXという形で定義されています。 dwTemplateという配列によって、メニュー項目のIDと定数を関連付けています。

else if (nId == ID_TOOL_INCIDENT) {
	NDFHANDLE hNDF;
	HRESULT   hr;

	hr = NdfCreateConnectivityIncident(&hNDF);
	if (SUCCEEDED(hr)) {
		NdfExecuteDiagnosis(hNDF, NULL);
		NdfCloseIncident(hNDF);
	}
}

ID_TOOL_INCIDENTは、「接続の問題の診断」という項目を識別します。 これを実行するためには、Windows Vistaから登場したNDF(Network Diagnostics Framework)というAPIを使用します。 診断を実行するにはNdfExecuteDiagnosisを呼び出すことになりますが、 このためには診断の種類を示すハンドルを第1引数に指定しなければなりません。 NDFが診断できる問題は様々存在しますが、一般的な接続問題を診断する場合は、 NdfCreateConnectivityIncidentを呼び出してハンドルを取得すればよいと思われます。 不要になったハンドルは、NdfCloseIncidentで閉じることになります。

else if (nId == ID_TOOL_INETOPTION) {
	TCHAR szCplFile[] = TEXT("inetcpl.cpl");
	ShellExecute(NULL, TEXT("open"), TEXT("control"), szCplFile, NULL, SW_SHOWNORMAL);
}

ID_TOOL_INETOPTIONは、「インターネットオプション」という項目を識別します。 ShellExecuteの第3引数は起動するファイル名であり、controlはcontrol.exeを表しています。 これに対してinetcpl.cplというファイルを渡すと、インターネットオプションのダイアログが表示されます。

else if (nId == ID_HELP_VERSION) {
	TASKDIALOGCONFIG config;
	int              nResult;
	HWND             hwndParent = g_pWebBrowserContainer->GetWindow();

	ZeroMemory(&config, sizeof(TASKDIALOGCONFIG));
	config.cbSize             = sizeof(TASKDIALOGCONFIG);
	config.hwndParent         = hwndParent;
	config.hInstance          = NULL;
	config.dwFlags            = TDF_ENABLE_HYPERLINKS;
	config.pszWindowTitle     = L"バージョン情報";
	config.pszMainIcon        = TD_INFORMATION_ICON;
	config.pszMainInstruction = L"Sample WebBrowser 1.0";
	config.pszContent         = L"配布元 <A HREF=\"http://eternalwindows.jp/\">http://eternalwindows.jp/</A>";
	config.pfCallback         = TaskDialogCallbackProc;

	TaskDialogIndirect(&config, &nResult, NULL, NULL);
}

ID_HELP_VERSIONは、「バージョン情報」という項目を識別します。 単純にMessageBoxを使用してもよいのですが、それではダイアログに開発元のURLを表示したりできません。 独自のダイアログを作成して表示することもできますが、ここではWindows Vistaから登場したTaskDialogIndirectを使用しています。 これならば、MessageBoxよりも少し高度なダイアログを表示できます。 dwFlagsにTDF_ENABLE_HYPERLINKSを指定すればA要素を文字列として指定でき、実際にハイパーリンクとして表示されます。 これが選択された場合はpfCallbackに指定したコールバック関数が呼ばれるため、そこでURLへアクセスする処理を行います。

メニュー項目は常に有効であるとは限りません。 アクティブなタブがない場合は、実行するわけにはいない項目も存在しますし、 チェックを付けるべき項目も存在します。 メニューが表示される前にこうした処理が行われるために、 親ウインドウのWM_INITMENUPOPUPはCMenuMgr::SetMenuStateを呼び出します。

if (nId == ID_FILE_SAVEAS ||
	nId == ID_FILE_PROPERTY ||
	nId == ID_EDIT_CUT ||
	nId == ID_EDIT_COPY ||
	nId == ID_EDIT_PASTE ||
	nId == ID_EDIT_SELECTALL ||
	nId == ID_EDIT_FIND ||
	nId == ID_VIEW_SOURCE ||
	nId == ID_VIEW_UPDATE ||
	nId == ID_VIEW_PRIVACY
	) {
	SetMenuItem(hmenu, nId, bEnable, FALSE);
}

これらの項目は、アクティブなタブが存在するかによって有効/無効が決定されます。 bEnableはアクティブなタブが存在する場合にはTRUEになるため、 この場合は項目が有効になります。 第4引数は項目にチェックを付けるかどうかですが、これらの項目にチェックが付くことはありません。

else if (nId == ID_FILE_OFFLINE) {
	DWORD dwState, dwLength;

	dwLength = sizeof(DWORD);
	InternetQueryOption(NULL, INTERNET_OPTION_CONNECTED_STATE, &dwState, &dwLength);

	SetMenuItem(hmenu, nId, TRUE, dwState & INTERNET_STATE_DISCONNECTED_BY_USER);
}

この項目は常に有効にはなりますが、オンライン状態の場合は項目にチェックが付くことになります。 オンライン状態であるかどうかは、dwStateにINTERNET_STATE_DISCONNECTED_BY_USERが含まれるかで分かります。

else if (nId == ID_VIEW_SIDEBAR)
	SetMenuItem(hmenu, nId, TRUE, g_pWebBrowserContainer->IsShowSidebar());

この項目は、サイドバーが現在表示されている場合にチェックが付きます。

else if (
	nId == ID_FONT_LARGEST ||
	nId == ID_FONT_LEARGE ||
	nId == ID_FONT_MIDDLE ||
	nId == ID_FONT_SMALL ||
	nId == ID_FONT_SMALLEST
	) {
	int nIndex = -1;

	if (pWebBrowserHost != NULL) {
		VARIANT var;
		VariantInit(&var);
		pWebBrowserHost->Exec(OLECMDID_ZOOM, OLECMDEXECOPT_DODEFAULT, NULL, &var);
		nIndex = 4 - var.lVal;
	}
	
	SetMenuItem(hmenu, nId, bEnable, i == nIndex);
}

これらの項目は、アクティブなタブが存在しない場合は無効になります。 また、現在の文字のサイズと一致する項目についてはチェックが付きます。 文字のサイズの最大値から現在の文字のサイズを引けば、 そのサイズに関連する項目のIDが分かります。

else if (
	nId == ID_AMBIENT_PICDOWNLOAD ||
	nId == ID_AMBIENT_PLAYSOUND ||
	nId == ID_AMBIENT_PLAYVIDEO ||
	nId == ID_AMBIENT_NOSCRIPT ||
	nId == ID_AMBIENT_NOJAVA ||
	nId == ID_AMBIENT_NOACTIVEX ||
	nId == ID_AMBIENT_NOACTIVEXDOWNLOAD
	) {
		BOOL bCheck = FALSE;
		
		if (pWebBrowserHost != NULL)
			bCheck = pWebBrowserHost->IsContainAmbientProperty(nId - ID_AMBIENT_PICDOWNLOAD);

		SetMenuItem(hmenu, nId, bEnable, bCheck);
}

これらの項目は、アクティブなタブが存在しない場合は無効になります。 また、指定されたインデックスに関連するアンビエントプロパティがホストに含まれている場合は、項目にチェックが付きます。

else if (
	nId == ID_FEATURE_POPUPMANAGEMENT ||
	nId == ID_FEATURE_FILEDOWNLOAD ||
	nId == ID_FEATURE_SECURITYBAND
	) {
	HRESULT             hr;
	INTERNETFEATURELIST featueEntry;
	BOOL                bCheck;

	if (nId == ID_FEATURE_POPUPMANAGEMENT)
		featueEntry = FEATURE_WEBOC_POPUPMANAGEMENT;
	else if (nId == ID_FEATURE_FILEDOWNLOAD)
		featueEntry = FEATURE_RESTRICT_FILEDOWNLOAD;
	else
		featueEntry = FEATURE_SECURITYBAND;

	hr = CoInternetIsFeatureEnabled(featueEntry, GET_FEATURE_FROM_PROCESS);
	bCheck = hr == S_OK ? TRUE : FALSE;
	SetMenuItem(hmenu, nId, TRUE, bCheck);
}

これらの項目は常に有効ですが、指定されたフィーチャが有効になっている場合は項目にチェックが付きます。

else if (
	nId == ID_COOKIE_NO ||
	nId == ID_COOKIE_HIGH ||
	nId == ID_COOKIE_MEDIUMHIGH ||
	nId == ID_COOKIE_MEDIUM ||
	nId == ID_COOKIE_MEDIUMLOW ||
	nId == ID_COOKIE_LOW
	) {
		int   nMapId;
		BOOL  bCheck;
		DWORD dwTemplate;
		
		PrivacyGetZonePreferenceW(URLZONE_INTERNET, PRIVACY_TYPE_THIRD_PARTY, &dwTemplate, NULL, NULL);
		
		if (dwTemplate == PRIVACY_TEMPLATE_NO_COOKIES)
			nMapId = ID_COOKIE_NO;
		else if (dwTemplate == PRIVACY_TEMPLATE_HIGH)
			nMapId = ID_COOKIE_HIGH;
		else if (dwTemplate == PRIVACY_TEMPLATE_MEDIUM_HIGH)
			nMapId = ID_COOKIE_MEDIUMHIGH;
		else if (dwTemplate == PRIVACY_TEMPLATE_MEDIUM)
			nMapId = ID_COOKIE_MEDIUM;
		else if (dwTemplate == PRIVACY_TEMPLATE_MEDIUM_LOW)
			nMapId = ID_COOKIE_MEDIUMLOW;
		else if (dwTemplate == PRIVACY_TEMPLATE_LOW)
			nMapId = ID_COOKIE_LOW;
		else
			nMapId = -1;
		
		bCheck = nId == nMapId;
		SetMenuItem(hmenu, nId, TRUE, bCheck);
}

これらの項目は常に有効ですが、現在のCookieのレベルと一致する項目はチェックが付きます。

ここからは、お気に入りメニューの実装について説明します。 このメニューを実装するためには、メニュー項目の隣にアイコンを表示することや、 項目をD&Dするなど考えるべきことが多くありますが、 Windows Vistaから登場したITrackShellMenuを使用すれば容易に実現できます。 まずは、「お気に入り」の項目が選択された際の処理を確認します。

else if (nId == ID_FAVORITE) {
	HMENU hmenuPopupFavorite;

	hmenuPopupFavorite = CreatePopupMenu();
	InitializeMenuItem(hmenuPopupFavorite, TEXT("お気に入りの追加(&A)"), ID_FAVORITE_ADD, NULL);
	InitializeMenuItem(hmenuPopupFavorite, TEXT("お気に入りの整理(&O)"), ID_FAVORITE_ORGANIZE, NULL);
	InitializeMenuItem(hmenuPopupFavorite, NULL, 0, NULL);
	SetMenuItem(hmenuPopupFavorite, ID_FAVORITE_ADD, pWebBrowserHost != NULL, FALSE);

	ShowFavoriteMenu(hmenuPopupFavorite);
}

ITrackShellMenuでお気に入りのメニューが表示されるのはよいのですが、 そのメニューに対していくつか独自の項目を追加したいものです。 よって、そうしたメニューを予め作成しておき、 これをShowFavoriteMenuに渡すようにしています。 「お気に入りの追加」については、アクティブなタブがない場合は無効になることに注意してください。 ShowFavoriteMenuの実装は次のようになっています。

void CMenuMgr::ShowFavoriteMenu(HMENU hmenuAppend)
{
	ITrackShellMenu *pTrackShellMenu;
	LPITEMIDLIST    pidl;
	HKEY            hKey;
	HRESULT         hr;
	POINT           pt;

	hr = CoCreateInstance(CLSID_TrackShellMenu, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pTrackShellMenu));
	if (FAILED(hr))
		return;

	pTrackShellMenu->Initialize(static_cast<IShellMenuCallback *>(this), (UINT)-1, ANCESTORDEFAULT, SMINIT_TOPLEVEL | SMINIT_RESTRICT_DRAGDROP);
	pTrackShellMenu->SetMenu(hmenuAppend, m_hwndParent, SMSET_TOP);

	SHGetKnownFolderIDList(FOLDERID_Favorites, 0, NULL, &pidl);
	RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MenuOrder\\Favorites"), 0, KEY_QUERY_VALUE, &hKey);
	pTrackShellMenu->SetShellFolder(NULL, pidl, hKey, SMSET_BOTTOM);

	GetCursorPos(&pt);	
	pTrackShellMenu->Popup(m_hwndParent, (PPOINTL)&pt, NULL, MPPF_BOTTOM);

	ILFree(pidl);
	pTrackShellMenu->Release();
	RegCloseKey(hKey);
}

ITrackShellMenuを取得するには、CoCreateInstanceにCLSID_TrackShellMenuを指定します。 これが成功したらInitializeを呼び出して、メニューを初期化します。 第1引数はIShellMenuCallbackを実装したオブジェクトであり、これは今回の場合はCMenuMgr自身になります。 第2引数は-1を指定し、第3引数はよく分かりませんがANCESTORDEFAULTで問題ないようです。 第4引数はSMINIT_TOPLEVELを指定するようにします。 この他、SMINIT_RESTRICT_DRAGDROPを指定すると、D&Dを禁止できます。 SetMenuを呼び出しているのは、第1引数のメニューの中身をお気に入りのメニューに追加するためです。 SetShellFolderを呼び出すと、メニューの中身を第2引数のフォルダで初期化できます。 pidlはFOLDERID_Favoritesで取得されたものですから、お気に入りのフォルダを指定したことになります。 第3引数にレジストリキーを指定した場合は、項目の順番がそのキーの内容で並びかえられます。 Favoritesキーを指定した場合は、IEと同じ並び方を実現できます。 Popupを呼び出せば実際にメニューを表示でき、 項目が選択された場合や違う場所をクリックされた場合は制御が返ります。 hmenuAppendに対してIsMenuを呼び出せば分かりますが、 この時点でhmenuAppendは破棄されています。

お気に入りのメニュー項目に関する通知は、IShellMenuCallback::CallbackSMを通じて通知されます。

STDMETHODIMP CMenuMgr::CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (uMsg == SMC_SFEXEC) {
		IShellItem *psi;

		SHCreateItemWithParent(psmd->pidlFolder, psmd->psf, psmd->pidlItem, IID_PPV_ARGS(&psi));
		NavigateFromShortcut(psi, FALSE);
		psi->Release();
		return S_OK;
	}

	return S_FALSE;
}

CallbackSMでS_FALSEを返した場合は既定の処理が行われますが、 S_OKを返した場合は既定の処理が行われません。 uMsgがSMC_SFEXECである場合は項目が選択されたことを意味し、 この場合はその項目に関連するページにアクセスしたいため、独自の処理を行います。 まず、SHCreateItemWithParentを呼び出して、選択された項目を識別するIShellItemを取得します。 お気に入りフォルダ内のファイルはインターネットショートカットですから、 IShellItemはインターネットショートカットを識別しています。 NavigateFromShortcutという自作関数は、ショートカットに関連するURLにアクセスします。

void NavigateFromShortcut(IShellItem *psi, BOOL bNewTab)
{
	LPWSTR                   lpszDisplayName, lpszUrl;
	IUniformResourceLocatorW *pUniformResourceLocator;
	IPersistFile             *pPersistFile;
	HRESULT                  hr;
	
	hr = CoCreateInstance(CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pUniformResourceLocator));
	if (FAILED(hr))
		return;
	
	pUniformResourceLocator->QueryInterface(IID_PPV_ARGS(&pPersistFile));

	psi->GetDisplayName(SIGDN_FILESYSPATH, &lpszDisplayName);
	pPersistFile->Load(lpszDisplayName, STGM_READ);
	
	pUniformResourceLocator->GetURL(&lpszUrl);
	g_pWebBrowserContainer->Navigate(lpszUrl, bNewTab);
	
	CoTaskMemFree(lpszUrl);
	CoTaskMemFree(lpszDisplayName);
	pPersistFile->Release();
	pUniformResourceLocator->Release();
}

インターネットショートカットを扱う場合は、IUniformResourceLocatorを使用します。 このインターフェースからはIPersistFileを照会でき、 Loadを通じて第1引数のファイルをロードできます。 このファイルのパスは、IShellItem::GetDisplayNameで取得できます。 ロードが完了したらGetURLで関連するURLを取得できるため、 後はNavigateでそのURLにアクセスすればよいことになります。

お気に入りのメニューには独自の項目も追加しましたから、それらの押下も確認しなければなりません。 これは、CMenuMgr::OnMenuCommandで行われています。

else if (nId == ID_FAVORITE_ADD)
	pWebBrowserHost->ExecDocument(&CGID_MSHTML, IDM_ADDFAVORITES, OLECMDEXECOPT_PROMPTUSER, 0, 0);

お気に入りの追加は、上記のようにするのが最も簡単です。 DoAddToFavDlgやIShellUIHelper::AddFavoriteでも同じことはできますが、 呼び出すための処理が多くなります。

else if (nId == ID_FAVORITE_ORGANIZE) {
	IShellUIHelper *pShellUIHelper;
	BSTR           bstrName;
	HRESULT        hr;
	
	hr = CoCreateInstance(CLSID_ShellUIHelper, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellUIHelper));
	if (SUCCEEDED(hr)) {
		bstrName = SysAllocString(L"OrganizeFavorites");
		pShellUIHelper->ShowBrowserUI(bstrName, NULL, NULL);
		pShellUIHelper->Release();
		SysFreeString(bstrName);
	}
}

お気に入りの整理は、IShellUIHelper::ShowBrowserUIで行います。 同じことはDoOrganizeFavDlgでも可能ですが、 この関数はヘッダーファイルに定義されていないため、 明示的リンクの手間が生じます。


戻る