S-JIS[2002-08-11]

メニューバーのメニューを追加・削除する方法

メニューバーの固定的なメニューは、リソースの「Menu」から IDR_MAINFRAMEのメニューを編集することで変えることが出来ます。
これに対し、例えば「ファイル」メニューの 最近使ったファイル名 のように 実行時にメニューを追加することも出来ます。


ここでは、以下の様な場所(表示→追加テスト→追加位置)にメニューを追加する例で説明します。


まずは、追加するメニューの最大個数を決めておきます。ここでは10個にしておきます。 「IDが決定しているメニュー」を追加するだけなら関係ありませんが。

resource.h:
#define IDM_ADDPOS                      32774
#define IDM_ADDPOS_RANGE                   10
IDM_ADDPOSの数値は、当然、ここで示している値であるとは限りません。

動的にメニューを変更するには、WM_INITMENUのハンドラのOnInitMenuを用意します。
このハンドラはメニューバーのメニューを選択するときに呼ばれます。 そして、一旦メニューを選択したか あるいはキャンセルした後、 再びメニューを選択するときにも また呼ばれます。
要するに、メニューを選択しようとする度に呼ばれます。

MainFrm.h:
class CMainFrame : public CFrameWnd
{
〜
// 生成されたメッセージ マップ関数
protected:
	//{{AFX_MSG(CMainFrame)
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	afx_msg void OnInitMenu(CMenu* pMenu);
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
〜
};
MainFrm.cpp:
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
	//{{AFX_MSG_MAP(CMainFrame)
	ON_WM_CREATE()
	ON_WM_INITMENU()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
〜
void CMainFrame::OnInitMenu(CMenu* pMenu) 
{
	CFrameWnd::OnInitMenu(pMenu);

	// TODO: この位置にメッセージ ハンドラ用のコードを追加してください
}

また、メニューが選択されたときに呼ばれるハンドラを先に準備しておきます。
「IDが決定しているメニュー」ならClassWizardからそのIDのCOMMANDハンドラを 作成できるので、ここでは省略します。
複数の場合はClassWizardは対応していないので、自分でコーディングする必要があります。 ここで先ほどのIDM_ADDPOS_RANGEを使います。

TestView.h:
class CTestView : public CView
{
〜
// 生成されたメッセージ マップ関数
protected:
	//{{AFX_MSG(CTestView)
	//}}AFX_MSG
	afx_msg void OnMenuTest(UINT uID);
	DECLARE_MESSAGE_MAP()
};
TestView.cpp:
〜
BEGIN_MESSAGE_MAP(CTestView, CView)
	//{{AFX_MSG_MAP(CTestView)
	//}}AFX_MSG_MAP
	ON_COMMAND_RANGE(IDM_ADDPOS,IDM_ADDPOS+IDM_ADDPOS_RANGE-1, OnMenuTest)
〜
END_MESSAGE_MAP()
〜
void CTestView::OnMenuTest(UINT uID) 
{
	〜
}

引数のuIDには、選択されたメニューのIDが入ってきます。 「uID-IDM_ADDPOS」という値を使うことで、IDM_ADDPOSの内の何番目のメニューが選択されたか分かります。


メニューを動的に変更するには、 OnInitMenuで渡されたCMenu*(CWnd::GetMenuで取得することも出来る)に対し GetSubMenuでポップアップメニューのCMenu*を取得します。
これを繰り返すことで目的のポップアップメニューに到達し、 そこでRemoveMenuを使って現在のメニューを削除し、 AppendMenuやInsertMenuを使ってメニューを追加します。
なぜメニューを削除しておく必要があるかというと、 前回追加したメニューが残っているからです。 削除しないで追加だけ行うと、どんどんメニューが増えていきます(苦笑)

GetSubMenuは、メニュー内の番号を引数に指定します。

0
ファイル
1
編集
2
表示
3
ヘルプ
0ツールバー
1ステータスバー
2追加テスト追加A
追加B
追加C

上図のメニュー構成の場合、以下の様になります。

MainFrm.cpp:

