EternalWindows
WebBrowser コントロール / バンドサイトの実装

今回は、バンドサイトの実装について説明します。 バンドサイトとは、バンドオブジェクト(IEツールバーなど)を管理するオブジェクトのことで、 次のようなポップアップメニューを提供しています。

レバーコントロールにはタブを表示するためのツールバーが存在しますが、 そこで右クリックを押した場合は、バンドオブジェクトの一覧が表示されます。 現在、表示されているバンドオブジェクトにはチェックが付くようになっており、 上図ではgoogleツールバーが表示されていることが分かります。 1つ注意しなければならないこととして、バンドサイトがタブ毎に存在するという点が挙げられます。 たとえば、上図のタブではgoogleツールバーが表示されていますが、 タブを切り替えた場合も無条件にgoogleツールバーが表示されることはありません。

バンドサイトがバンドオブジェクトを管理するとは、具体的にどういうことなのでしょうか。 まず、バンドオブジェクトの正体はCOMのインプロセスサーバーであり、 CLSIDがあればCoCreateInstanceで作成できます。 バンドオブジェクトを作成したら、次はそれを初期化しなければなりませんが、 この際にはホスト側(バンドオブジェクトを使用する側)の情報を渡されなければなりません。 理由は、そうした情報がなければバンドオブジェクトを初期化できないからです。 たとえば、バンドオブジェクトがツールバーを作成するためには親ウインドウのハンドルが必要ですから、 そのようなハンドルを取得できる手段を与えなければならないわけです。 この手段というのが、ホスト側が実装したオブジェクト(バンドサイト)であり、 バンドオブジェクトはこれを使用して情報を取得します。 バンドサイトの役割を果たすオブジェクトはCBandSiteとして実装され、 次のようなメソッドを持っています。

STDMETHODIMP CBandSite::GetWindow(HWND *phwnd)
{
	*phwnd = m_hwndRebar;

	return S_OK;
}

STDMETHODIMP CBandSite::QueryService(REFGUID guidService, REFIID riid, void **ppv)
{
	*ppv = NULL;

	if (IsEqualGUID(guidService, SID_SWebBrowserApp))
		return m_pWebBrowser2->QueryInterface(riid, ppv);
	else
		return E_NOINTERFACE;
}

バンドサイトは、バンドオブジェクトからの要求を満たす実装になっていなければなりません。 たとえば、バンドオブジェクトは親ウインドウのハンドルを必要としているわけですから、 そのようなハンドルを返すインターフェースを実装している必要があります。 この役割として使用されるのはIOleWindowであり、GetWindowで親ウインドウのハンドルを返すことができます。 CBandSiteはIServiceProvider::QueryServiceも実装していますが、これはIWebBrowser2へのポインタをバンドオブジェクトに渡せるようにするためです。 バンドオブジェクトがIWebBrowser2を要求する際にはSID_SWebBrowserAppが指定されるはずなので、 この際にIWebBrowser2を返すようにします。 実際にバンドオブジェクトを実装してIEにロードされると分かることですが、 IEから渡されるオブジェクトはIBandSiteを実装するようになっています。 よって、CBandSiteもIBandSiteを実装するべきといえるかもしれませんが、 今回は実装しないようになっています。

CBandSiteは、CWebBrowserHostによって使用されます。 CWebBrowserHostはタブ毎に作成されますから、CBandSiteもタブの数だけ存在します。 次に示すCreateでは、レジストリに登録されているバンドオブジェクトの情報を取得します。

