EternalWindows
インプレースアクティベーション / 共有メニュー

インプレースアクティベーションされたオブジェクトは、コンテナに対してUIに関する通知を送りたいことがあります。 たとえば、インプレースアクティベーションに伴ってUIアクティベーションも行う場合は、 コンテナのウインドウ上にオブジェクトのメニューを表示しなければなりませんし、 ツールバーも表示しなければなりません。 よって、このようなUIの要求に応えるために、コンテナはIOleInPlaceFrameを実装することになっています。 今回は、IOleInPlaceFrameのメソッドの中で共有メニューに関するものを取り上げていきます。

UIアクティベーションを行った場合は、コンテナのメニューにオブジェクトのメニュー項目が追加されることになりますが、 これはコンテナのメニュー内容自体が変更されたわけではありません。 正確には、オブジェクトから空のメニューがコンテナに対して送られ、 その空のメニューに対してコンテナとオブジェクトが項目を追加しているのです。 つまり、1つのメニューをコンテナとオブジェクトが共有して使用していくのです。 空の共有メニューは、IOleInPlaceFrame::InsertMenusを通じて渡されます。

STDMETHODIMP CContainer::InsertMenus(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
{
	InitializeMenuItem(hmenuShared, TEXT("ファイル(&F)"), ID_FILE, GetSubMenu(m_hmenu, 0));
	InitializeMenuItem(hmenuShared, TEXT("オブジェクトの挿入(&I)"), ID_INSERT, NULL); 

	lpMenuWidths->width[0] = 1;
	lpMenuWidths->width[1] = 0;
	lpMenuWidths->width[2] = 1;
	lpMenuWidths->width[3] = 0;
	lpMenuWidths->width[4] = 0;
	lpMenuWidths->width[5] = 0;

	return S_OK;
}

hmenuSharedには、コンテナのトップメニューに追加されている項目と同じ項目を追加します。 コンテナのトップメニューには、「ファイル」と「オブジェクトの挿入」という項目がありましたから、 それらをhmenuSharedに追加するようにします。 「ファイル」項目はサブメニューを持っていましたが、これに関してはここで新たに作成するようなことはせず、 GetSubMenuで既存のメニューからサブメニューを取得すればよいでしょう。 InsertMenusにはlpMenuWidthsという引数がありますが、この引数の意味は少し分かりにくいと思われます。 InsertMenusが制御を返した後、オブジェクトはhmenuSharedに自身の項目を追加しなければならないわけですが、 その項目がコンテナの項目よりも前に来るのか後に来るのかを判断できなければなりません。 よって、その判断要素としてlpMenuWidthsが存在しているのですが、この初期化の仕方が複雑なのです。 少し長い話になりますが、このlpMenuWidthsについて説明していきます。

基本的に項目の並び方は、コンテナとオブジェクトで交互に行われるのが平等であるといえます。 つまり、先頭にコンテナの項目が来るならば、次の項目はオブジェクトの項目であり、 その次の項目はコンテナの項目、そして今度はオブジェクトの項目という具合です。 次に、項目の順番を示します。

コンテナ オブジェクト コンテナ オブジェクト コンテナ オブジェクト
   0          1          2           3          4          5

この順番から分かるように、コンテナの項目は0,2,4という偶数のインデックスに存在することになります。 このため、コンテナはlpMenuWidthsの0,2,4を初期化してよいことになっています。 問題はlpMenuWidths[0]やlpMenuWidths[2]、lpMenuWidths[4]に何を代入するかという点ですが、 まず1つの前提として、これらの値の合計はコンテナの項目の数を超えてはなりません。 今回のコンテナの項目は「ファイル」と「オブジェクトの挿入」という2つだけですから、 lpMenuWidths[0],lpMenuWidths[2],lpMenuWidths[4]の合計が2を超えてはなりません。 例として、lpMenuWidths[0]に1を指定し、lpMenuWidths[2]に1を指定した際の共有メニューを見てみます。

上図は、PaintbrushをUIアクティベーションしたものです。 メニュー項目の順番を見てみると、コンテナの「ファイル」、オブジェクトの「編集」、 コンテナの「オブジェクトの挿入」というように、交互に項目が追加されていることが分かります。 「オブジェクトの挿入」以降には全てオブジェクトの項目になっていますが、 これはコンテナが項目を2つしか追加しなかったことに起因します。

話はここから複雑になってきます。 次の図は、WordPadをUIアクティベーションした結果です。

上図のメニュー項目の順番を見てみると、コンテナの「ファイル」、オブジェクトの「編集」、オブジェクトの「表示」となっており、 項目の表示が交互に行われていないように思えます。 しかし、実際には上記の表示には問題ありません。 確かに項目の表示はコンテナとオブジェクトで交互になっていなければならないのですが、 自分の順番の時にどれだけ項目を表示するかは自由になっているからです。 そして、この表示する数はlpMenuWidthsの要素に指定することになっています。 まず、lpMenuWidths[0]には1を指定していたため、コンテナの項目が1つ表示されます。 次に、lpMenuWidths[1]の数だけオブジェクトの項目が表示されるわけですが、 「編集」、「表示」、「挿入」、「書式」という4つの項目が表示されているところを見ると、 lpMenuWidths[1]には4が指定されていると考えてよいでしょう。 そして、今度はlpMenuWidths[2]が考慮されるわけですが、 ここには1を指定していたためコンテナの項目が1つ表示されます。 もし、lpMenuWidths[0]に2を指定してlpMenuWidths[2]に0を指定したならば、 最初にコンテナの項目が2つ表示されることになりますから、 「オブジェクトの挿入」は「ファイル」の直後に表示されることになるでしょう。 以上の事から分かるように、コンテナとオブジェクトが交互に行っていることは正確には項目の表示ではなく、 メニューグループ(1つ以上の項目)の表示ということになります。

InsertMenusが制御を返せば、オブジェクトはlpMenuWidthsの中身を考慮しながら自身のメニュー項目を適切な位置に追加します。 そして、完成した共有メニューをコンテナに設定するためにSetMenuを呼び出します。

STDMETHODIMP CContainer::SetMenu(HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject)
{
	if (holemenu != NULL)
		::SetMenu(m_hwnd, hmenuShared);
	else
		::SetMenu(m_hwnd, m_hmenu);

	OleSetMenuDescriptor(holemenu, m_hwnd, hwndActiveObject, static_cast<IOleInPlaceFrame *>(this), m_pActiveObject);

	return S_OK;
}

holemenuがNULLでない場合は、Widnwos APIのSetMenuを呼び出すことで共有メニューを設定します。 一方、holemenuがNULLである場合は、元のコンテナのメニューに戻す目的でSetMenuを呼び出します。 共有メニューでオブジェクトの項目が選択された場合は、 オブジェクトのウインドウに対してWM_COMMANDが送られなければなりませんが、 これを可能にするのがOleSetMenuDescriptorです。 この関数を呼び出せば、WM_COMMANDやWM_MENUSELECTをメッセージフックするための仕組みが内部で作成されることになります。 WM_MENUSELECTは項目の上にカーソルを乗せた際に生成され、 主に項目の説明文を表示する際に利用します。 WM_MENUSELECTを取得したオブジェクトは項目の説明文をIOleInPlaceFrame::SetStatusTextに指定することで、 コンテナに項目の説明文を表示する機会を与えます。 コンテナはこのメソッド内で、スタータスバーに説明文を表示したりすることができます。

コンテナがUIアクティベーションの解除を要求すると、 オブジェクトはRemoveMenusを呼び出します。 コンテナはこのメソッド内で、共有メニューに追加された項目を削除します。

STDMETHODIMP CContainer::RemoveMenus(HMENU hmenuShared)
{
	int i, nCount;

	nCount = GetMenuItemCount(hmenuShared);

	for (i = 0; i < nCount; i++)
		RemoveMenu(hmenuShared, i, MF_BYPOSITION);

	return S_OK;
}

項目の数はGetMenuItemCountで取得できるため、この数だけRemoveMenuを呼び出せばよいことになります。


戻る