メニューバーの固定的なメニューは、リソースの「Menu」から
IDR_MAINFRAMEのメニューを編集することで変えることが出来ます。
これに対し、例えば「ファイル」メニューの 最近使ったファイル名 のように
実行時にメニューを追加することも出来ます。
ここでは、以下の様な場所(表示→追加テスト→追加位置)にメニューを追加する例で説明します。
まずは、追加するメニューの最大個数を決めておきます。ここでは10個にしておきます。 「IDが決定しているメニュー」を追加するだけなら関係ありませんが。
resource.h:#define IDM_ADDPOS 32774 #define IDM_ADDPOS_RANGE 10IDM_ADDPOSの数値は、当然、ここで示している値であるとは限りません。
動的にメニューを変更するには、WM_INITMENUのハンドラのOnInitMenuを用意します。
このハンドラはメニューバーのメニューを選択するときに呼ばれます。
そして、一旦メニューを選択したか あるいはキャンセルした後、
再びメニューを選択するときにも また呼ばれます。
要するに、メニューを選択しようとする度に呼ばれます。
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を使います。
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を探す関数を作ってみました。
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が無いとうまく使えません。
という訳で、今度は メニューの名称で検索する関数を作ってみました。
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; }
あとは応用を利かせてください(笑)