BOOL CBandSite::Create(IWebBrowser2 *pWebBrowser2, HWND hwndRebar)
{
	int        i = 0;
	int        nCount = 0;
	HKEY       hKey, hKeyClasses;
	LONG       lResult;
	WCHAR      szKey[256], szValue[256];
	TCHAR      szData[256];
	CLSID      clsid;
	DWORD      dwType, dwValue, dwSize;
	LPBANDDATA lpBandData;
	int        nBandFirstId = 100;
	
	lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Internet Explorer\\Toolbar"), 0, KEY_QUERY_VALUE, &hKey);
	if (lResult != ERROR_SUCCESS)
		return FALSE;

	m_hdpa = DPA_Create(1);

	for (;;) {
		dwValue = sizeof(szValue);
		lResult = RegEnumValueW(hKey, i, szValue, &dwValue, NULL, NULL, NULL, 0);
		if (lResult == ERROR_NO_MORE_ITEMS)
			break;
		
		CLSIDFromString(szValue, &clsid);
		wsprintfW(szKey, L"CLSID\\%s", szValue);
		lResult = RegOpenKeyExW(HKEY_CLASSES_ROOT, szKey, 0, KEY_QUERY_VALUE, &hKeyClasses);
		if (lResult != ERROR_SUCCESS) {
			i++;
			continue;
		}

		dwSize = sizeof(szData);
		szData[0] = '\0';
		RegQueryValueEx(hKeyClasses, NULL, NULL, &dwType, (LPBYTE)szData, &dwSize);
		
		RegCloseKey(hKeyClasses);

		lpBandData = (LPBANDDATA)HeapAlloc(GetProcessHeap(), 0, sizeof(BANDDATA));

		lpBandData->clsid     = clsid;
		lpBandData->pDeskBand = NULL;
		lpBandData->bShow     = FALSE;
		lpBandData->nBandId   = nBandFirstId + nCount;
		lstrcpy(lpBandData->szName, szData);
		
		DPA_InsertPtr(m_hdpa, nCount++, (void *)lpBandData);

		i++;
	}
	
	m_hwndRebar = hwndRebar;
	m_pWebBrowser2 = pWebBrowser2;

	return TRUE;
}

ツールバーとして使用されるバンドオブジェクトは、 RegOpenKeyExの第2引数に指定しているレジストリキーに登録されています。 DPAを作成しているのは、インデックスベースでバンドオブジェクトの情報にアクセスするためです。 ループ内ではRegEnumValueでバンドオブジェクトのCLSIDを取得し、 実際にHKEY_CLASSES_ROOT\CLSID以下にそのオブジェクトが存在するかをRegOpenKeyExで調べます。 これが成功した場合は、オブジェクトが実際に存在するということなので、 オープンしたキーを使用してオブジェクトの名前を取得します。 BANDDATA構造体は、1つのバンドオブジェクトに関する情報を格納できます。 clsidはバンドオブジェクトのCLSIDを格納し、pDeskBandはバンドオブジェクトへのポインタを格納します。 バンドオブジェクトはIDeskBandを実装しているため、このインターフェースで識別できます。 bShowはバンドオブジェクトが現在表示されているかを示し、 nBandIdはバンドオブジェクトのIDを格納します。 このIDは、レバーコントロールにおけるバンドを識別するためにも使用されます。

CRebarMgrは、タブを配置するツールバー上でマウスの右ボタンが押されると、 CWebBrowserHost::ShowBandMenuを呼び出します。 そして、このメソッドはCBandSite::ShowBandMenuを呼び出します。 ここでは、ポップアップメニューから選択されたバンドオブジェクトの作成または破棄を行います。

