オーナードローメニューの作製(おまけ)

 今回は「オーナードローメニュー」の作製に関する色々なことを見ていきます。アクセラレータ機能、色やフォントの更新、ツリー形式メニューの作製、そして拡張メニュー作成のヒントを紹介します。

アクセラレータ
 普通のメニューなら、文字列に&を付けると、その次の文字が自動的にアクセラレータ、つまり「キーを押すとそのメニューが選択される」機能が付けられます。コンピューターに染まりきったユーザーは何もかもキーボードで操作したがるので、この機能はできるだけインプリメントすべきでしょう(もちろん、対象とするユーザーやメインのインターフェイスに寄ります)。

 メニュー上でキーが押されたときにはWM_MENUCHARが送られます。というわけで、インプリメントはCWnd::OnMenuChar()をオーバーライドして行います。第1引数に押されたキーコードが入っています。このコードはよく判らないのですが、どうやら次のようになっているようです。


	UINT	nID;
	if( nChar >=0x01 && nChar <= 0x1a )	//Shift a - Shift z
		nID = nChar - 0x01;
	else if( nChar == 0x20 )	//^0
		nID = 10;
	else if( nChar >= 0x21 && nChar <= 0x29 )	//^1 - ^9
		nID = nChar - 0x21;
	else if( nChar == 0x30 )	//0
		nID = 10;
	else if( nChar >= 0x31 && nChar <= 0x39 )	//1 - 9
		nID = nChar - 0x31;
	else if( nChar >= 0x41 && nChar <= 0x5a )	//^a - ^z
		nID = nChar - 0x41;
	else if( nChar >= 0x61 && nChar <= 0x7a )	//a - z
		nID = nChar - 0x61;
	

 オーナードローメニューを追加するときに、文字コードとメニューハンドルをキーとしたマップを作製するといいかもしれません。ただし「今どのメニューが開いているのか」ということをチェックしておかなければならないでしょう。後述するWM_MENUSELECTで得た情報をメンバに格納すればいいでしょう。
 あと、なぜかOnMenuChar()のリファレンスには関数の戻り値に0〜2までしかありませんが、実際には3もあって、これを使うと、メニューアイテムを選択状態にすることができます。詳しくはWM_MENUCHARのリファレンスを参照してください。

色やフォントの変更
 テキストや背景の色、フォント等を描画するたびに取得すると、いくらかのオーバーヘッドになります。この問題を避けるためにアプリケーションの初期化時に各情報を取得するといいでしょう。
 ですが、この場合には「ディスプレイ」のプロパティでデザインを変更したときに、メニューの各情報が書き換えられず、かなり恥ずかしいことになります。

 その部分を解決するのが、WM_SYSCOLORCHANGEメッセージです。このメッセージは、「ディスプレイ」のプロパティが変更された時にすべてのアプリケーションに送られます。このメッセージが来たときに再びメニューの初期化を行うようにすれば、オーナードローメニューは常に新しい形で表示されるでしょう。実際には、CWnd::OnSysColorChange()の中でインプリメントします。

ツリー形式メニューの作製
 ツリー形式メニューの作製は、ちょっとしたコツをつかめば簡単に実装できます。それはWM_MENUSELECTを使用することです。このメッセージはメニューが選択状態になる度に送られてきます。なぜこのメッセージを利用するといいのでしょうか。
 ツリー形式メニューは、基本的に膨大な量のメニューが存在することになります。例えば、もしあなたがディレクトリシステムそのものをメニューにしようなどと考えたとします。メニュー全体を一度にすべて作製するとしたらどれほどの時間が掛かるでしょう。メニューというのはユーザーインターフェイスの中でも特に使いやすくあるべきです。そのようなオーバーヘッドはユーザーに嫌われること請け合いです。
 それをクリアするために、WM_MENUSELECTを使用します。MFCではCWnd::OnMenuSelect()を使用します。これを使用して、「ポップアップメニューアイテムを選択したら、その時に作ってしまう」というふうにしてしまいましょう。

 この関数の第2引数はnFlagsにはメニューアイテムのフラグが入っています。このフラグにMF_POPUPMF_MOUSESELECTが入っているときにのみ以降の処理をするようにします。
 次に第1引数のnItemIDと第3引数のhSysMenuを使用します。ポップアップメニューの場合、nItemIDにはメニューアイテムのインデックス(上から何番目か)が、hSysMenuには今選択されているメニューハンドルが入っています。

 これらの情報から次のメニューを作製するわけですから、そのための情報を前もって用意する必要があります。動的にポップアップメニューを作製したときに、そのメニューアイテムの情報を何らかの形で保存しておきましょう。例えば、メニューハンドルはWORDしかない(っぽい)ので、下位WORDにはメニューハンドル、上位WORDにはアイテムのインデックスを使ったDWORDキーを用いてマップを作製すればいいでしょう。具体的には次のように。


