EternalWindows
インプレースアクティベーション / メニューとツールバー

前節では、インプレースアクティベーションについて説明したため、 今回はUIアクティベーションについて説明します。 UIアクティベーションの際には、UIActivateという自作メソッドが呼ばれる。

BOOL CObject::UIActivate()
{
	int                 i;
	HRESULT             hr;
	HWND                hwndParent;
	HMENU               hmenu;
	HOLEMENU            holemenu;
	OLEMENUGROUPWIDTHS  menuWidths;
	BORDERWIDTHS        borderWidths;
	RECT                rc, rcPos, rcClip;

	m_pInPlaceSite->OnUIActivate();

	m_frameInfo.cb = sizeof(OLEINPLACEFRAMEINFO);
	hr = m_pInPlaceSite->GetWindowContext(&m_pInPlaceFrame, &m_pInPlaceUIWindow, &rcPos, &rcClip, &m_frameInfo);
	if (FAILED(hr))
		return FALSE;

	// メニューの設定(ここでは省略)
	
	// ツールバーの設定(ここでは省略)

	if (m_pInPlaceUIWindow != NULL)
		m_pInPlaceUIWindow->SetActiveObject(static_cast<IOleInPlaceActiveObject *>(this), NULL);

	m_pInPlaceFrame->SetActiveObject(static_cast<IOleInPlaceActiveObject *>(this), NULL);
	
	return TRUE;
}

UIアクティベーションを行う場合は、最初にIOleInPlaceSite::OnUIActivateを呼び出してサイトに通知します。 IOleInPlaceSite::GetWindowContextを呼び出しているのは、UIアクティベーションに必要な情報を取得するためであり、 特に第1引数のIOleInPlaceFrameは重要です。 これはコンテナのフレームを表すインターフェースであり、 コンテナにメニューやボーダースペースを表示するには、このIOleInPlaceFrameが必要になります。 第2引数のIOleInPlaceUIWindowはドキュメントを表すインターフェースであり、 たとえばExcelでいえば1つのワークブックを識別します。 ドキュメントを持たないコンテナの場合はNULLを返すことも考えられるため、 使用する場合はNULLでないかの確認が必要です。 メニューやボーダースペースの設定が終われば、 SetActiveObjectを呼び出してアクティブオブジェクトをコンテナに通知します。 アクティブオブジェクトというのは、現在UIアクティベーションされているオブジェクトのことであり、 正にこのCObjectそのものです。 アクティブオブジェクトは、IOleInPlaceActiveObjectを実装している必要があります。

メニューの設定処理は、次のようになります。

for (i = 0; i < 6; i++)
	menuWidths.width[i] = 0;
hmenu = CreateMenu();
m_pInPlaceFrame->InsertMenus(hmenu, &menuWidths);
InitializeMenuItemByPos(hmenu, TEXT("編集(&E)"), ID_EDIT, menuWidths.width[0], GetSubMenu(m_hmenu, 1));
menuWidths.width[1] = 1;
holemenu = OleCreateMenuDescriptor(hmenu, &menuWidths);
m_pInPlaceFrame->SetMenu(hmenu, holemenu, m_hwnd);
m_holemenu = holemenu;
m_hmenuShared = hmenu;

コンテナに設定されるメニューは、コンテナの項目とオブジェクトの項目が混ざった共有メニューです。 項目の追加はコンテナから行うことになっているため、コンテナのために空のメニューを作成し、 これをIOleInPlaceFrame::InsertMenusに指定します。 これによりコンテナは、引数のメニューに自身の項目を追加することになります。 InsertMenusが制御を返したら、今度はオブジェクトが項目を追加しなければなりませんが、 この項目はどの位置に追加すればよいのでしょうか。 menuWidths.width[0]を確認すれば、コンテナが追加した1つ目のメニューグループ(複数の項目)の数が分かるため、 この値を基にInitializeMenuItemByPosという自作メソッドを呼び出します。 そうすると、1つ目のメニューグループの直後に「編集」という項目が存在することになります。 menuWidths.width[1]にはオブジェクトが追加した1つ目のメニューグループの数を指定することになっており、 今回は1つの項目を追加しましたから1を指定します。 menuWidths.width[2]にはコンテナの2つ目のメニューグループの数が格納されていますが、 今回の場合はもう追加する項目が存在しないため、これを参照する必要はありません。 完成した共有メニューはIOleInPlaceFrame::SetMenuでコンテナに渡すことになりますが、 その前にOleCreateMenuDescriptorでメニュー記述子を作成するようにします。 メニュー記述子というものが存在することによって、 コンテナ上で選択された項目の通知がオブジェクトのウインドウまで送られることになります。 メニュー記述子のハンドルと共有メニューのハンドルはUIアクティベーションを解除する際に必要となるため、 メンバ変数に保存しておきます。

