EternalWindows
インプレースアクティベーション / IOleInPlaceSiteの実装

OLE埋め込みの章では、コンテナのウインドウ上にオブジェクトのイメージを表示し、 それをダブルクリックすることでオブジェクトのウインドウを表示することに成功しました。 この動作は、コンテナのウインドウからオブジェクトのウインドウを素早く使用できる効果をもたらしますが、 より操作性を望むならば、コンテナのウインドウ上でオブジェクトを操作したいと思うはずです。 つまり、オブジェクトを独立したトップレベルウインドウとして表示するのではなく、 オブジェクトを子ウインドウとしてコンテナのウインドウに貼り付けるのです。 このような仕組みはインプレースアクティベーションと呼ばれ、 ウインドウ間の切り替えが不要になることにより、 オブジェクトの操作をシームレスに行える利点が生じます。 多くの埋め込み可能なオブジェクトはインプレースアクティベーションをサポートしており、 たとえばPaintburushのオブジェクトをインプレースアクティベーションした場合は、 次のようになります。

見て分かるように、Paintburushのウインドウがコンテナのウインドウ上に表示され、 図を描けるようになっています。 ただし、コンテナ上でオブジェクトを操作するならば、オブジェクトのウインドウだけでなく メニューなどもコンテナ上に表示されてほしいため、 多くのインプレースアクティベーションでは、 UIアクティベーションと呼ばれる作業も同時に行われます。 UIアクティベーションとは、オブジェクトのメニューをトップメニューにマージしたり、 ツールバーをコンテナ上に表示したりすることを意味します。

インプレースアクティベーションを行うためには、コンテナとサイトがそれ用のインターフェースを実装することになります。 まず、コンテナが実装すべきインターフェースはIOleInPlaceFrameです。 OLE埋め込みの章ではCContainerというクラスを定義しましたが、 このクラスがIOleInPlaceFrameを実装することになります。 また、コンテナにはオブジェクトの数だけサイトを作成することになっていますが、 このサイトはIOleInPlaceSiteを実装することになります。 これらの条件を満たして、さらにオブジェクトがインプレースアクティベーションをサポートしているのであれば、 IOleObject::DoVerbにOLEIVERB_PRIMARYを指定することで、インプレースアクティベーションが開始されるはずです。 アウトプレース(独立したウインドウ)としてオブジェクトを起動したい場合は、OLEIVERB_OPENを指定するようにします。

今回は、サイトが実装するIOleInPlaceSiteについて見ていくことにします。 まず、サイトの目的というのはオブジェクトからの通知を受けることでした。 たとえば、IOleClientSiteはオブジェクトが保存された際の通知を受け取ることができますし、 IAdviseSinkはオブジェクトが閉じられた際の通知を受け取ることができます。 それでは、IOleInPlaceSiteが受け取る通知は何かというと、 インプレースアクティベーション(及びUIアクティベーション)の発生や解除などが代表的です。 次に、インプレースアクティベーション(及びUIアクティベーション)の発生に関する流れを示します。

CanInPlaceActivate
↓
OnInPlaceActivate
↓
OnUIActivate
↓
GetWindowContext

基本的には上記の流れになりますが、IOleObject::DoVerbにOLEIVERB_INPLACEACTIVATEを指定した場合は、 OnInPlaceActivateまでしか呼ばれないことがあります。 これは、OLEIVERB_INPLACEACTIVATEがインプレースアクティベーションだけを行うことを目的にしているからです。 OLEIVERB_PRIMARYを指定した場合は、インプレースアクティベーションとUIアクティベーションが行われるため、 OnUIActivateやGetWindowContextも呼ばれるはずです。 それでは、CanInPlaceActivateから処理を順に確認します。

STDMETHODIMP CSite::CanInPlaceActivate()
{
	return S_OK;
}

CanInPlaceActivateは、インプレースアクティベーションを実行してもよいかを確認する目的で呼ばれます。 S_OKを返すことでインプレースアクティベーションの実行を許可することができます。

STDMETHODIMP CSite::OnInPlaceActivate()
{
	m_pOleObject->QueryInterface(IID_PPV_ARGS(&m_pInPlaceObject));

	return S_OK;
}

OnInPlaceActivateは、実際にインプレースアクティベーションが行われた際に呼ばれます。 つまり、この時点でオブジェクトのウインドウはコンテナのウインドウの中に表示されています。 この際には、インプレースアクティベーションされたオブジェクトをIOleInPlaceObjectで表すことができるため、 QueryInterfaceで取得しておくことになります。

STDMETHODIMP CSite::OnUIActivate()
{
	return S_OK;
}