void CMainFrame::OnInitMenu(CMenu* pMenu) 
{
	CMenu *pSub =pMenu->GetSubMenu(2);	//「表示」メニューを取得
	CMenu *pTest=pSub ->GetSubMenu(2);	//「追加テスト」メニューを取得

	//以前のメニューを削除
	UINT mx=pTest->GetMenuItemCount();
	for(UINT i=0;i<mx;i++){
//		CString str;pTest->GetMenuString(0,str,MF_BYPOSITION);TRACE("remove[%d]%s\n",i,str);
		pTest->RemoveMenu(0,MF_BYPOSITION);
	}

	//メニューを追加
#if 1
	for(i=0;i<3;i++){	//AppendMenuを使う例
		CString str;str.Format("追加%c",'A'+i);
		pTest->AppendMenu(MF_STRING,IDM_ADDPOS+i,str);
	}
#else
	for(i=3;i>0;i--){	//InsertMenuを使う例
		CString str;str.Format("追加%c",'A'+i-1);
		pTest->InsertMenu(0,MF_BYPOSITION,IDM_ADDPOS+i-1,str);
	}
#endif
	DrawMenuBar();

	CFrameWnd::OnInitMenu(pMenu);
}

DrawMenuBarは、メニューを変更した後に呼ぶ必要があります。 これにより、メニューバーの部分が再描画されます。
最後の行のCFrameWnd::OnInitMenuは、呼んでも呼ばなくてもいいみたいです。


上記のようにGetSubMenuを使って目的の場所に到達するやり方だと、 メニューリソースを変更してメニューの位置がずれた場合に プログラム上の 番号まで変更してやる必要があり、好ましくありません。
しかし、それ以外に目的の位置を特定する方法は見つかりませんでした。 特定のIDから 文字列や属性を取得する手段はあるのですが、CMenuを返す関数が無いのです。
という訳で、IDをサブメニューに含むCMenuを探す関数を作ってみました。

MainFrm.h:
class CMainFrame : public CFrameWnd
{
〜
private:
	static CMenu* GetMenuFromID(CMenu *pMenu,UINT id);
〜
};
MainFrm.cpp:
void CMainFrame::OnInitMenu(CMenu* pMenu) 
{
//	CMenu *pSub =pMenu->GetSubMenu(2);	//「表示」メニューを取得
//	CMenu *pTest=pSub ->GetSubMenu(2);	//「追加テスト」メニューを取得
	CMenu *pTest=GetMenuFromID(pMenu,IDM_ADDPOS);
〜
}

//idをサブメニューに含むCMenu*を返す
CMenu* CMainFrame::GetMenuFromID(CMenu *pMenu,UINT id)
{
	if(pMenu==NULL) return NULL;

	for(UINT i=0;i<pMenu->GetMenuItemCount();i++){
		if(pMenu->GetMenuItemID(i)==id) return pMenu;

		CMenu *pRet=GetMenuFromID(pMenu->GetSubMenu(i),id);
		if(pRet!=NULL) return pRet;
	}
	return NULL;
}

しかし上記の関数は、動的に変更したメニューの中に 探すIDが無いとうまく使えません。
という訳で、今度は メニューの名称で検索する関数を作ってみました。

MainFrm.h:
class CMainFrame : public CFrameWnd
{
〜
private:
	static CMenu* GetMenuFromString(CMenu *pMenu,LPCTSTR menu);
〜
};
MainFrm.cpp:
void CMainFrame::OnInitMenu(CMenu* pMenu) 
{
//	CMenu *pSub =pMenu->GetSubMenu(2);	//「表示」メニューを取得
//	CMenu *pTest=pSub ->GetSubMenu(2);	//「追加テスト」メニューを取得
//	CMenu *pTest=GetMenuFromID(pMenu,IDM_ADDPOS);
	CMenu *pTest=GetMenuFromString(pMenu,"追加テスト");
〜
}

//名称が一致するメニューのサブメニューを返す
CMenu* CMainFrame::GetMenuFromString(CMenu *pMenu,LPCTSTR menu)
{
	if(pMenu==NULL) return NULL;

	for(UINT i=0;i<pMenu->GetMenuItemCount();i++){
		CString str;pMenu->GetMenuString(i,str,MF_BYPOSITION);
		if(str==menu) return pMenu->GetSubMenu(i);

		CMenu *pRet=GetMenuFromString(pMenu->GetSubMenu(i),menu);
		if(pRet!=NULL) return pRet;
	}
	return NULL;
}

あとは応用を利かせてください(笑)


VC++ページへ戻る / 技術メモへ戻る
メールの送信先:ひしだま