ツールバーの設定処理は、次のようになっています。

SetRect((LPRECT)&borderWidths, 0, m_nToolbarHeight, 0, 0);
hr = m_pInPlaceFrame->RequestBorderSpace(&borderWidths);
if (hr == S_OK) {
	m_pInPlaceFrame->SetBorderSpace(&borderWidths);
	m_pInPlaceFrame->GetBorder(&rc);
	m_pInPlaceFrame->GetWindow(&hwndParent);
	SetParent(m_hwndToolbar, hwndParent);
	MoveWindow(m_hwndToolbar, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE);
}

まず、RequestBorderSpaceを呼び出して、特定の高さを持ったツールバーを表示してもよいかを確認します。 これが成功した場合はSetBorderSpaceを呼び出すことによって、 実際にそのスペースを確保する機会をコンテナに与えます。 そしてこれが終了したらGetBorderを呼び出して、実際にツールバーを表示すべき範囲を取得します。 後は、ツールバーをフレームの子ウインドウにして先に取得したサイズを設定すれば、 ツールバーはコンテナ上に表示されるはずです。

オブジェクトのUIアクティベーションが解除される場合は、IOleInPlaceObject::UIDeactivateが呼ばれます。

STDMETHODIMP CObject::UIDeactivate()
{
	if (m_pInPlaceSite != NULL)
		m_pInPlaceSite->OnUIDeactivate(FALSE);
	
	if (m_pInPlaceUIWindow != NULL) {
		m_pInPlaceUIWindow->SetActiveObject(NULL, NULL);
		m_pInPlaceUIWindow->Release();
		m_pInPlaceUIWindow = NULL;
	}

	if (m_pInPlaceFrame != NULL) {
		m_pInPlaceFrame->SetMenu(NULL, NULL, NULL);
		OleDestroyMenuDescriptor(m_holemenu);
		m_holemenu = NULL;
		RemoveMenu(m_hmenuShared, ID_EDIT, MF_BYCOMMAND);
		m_pInPlaceFrame->RemoveMenus(m_hmenuShared);
		DestroyMenu(m_hmenuShared);
		m_hmenuShared = NULL;

		SetParent(m_hwndToolbar, m_hwnd);

		m_pInPlaceFrame->SetActiveObject(NULL, NULL);
		m_pInPlaceFrame->Release();
		m_pInPlaceFrame = NULL;
	}

	return S_OK;
}

まず、IOleInPlaceSite::OnUIDeactivateを呼び出すことで、UIアクティベーションの解除を行うことをコンテナに通知します。 m_pInPlaceSiteの開放はInPlaceDeactivateで行うことになっているため、このメソッドでは行わないようにします。 m_pInPlaceUIWindowが初期化されている場合は、 SetActiveObjectを呼び出してアクティブオブジェクトが存在しなくなることを通知します。 また、m_pInPlaceUIWindowは必要がなくなるため、ここで開放するようにします。 m_pInPlaceFrameが初期化されている場合は、 メニューの設定とツールバーの表示を解除しなければなりません。 IOleInPlaceFame::SetMenuを呼び出すことでメニューを解除することができ、 これによりm_holemenuも不要になるため、OleDestroyMenuDescriptorで破棄するようにします。 メニュー自体の破棄はDestroyMenuで可能ですが、 できれば追加していた項目は全て削除しておきたいため、 自分で追加した「編集」という項目をRemoveMenuで削除しています。 また、この後にはIOleInPlaceFame::RemoveMenusを呼び出すことで、 コンテナが追加した項目をコンテナ自身に削除させるようにしています。 ツールバーを表示しないようにするには、 ツールバーの親ウインドウを通常のウインドウに変更するだけで問題ありません。 最後にSetActiveObjectでアクティブオブジェクトの不在を通知し、 不要になったm_pInPlaceFrameを開放します。