OnUIActivateは、UIアクティベーションが行われた際に呼ばれます。 つまり、この時点でコンテナのウインドウにはオブジェクトのツールバーなどが表示されています。 今回は特に行うことがないので単純にS_OKを返しています。

STDMETHODIMP CSite::GetWindowContext(IOleInPlaceFrame **ppFrame, IOleInPlaceUIWindow **ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
	RECT rc;
	HWND hwnd;

	g_pContainer->GetWindow(&hwnd);
	GetClientRect(hwnd, &rc);

	*ppFrame = static_cast<IOleInPlaceFrame *>(g_pContainer);
	g_pContainer->AddRef();
	*ppDoc = NULL;
	*lprcPosRect = m_rc;
	*lprcClipRect = rc;

	return S_OK;
}

GetWindowContextは、コンテナの情報を取得する目的で呼ばれます。 ppFrameは、IOleInPlaceFrameを実装しているコンテナのアドレスを指定します。 これにより、オブジェクトはコンテナを参照できることになるため、 AddRefで参照カウントを増加させておきます。 ppDocは、MDIウインドウを作成しない場合はNULLでよいでしょう。 lprcPosRectはオブジェクトの位置を指定し、 lprcClipRectはオブジェクトを描画できる最大の範囲を指定します。 これは、コンテナのクライアント領域全体でよいと思われるため、GetClientRectで初期化した値を指定します。 lpFrameInfoは、コンテナのアクセラレータテーブルの情報を返すことになっていますが、 今回はこれを持っていないので初期化していません。

インプレースアクティベーションされたオブジェクトは、マウスで移動したりサイズを変更したりすることができます。 こうした操作を行った場合は、OnPosRectChangeが呼ばれます。

STDMETHODIMP CSite::OnPosRectChange(LPCRECT lprcPosRect)
{
	HWND hwnd;
	RECT rc;

	g_pContainer->GetWindow(&hwnd);
	GetClientRect(hwnd, &rc);
	
	m_pInPlaceObject->SetObjectRects(lprcPosRect, &rc);
	m_rc = *lprcPosRect;

	return S_OK;
}

lprcPosRectには、オブジェクトの新しい位置情報が格納されています。 この変更を許可する場合はIOleInPlaceObject::SetObjectRectsを呼び出し、 オブジェクトの位置を実際に第1引数の位置に変更します。 第2引数はオブジェクトを描画できる最大の範囲であり、 コンテナのクライアント領域を指定すれば問題ありません。 新しい位置情報はオブジェクトの描画の際に必要となりますから、メンバ変数に保存しておきます。

インプレースアクティベーションされたオブジェクトは、どのような状況で元に戻ることになるのでしょうか。 一般的には、ユーザーがコンテナのウインドウのどこかをクリックした場合が妥当であると思われます。 これを検出した場合は、OLE埋め込みの章で取り上げたChangeSelectが呼ばれるため、 このメソッドでインプレースアクティベーションを解除することになります。

void CSite::ChangeSelect(BOOL bSelect)
{
	DWORD misc;

	m_bSelect = bSelect;
	
	if (!m_bSelect && m_pInPlaceObject != NULL)
		m_pInPlaceObject->InPlaceDeactivate();
}

m_bSelectがFALSEである場合は、ユーザーがこのオブジェクトでない別の場所をクリックしたことを意味します。 このとき、そのオブジェクトがインプレースアクティベーションされているのであれば、 InPlaceDeactivateを呼び出すことでインプレースアクティベーションを解除します。 この解除に伴い、UIアクティベーションも解除されることになるため、 それを通知するOnUIDeactivate(正確にはこの前にSetActiveObject(NULL))が呼ばれます。

STDMETHODIMP CSite::OnUIDeactivate(BOOL fUndoable)
{
	g_pContainer->SetMenu(NULL, NULL, NULL);

	return S_OK;
}

OnUIDeactivateが制御を返した場合は、UIアクティベーションが解除されることになります。 ただし、それに伴ってコンテナのメニューが自動で元のメニューに戻るようなことはないため、 SetMenuを呼び出して明示的に元に戻すようにしています。 OnUIDeactivateの後には、インプレースアクティベーションが解除されたことを通知するOnInPlaceDeactivateが呼ばれます。

STDMETHODIMP CSite::OnInPlaceDeactivate()
{
	m_pInPlaceObject->Release();
	m_pInPlaceObject = NULL;

	return S_OK;
}

このメソッドが呼ばれている時点で、既にインプレースアクティベーションは解除されているため、 IOleInPlaceObject::Releaseを呼び出すことでオブジェクトを開放することができます。


戻る