#include <afxtempl.h>		// テンプレートを使用するため。

// テンプレートの定義。
typedef CMap<DWORD,DWORD&,String,String&>	CMapDWToString;

class CMainFrame : public CFrameWnd
{
private:
	CMapDWToString	m_cMenuStrMap;
// 以下略
	

 このようなマップを作製し、メニューを追加するときに


	DWORD	dwItemKey;
	dwItemKey = (DWORD)MAKELONG( hSysMenu, ::GetMenuItemCount( hSysMenu ) );
	
	m_cMenuStrMap.SetAt( dwItemKey, cPathStr );	//マップにパスを追加します。
	

 と、メニューのハンドルとアイテムのインデックスをキーにしてマップに情報(ここではパスとします)を追加します。
 さて、この追加したメニューアイテムを選択したら、この情報を引き出せなければいけません。そこで、キーを使ってマップから情報を取得します。


	DWORD	dwItemKey;

	dwItemKey = MAKELONG( hSysMenu, nItemID );	//メニューハンドルとインデックスをくっつけます。
	m_cMenuStrMap.Lookup( dwItemKey, cPathStr );	//パスを取得します。
	

 こうして取得した情報、つまりここではパスを元にして、フォルダのリストを作製し、それを元にまたメニューを追加し、その時、マップに各フォルダのパスを格納し、それをまた取り出して……と続けばよいでしょう。

 ただ、問題なのはメニューアイテムの管理と同時にマップの管理もしなければならないということです。
  例えば、あるポップアップメニューに5つのメニューアイテムがあったとして、その3番目のアイテムを::DeleteMenu()で削除したとします。ウィンドウズは自動的にメニューを変更してくれますが、マップはアプリケーションが自分で変更しなければなりません。となると、マップのキーはメニューハンドルとインデックスで作られたオリジナルな値ですから、問題が生じます。
 実際には、マップ中の4番目と5番目のアイテムの情報をそのまま上にずらすようにして上書きし、残った5番目を削除しなければなりません。こういった部分が色々と面倒になりますし、この部分のインプリメントをべたべたな方法で行うとオーバーヘッドが生じて重くなってしまいます。もしどうしても「実現できなーい!!」という場合には、メニューでの実装をあきらめた方がいいかもしれません。実際、メニューが最適なインターフェイスとは限らないのですから……。

拡張メニュー作製のヒント
 最後のおまけ。オーナードローメニューを使うと、かなりミョーなメニューを作製できます。その理由は「デバイスコンテキストを取得できる」という点にあります。
 WM_DRAWITEMから、メニューのデバイスコンテキストが取得できます。ということは、::GetWindow()からウィンドウハンドルを取得できるということです。ウィンドウハンドルが取得できるということは……

・丸や三角のメニュー
・選択すると逃げていくメニュー
・普通のアプリケーションのようなメニュー

 なんてものが作れるかもしれません。その他、サブクラス化などしてマウスのメッセージを奪ってしまえば、IE4.0で実装されているアイテムをドラッグできるメニューなんていうのも実現可能かもしれません。
 ですが……やはりメニューである以上、ユーザーインターフェイスはある程度重視すべきだと思います。それに、やっぱりフツーにポップアップウィンドウを作製してしまった方が楽なんじゃないかと……。

(C)KAB-studio 1997 ALL RIGHTS RESERVED.