EternalWindows
インプレースアクティベーション / アクティブオブジェクト

現在UIアクティベーションされているオブジェクトは、コンテナにとって是非とも把握しておきたい存在です。 理由は、コンテナの状態が変更した場合にそのオブジェクトへ通知を送ることができるからです。 インプレースアクティベーションされたオブジェクトが、 必ずUIアクティベーションされたオブジェクトと一致するとは限らないことに注意してください。 インプレースアクティベーションは複数のオブジェクトに対して行うことができますが、 メニューやボーダースペースを占有するUIアクティベーションは、1つのオブジェクトのみ可能です。 UIアクティベーションされたオブジェクトは、SetActiveObjectによって通知されます。

STDMETHODIMP CContainer::SetActiveObject(IOleInPlaceActiveObject *pActiveObject, LPCOLESTR pszObjName)
{
	if (m_pActiveObject != NULL)
		m_pActiveObject->Release();

	m_pActiveObject = pActiveObject;

	if (m_pActiveObject != NULL)
		m_pActiveObject->AddRef();

	return S_OK;
}

第1引数のpActiveObjectがUIアクティベーションされたオブジェクトであり、 これはIOleInPlaceActiveObjectで識別することができます。 このオブジェクトは後で必要になるため、メンバ変数に保存して参照カウントを上げるようにします。 ただし、既にメンバ変数が初期化されている場合は、オブジェクトを開放するようにします。 SetActiveObjectはUIアクティベーションを解除した場合にも呼ばれるようになっており、 そのような場合はpActiveObjectにNULLが格納されています。

アクティブなオブジェクトに必ず通知しなければならないのは、コンテナのサイズ変更です。 コンテナのサイズが変更された場合は、それに併せてオブジェクトのボーダースペースも変更しなければならないため、 コンテナのWM_SIZEを検出することになります。

case WM_SIZE: {
	RECT  rc;
	CSite *p = m_pSite;

	SetRect(&rc, 0, 0, LOWORD(lParam), HIWORD(lParam));
	if (m_pActiveObject != NULL)
		m_pActiveObject->ResizeBorder(&rc, static_cast<IOleInPlaceUIWindow *>(this), TRUE);

	while (p != NULL) {
		p->SetClipRect(&rc);
		p = p->m_pNext;
	}

	return 0;
}

新しいサイズのクライアント領域をRECT構造体に格納し、これをResizeBorderに指定します。 これにより、オブジェクトのボーダースペースはクライアント領域の端まで表示されるはずです。 SetClipRectという自作メソッドはアクティブなオブジェクトとは関係のない関数ですが、 WM_SIZEで行うべき1つの処理を担っています。 コンテナのサイズが変更されたということは、インプレース済みのオブジェクトのクリップ領域もそれに変更されなければならないため、 SetClipRectでそれを行っています。

コンテナのウインドウのアクティブ状態が変更した場合は、その旨をアクティブなオブジェクトに通知すべきとされています。 ウインドウのアクティブ状態が変更した場合はWM_ACTIVATEAPPが送られるため、これを検出します。

case WM_ACTIVATEAPP:
	if (m_pActiveObject != NULL)
		m_pActiveObject->OnFrameWindowActivate((BOOL)wParam);
	return 0;

OnFrameWindowActivateを呼び出すことで、アクティブ状態の変更を通知することができます。 ウインドウがアクティブになった場合は第1引数にTRUEを指定し、 非アクティブになった場合はFALSEを指定する必要があります。 WM_ACTIVATEAPPのwParamはそうしたBOOL値を格納しているため、これを指定すればよいことになります。