コンテナにアクティブオブジェクトを渡したことにより、 必要に応じてIOleInPlaceActiveObjectのメソッドが呼ばれることが考えられます。 次に、各メソッドの実装を示します。

STDMETHODIMP CObject::TranslateAccelerator(LPMSG lpmsg)
{
	return E_NOTIMPL;
}

TranslateAcceleratorは、サーバーがインプロセスサーバーの場合に呼ばれます。 インプロセスサーバーの場合は、コンテナがオブジェクトよりも先にキー入力を検出するため、 そのキー入力がオブジェクトのアクセラレータキーに一致するかを調べるためにTranslateAcceleratorが呼ばれます。 しかし、今回のサーバーはローカルサーバーとして実装されているため、TranslateAcceleratorが呼ばれることはないはずです。

STDMETHODIMP CObject::OnFrameWindowActivate(BOOL fActivate)
{
	return S_OK;
}

OnFrameWindowActivateは、コンテナのフレームのアクティブ状態が変化した場合に呼ばれます。 今回は特に行うことがないため、単純にS_OKを返すだけにしています。

STDMETHODIMP CObject::OnDocWindowActivate(BOOL fActivate)
{
	return S_OK;
}

OnDocWindowActivateは、ドキュメントウインドウのアクティブ状態が変化した場合に呼ばれるとされていますが、 Excelのようなドキュメントを表示するアプリケーションで試しても、呼ばれることはありませんでした。 特に行うことがない場合はS_OKを返すだけでよいと思われます。

STDMETHODIMP CObject::ResizeBorder(LPCRECT prcBorder, IOleInPlaceUIWindow *pUIWindow, BOOL fFrameWindow)
{
	MoveWindow(m_hwndToolbar, prcBorder->left, prcBorder->top, prcBorder->right - prcBorder->top, m_nToolbarHeight, TRUE);

	return S_OK;
}

ResizeBorderは、コンテナのサイズが変更された場合に呼ばれます。 この場合は、オブジェクトのツールバーのサイズも変更するようにしたいため、 prcBorderを基にMoveWindowを呼び出しています。

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

EnableModelessは、オブジェクトが表示しているモードレスダイアログを有効または無効にする場合に呼ばれます。 通常は呼ばれることはありませんし、モードレスダイアログを表示する予定もないためE_NOTIMPLを返しています。

先のTranslateAcceleratorでも少し述べましたが、 オブジェクトがローカルサーバー上で動作している場合は、キー入力をコンテナよりも先に検出します。 通常これは望ましいことであるといえますが、コンテナからしてみればアクセラレータキーの入力ぐらいは受け取りたいと思うため、 そのようなキーの入力を検出する仕組みが必要になります。

while (GetMessage(&msg, NULL, 0, 0) > 0) {
	if (OleTranslateAccelerator(m_pInPlaceFrame, &m_frameInfo, &msg) != S_OK) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

OleTranslateAcceleratorの第2引数に指定しているのはOLEINPLACEFRAMEINFO構造体のアドレスであり、 これはIOleInPlaceSite::GetWindowContextで取得することができます。 この構造体にはコンテナのアクセラレータテーブルのハンドルが格納されており、 OleTranslateAcceleratorはこのテーブルと第3引数のメッセージを比較します。 そして、メッセージが確かにコンテナのアクセラレータキーであると分かったならば、 IOleInPlaceFrame::TranslateAcceleratorを呼び出してコンテナにアクセラレータキーの入力を通知します。 試しにExcelで今回のオブジェクトをインプレースアクティベーションし、Ctrl + Oを押してみたところ、 確かにExcelの「開く」コマンドの実行を確認できました。


戻る