前編では、動的にメニューを追加する簡単な方法を見ていきました。まだ見てない方はまずそちらを見て欲しいです。
さて、その「前編」の方法では、いくつか問題点が存在します。ネットスケープのツリー表示と比べてみれば歴然ですね。
ということで、「後編」では1万個近く自由にメニューを作成できるようなアプリケーションの作成方法を見ていきます。
まず、メニューの元になる配列を作っておきましょう。単純なメッセージの羅列を用意します。今回はCStringArrayクラスを使用します。 |
void CKABView::OnInitialUpdate()
{
CView::OnInitialUpdate();
////////////
// メニューを動的に作成します。
// 面倒くさいので、最初からデータを入れておきます。
CKABDoc* pDoc = GetDocument(); // ドキュメントを取得しておきます。
pDoc->m_cMsgStrAry.Add( _T( "いちばんめ" ) );
pDoc->m_cMsgStrAry.Add( _T( "にばんめ" ) );
pDoc->m_cMsgStrAry.Add( _T( "さんばんめ" ) );
// つづく。
}
ビュークラスに割り当てられているドキュメントへのポインタを取得してから、メンバ変数にアクセスして最初にメニューの項目を作ってしまいます。
(注:ドキュメントクラスにこのような設定をしたのは、いちばん一般的な方法かなと思ったからです。ネットスケープのブックマークはこれに近いと思います。
さて、次に、前回の「簡単にメニューを作る方法」を付け足します。 |
// つづき。
///////////////
// メニューを加えます。
// トップメニューを取得します。
CMenu* pcTopMenu = AfxGetMainWnd()->GetMenu();
// サブメニューを取得します。
CMenu* pcThisMenu = pcTopMenu->GetSubMenu( 2 );
// メニューを追加します。
pcThisMenu->AppendMenu( MF_STRING, ID_NEWMENU, _T( "追加" ) );
これは前回のものそのままなので、変更しなければいけません。もともとあるメニューは同じなので、サブメニューを取得するところまでは同じです。
もし、最初のひとつだけメニューを加えるとしたら、「メニューを追加します」の次の行を以下のように変えるでしょう。 |
pcThisMenu->AppendMenu( MF_STRING, ID_NEWMENU_0001, pDoc->m_cMsgStrAry[0] );
こうすれば「いちばんめ」というメニューが作成されます。が、問題はメッセージID「ID_NEWMENU_0001」です。
前回のように新しくメニューを作成して、そのメニューアイテムにID_NEWMENU_0001を割り当ててしまったらなんの意味もありません。 が、そのままではID_NEWMENU_0001はなんの定義もされていないので、メッセージだけ作成しておきます。VC++のメニュー「表示」−「シンボルブラウザ」を選択してください。シンボル一覧のダイアログが現れます。 そうしたら、ID_NEWMENU_0001というメッセージをシンボルを作成して、整数値は36001くらいにでもしておいてください。
さて、ここでメッセージがなんなのか、シンボルがなんなのか考えてみると……ただの整数値です。その証拠にresource.hファイルを開いてみましょう。中に |
#define ID_NEWMENU_0001 36001
というふうに書かれているはずです。つまり、メッセージはただの整数値だということです。
そこで、先ほどのメニュー追加の部分を次のように書いてしまいます。 |
// メニューを追加します。
for( int i = 0; i <= 2; i++ )
pcThisMenu->AppendMenu( MF_STRING, ID_NEWMENU_0001 + i, pDoc->m_cMsgStrAry[i] );
これで、「にばんめ」のメニューアイテムに ID_NEWMENU_0001 + 1、つまり36002が、「さんばんめ」には36003が割り当てられました。これで、無数にメッセージを割り当てることができるようになります。
(注:もちろん「無数に」は比喩ですが、それより前に、シンボルが重ならないようにする必要があります。
さて、ここまで作成してからビルドをしてみてください。成功すればメニューが自動的に作成されているはずです……淡色表示で。
(ここから下を省略できます。詳しくは最後の
実際にしてみましょう。ビュークラスを開いて、オブジェクトIDはそのまま、メッセージにOnCmdMsgを選んで新しくハンドラを作成します。そうすれば、OnCmdMsgメンバ関数が新たに作成されるはずです。 |
BOOL CKABView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
if( pHandlerInfo == NULL ) // すでに割り当てられているメッセージがない場合……
{
// この部分にあとで追加します。
}
// コマンドを処理しなかった場合には基本クラスの同名の関数を呼び出します。
return CView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
pHandlerInfoという引数は、そのメッセージがすでに関連づけられているか、つまり、同ファイルの上の方のメッセージマップ上に
|
BEGIN_MESSAGE_MAP(CT_Menu1View, CView)
//{{AFX_MSG_MAP(CT_Menu1View)
ON_COMMAND(ID_ADDMENU, OnAddmenu)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
てなふうに書かれているか(ヘッダーファイルにも書き込む必要がある)という情報が入っています。
これがNULLということは、関連づけられていないということです。逆に関連づけられている場合にはこのifに引っかからないので、最後の通常のメッセージループへと入ってきます(これが「注」で書いた、あらかじめ関連づけられているメッセージの方が優先される理由です)。 そこで、関連づけられていないメッセージに対する処理を「この部分にあとで追加します。」の部分に追加します。 |
CKABDoc* pDoc = GetDocument(); // ドキュメントを取得しておきます。
// 各メッセージをチェックします。
for( int i = 0; i <= pDoc->m_cMsgStrAry.GetSize() - 1; i++ )
{
if( nID == (UINT)( ID_NEWMENU_0001 + i ) )// もしそのメッセージなら……
{
// この部分にさらに追加します。
}
}
まずドキュメントへのポインタを取得して、メニューの項目を持っているm_cMsgStrAryメンバ変数へとアクセスできるようにします。次に、CStringArrayクラスのGetSizeメンバ関数で、配列の中の項目数を取得します。項目数は当然3つですが、それだと0から数えたとき1つ多いので、−1しておきます。
そして、さらにIDをチェックします。nID引数には、送られてきたメッセージのIDが入っています。それが、先ほど新しく追加したメニューに割り当てたメッセージかどうかチェックします。 そして、そのメッセージなら「この部分にさらに追加します。」と書かれた部分が実行されるわけです。その部分は…… |
if( nCode == CN_COMMAND )
{
// WM_COMMAND メッセージを処理します。
MessageBox( pDoc->m_cMsgStrAry[i], pDoc->m_cMsgStrAry[i] );
}
else if( nCode == CN_UPDATE_COMMAND_UI )
{
// シンボルをオンにします(これをしないとメニュー不可のままです)。
CCmdUI* pCmdUI = (CCmdUI*)pExtra;
pCmdUI->Enable( TRUE );
}
return TRUE;
nCode引数には、そのメッセージのコマンドが入っています。試しにオブジェクトIDを「ID_ADDMENU」にして、メッセージを見てみてください。COMMANDとUPDATE_COMMAND_UIがあるでしょう。この2つに対処しておきます。
COMMANDは、そのメッセージが実行されたことを意味します。そこで、そのメッセージに対する反応をこのifの中に書き込みます。今回は単に選択されたメニューアイテムの名前が書かれたダイアログを表示するだけですが、実際にアプリケーションを作成するときにはこの中にそれぞれのメッセージに対する反応を書き込みます。 UPDATE_COMMAND_UIはシンボルの状態が確認されるときに送られます。一番最初に表示されるときにも呼び出されます。ここで、pExtra引数をCCmdUIクラスにキャストして、このメニューハンドラへのポインタを得ます。そして、同クラスのEnableメンバ関数を使ってメニューを使用可能にします。これでやっと、淡色表示ともおさらばです。 最後に、普通のメッセージループで処理されないようにTRUEを返してOnCmdMsgメンバ関数を終了しておきます。
ここからの拡張は、実際には難しくなります。アイコン付きのメニューは「オーナードロー」というものを使います。また、ツリー形式のメニューを作製する場合には、CMenuの配列を作るよりはHMENUの配列を作って、Win32APIベースで作る方が楽でしょう。これらについてはこちらをご覧ください。 |
|
(C)KAB-studio 1997 ALL RIGHTS RESERVED. |