オブジェクトをUIアクティベーションした場合、オブジェクトのウインドウはキーボードフォーカスを取得することになり、 直ちにキー入力ができる状態になります。 ただし、コンテナのウインドウが最小化などで非アクティブになってしまうと、 オブジェクトのウインドウはフォーカスを失うことになるため、 コンテナを再アクティブしてもオブジェクトは自動でフォーカスを取得できないことになります。 このような問題を防ぎ、常にオブジェクトのウインドウにフォーカスを与えるようにしたい場合は、 コンテナのWM_SETFOCUSを検出するようにします。

case WM_SETFOCUS:
	if (m_pActiveObject != NULL) {
		HWND hwndActive;
		m_pActiveObject->GetWindow(&hwndActive);
		SetFocus(hwndActive);
	}
	return 0;

WM_SETFOCUSが送られたということは、コンテナにフォーカスが割り当てられたことを意味しますが、 このタイミングでオブジェクトのウインドウに対してSetFocusを呼び出します。 このようにすれば、コンテナがアクティブになると同時にオブジェクトがフォーカスを取得できるようになります。

アクセラレータについて

コンテナがインスプレースアクティベーションをサポートしながらも、 オブジェクトをアウトプレースとして起動したい場合があります。 このような場合は、OLE標準のCtrl + Enterキーという組み合わせを、アウトプレースによる起動と見立てればよいでしょう。 これとは別に、オブジェクトのインスプレースアクティベーション中にアクセラレータキーを受け取りたいことがあるかもしれませんが、 これは既定で上手く動作しないことに注意してください。 理由は、インプレースアクティベーションによってオブジェクトのウインドウがフォーカスを持つからであり、 それによってキー入力がオブジェクトのウインドウに送信されてしまうからです。 次のようなコードでアクセラレータテーブルを作成したとして、話を進めていきます。

ACCEL accel[2];

accel[0].key   = 0x4f; 
accel[0].cmd   = ID_OPEN;
accel[0].fVirt = FCONTROL | FVIRTKEY;

accel[1].key   = 0x53; 
accel[1].cmd   = ID_SAVE;
accel[1].fVirt = FCONTROL | FVIRTKEY;

m_haccel = CreateAcceleratorTable(accel, 2);

このような処理をWM_CREATEなどで行い、アクセラレータテーブルをm_haccelで識別できるようにします。 そして、このハンドルをIOleInPlaceSite::GetWindowContextで返すようにします。

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;
	
	if (lpFrameInfo != NULL) {
		lpFrameInfo->fMDIApp       = FALSE;
		lpFrameInfo->hwndFrame     = hwnd;
		lpFrameInfo->haccel        = m_haccel;
		lpFrameInfo->cAccelEntries = 2;
	}

	return S_OK;
}

lpFrameInfoには、コンテナのアクセラレータテーブルの情報を格納することができます。 ただし、この引数にはNULLが渡される可能性があるため、まずはNULLでないかを確認します。 fMDIAppにはコンテナがMDIウインドウを持つかどうかを指定します。 hwndFrameは、コンテナのウインドウハンドルを指定します。 haccelは、アクセラレータテーブルのハンドルを指定します。 cAccelEntriesは、アクセラレータテーブルのエントリ数を指定します。

GetWindowContextでアクセラレータターブルのハンドルを取得したオブジェクトは、 キー入力を受け取った際にOleTranslateAcceleratorを呼び出すようになります。 そして、そのキー入力がコンテナのアクセラレータテーブルと一致する場合は、 IOleInPlaceFrame::TranslateAcceleratorを呼び出します。 つまり、アクセラレータキーを処理する機会を与えてくれます。

STDMETHODIMP CContainer::TranslateAccelerator(LPMSG lpmsg, WORD wID)
{
	SendMessage(m_hwnd, WM_COMMAND, wID, 0);

	return S_OK;
}

wIDは、入力されたアクセラレータキーに関連するコマンドIDが格納されます。 これをWM_COMMANDに送れば、コマンドの内容を実際に処理することができます。 戻り値でS_FALSEを返すと、入力されたキーがオブジェクトによって処理されるため、 S_OKを返すようにします。



戻る