BOOL CBandSite::ShowBandMenu()
{
	int             i, n, nId, nIndex;
	HMENU           hmenuPopup;
	MENUITEMINFO    mii;
	POINT           pt;
	HRESULT         hr;
	LPBANDDATA      lpBandData;
	IDeskBand       *pDeskBand;
	IObjectWithSite *pObjectWithSite;
	int             nBandMenuFirstId = 100;
	
	hmenuPopup = CreatePopupMenu();
	
	n = DPA_GetPtrCount(m_hdpa);
	for (i = 0; i < n; i++) {
		lpBandData = (LPBANDDATA)DPA_GetPtr(m_hdpa, i);

		mii.cbSize     = sizeof(MENUITEMINFO);
		mii.fMask      = MIIM_ID | MIIM_TYPE | MIIM_STATE;
		mii.fState     = lpBandData->bShow ? MFS_CHECKED : MFS_UNCHECKED;
		mii.wID        = nBandMenuFirstId + i;
		mii.fType      = MFT_STRING;
		mii.dwTypeData = lpBandData->szName;
		InsertMenuItem(hmenuPopup, i, FALSE, &mii);
	}

	GetCursorPos(&pt);
	nId = TrackPopupMenu(hmenuPopup, TPM_RETURNCMD, pt.x, pt.y, 0, m_hwndRebar, NULL);
	if (nId == 0) {
		DestroyMenu(hmenuPopup);
		return FALSE;
	}
	
	nIndex = nId - nBandMenuFirstId;
	
	lpBandData = (LPBANDDATA)DPA_GetPtr(m_hdpa, nIndex);
	if (lpBandData->bShow) {
		ShowRebarBand(lpBandData, FALSE);

		lpBandData->bShow = FALSE;
		lpBandData->pDeskBand->ShowDW(FALSE);
		lpBandData->pDeskBand->CloseDW(0);
		lpBandData->pDeskBand->Release();
		lpBandData->pDeskBand = NULL;
	}
	else {
		hr = CoCreateInstance(lpBandData->clsid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDeskBand));
		if (FAILED(hr))
			return FALSE;
		
		pDeskBand->QueryInterface(IID_PPV_ARGS(&pObjectWithSite));
		pObjectWithSite->SetSite(static_cast<IServiceProvider *>(this));
		pObjectWithSite->Release();

		lpBandData->pDeskBand = pDeskBand;
		lpBandData->bShow = TRUE;

		pDeskBand->ShowDW(TRUE);

		ShowRebarBand(lpBandData, TRUE);
	}

	return TRUE;
}

インデックスをベースにBANDDATA構造体を取得し、lpBandData->szNameの名前を持った項目をメニューに追加します。 既にバンドオブジェクトが表示されている場合は、項目にチェックが付くことになります。 TrackPopupMenuでポップアップメニューを表示してアイテムが選択されたら、 そのアイテムのインデックスを基にBANDDATA構造体を取得します。 バンドオブジェクトが既に作成されている場合は、そのバンドオブジェクトを破棄します。 まず、レバーコントロールのバンドをShowRebarBandで非表示にし、 lpBandData->pDeskBand->ShowDWでバンドオブジェクトのウインドウの非表示にします。 さらに、CloseDWでウインドウを破棄し、Releaseでオブジェクトを破棄します。 バンドオブジェクトが作成されていない場合は、バンドオブジェクトをCoCreateInstanceで作成します。 既に述べたように、バンドオブジェクトにはバンドサイトを渡さなければなりませんが、 これはIObjectWithSite::SetSiteを通じて行うことができます。 初期化が終わればバンドオブジェクトをShowDWで表示しますが、 これだけではバンドオブジェクトのウインドウを確認することはできません。 このウインドウの親ウインドウはレバーコントロールであるため、 レバーコントロールのバンドが表示されないことには、 バンドオブジェクトのウインドウも確認できません。 ShowRebarBandの実装は次のようになっています。

void CBandSite::ShowRebarBand(LPBANDDATA lpBandData, BOOL bShow)
{
	if (bShow) {
		HWND          hwndChild;
		DESKBANDINFO  desk;
		REBARBANDINFO bandInfo;
		
		lpBandData->pDeskBand->GetWindow(&hwndChild);

		bandInfo.cbSize    = sizeof(REBARBANDINFO);
		bandInfo.fMask     = RBBIM_STYLE | RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_ID;
		bandInfo.fStyle    = RBBS_CHILDEDGE | RBBS_BREAK;
		bandInfo.hwndChild = hwndChild;
		bandInfo.wID       = lpBandData->nBandId;

		desk.dwMask = DBIM_MINSIZE;
		lpBandData->pDeskBand->GetBandInfo(lpBandData->nBandId, 0, &desk);
		if (desk.dwMask & DBIM_MINSIZE) {
			bandInfo.cxMinChild = desk.ptMinSize.x;
			bandInfo.cyMinChild = desk.ptMinSize.y;
		}
		else {
			bandInfo.cxMinChild = 100;
			bandInfo.cyMinChild = 25;
		}

		SendMessage(m_hwndRebar, RB_INSERTBAND, (WPARAM)-1, (LPARAM)&bandInfo);
	}
	else {
		int nIndex;
		nIndex = (int)SendMessage(m_hwndRebar, RB_IDTOINDEX, lpBandData->nBandId, 0);
		SendMessage(m_hwndRebar, RB_DELETEBAND, nIndex, 0);
	}
}

bShowがTRUEである場合は、レバーコントロールのバンドを表示するためにRB_INSERTBANDでバンドを追加します。 REBARBANDINFO構造体の初期化では、hwndChildメンバにバンドオブジェクトのウインドウを指定する点が重要です。 このウインドウは、pDeskBand->GetWindowで取得できます。 wIDにバンドのIDを指定しているのは、バンドの破棄の際に送信するRB_IDTOINDEXを成功させるためです。 cx(y)MinChildはバンドの最小サイズを指定できますが、これはpDeskBand->GetBandInfoでバンドオブジェクトに問い合わせることも可能です。 dwMaskにDBIM_MINSIZEが含まれている場合は、バンドオブジェクトが返したサイズを指定するようにします。 レバーコントロールのバンドを非表示にする場合は、RB_DELETEBANDでバンドを破棄します。 このメッセージはバンドのIDではなくインデックスを要求するため、RB_IDTOINDEXで取得しています。

タブが切り替わるような場合は、現在表示されている全てのバンドオブジェクトの状態が変化すべきです。 このような場合は、ShowAllRebarBandが呼ばれます。

void CBandSite::ShowAllRebarBand(BOOL bShow)
{
	int        i, n;
	LPBANDDATA lpBandData;
	
	n = DPA_GetPtrCount(m_hdpa);
	for (i = 0; i < n; i++) {
		lpBandData = (LPBANDDATA)DPA_GetPtr(m_hdpa, i);
		if (lpBandData->pDeskBand != NULL) {
			lpBandData->pDeskBand->ShowDW(bShow);
			ShowRebarBand(lpBandData, bShow);
		}
	}
}

既にバンドオブジェクトが作成されているBANDDATA構造体を発見した場合は、 ShowDWを呼び出すことでバンドオブジェクトの表示状態をbShowのものにします。 また、ShowRebarBandを呼び出して、レバーコントロールのバンドも調整します。

アクセラレータキーが入力された場合は、現在フォーカスを持っているバンドオブジェクトにそれを通知します。

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

	if (m_pInputObject == NULL)
		return S_FALSE;

	hr = m_pInputObject->HasFocusIO();
	if (hr == S_OK)
		hr = m_pInputObject->TranslateAcceleratorIO(lpMsg);

	return hr;
}

バンドオブジェクトはIInputObjectを実装しており、HasFocusIOを呼び出すことでフォーカスを持っているかが分かります。 S_OKが返った場合はアクセラレータキーを処理させるために、TranslateAcceleratorIOを呼び出します。 IInputObjectはIInputObjectSite::OnFocusChangeISとして通知されるため、 CBandSiteはこれを実装しておくことになります。

STDMETHODIMP CBandSite::OnFocusChangeIS(IUnknown *punkObj, BOOL fSetFocus)
{
	if (m_pInputObject != NULL) {
		m_pInputObject->Release();
		m_pInputObject = NULL;
	}

	return punkObj->QueryInterface(IID_PPV_ARGS(&m_pInputObject));
}

バンドオブジェクトは、自身のフォーカスが変化した際にIInputObjectSite::OnFocusChangeISを呼び出します。 第1引数はバンドオブジェクトであり、QueryInterfaceでIInputObjectを照会できます。


戻る