(孫鑫)菜單的原理及編寫

孫鑫VC視頻筆記 
新建一個工程->MFCAppWizard[exe]->工程名Menu->單文檔應用程序 

彈出菜單不能用來命令響應 

在資源裏面,增加一個菜單項,爲它改了一個新id:IDM_MYMENU,並做消息響應。 
在左邊object ids選擇IDM_MYMENU,在消息上面選COMMAND,在CMainFrame這個類增加消息響應函數。 
=== 

消息的分類 
對一個消息來說,我們將它們分成3類:標準消息,命令消息,通告消息。 
[標準消息]:除WM_COMMAND之外,所有以WM_開頭的消息。 
凡是從CWnd派生的類,纔可以接收[標準消息]。例如:WM_CHAR,WM_CREATE,WM_PAINT。 
當然也能接受[命令消息]和[通告消息]。 

[命令消息]:來自菜單、加速鍵或工具欄按鈕的消息。這類消息都以WM_COMMAND呈現。 
在MFC中,通過菜單項的標識(ID)來區分不同的命令消息; 
在SDK中,通過消息的wParam參數識別。 
凡是從CCmdTarget派生的類,都可以接收[命令消息],[通告消息]。 

[通告消息]:由控件產生的消息,例如,按鈕的單擊.這類消息也是以WM_COMMAND形式呈現. 

所以在程序中App類和Doc類,都是從CCmdTarget派生的,所以可以接受菜單的[命令消息]。 
但是這兩個類不能接受[標準消息]。 
=== 

創建標記菜單 

如果要在“文件”->“新建”的菜單項上創建標記,那麼我們可以選擇在CMainFrame::OnCreate下進行。 

像[文件],[編輯],[查看],[幫助],我們叫它們爲“子菜單”。 
如果要對菜單編程,就好像我們找一個房間一樣,我們首先找到整棟樓,然後再確定它在的樓層(它在多小 
樓),最後找到它的房間。 

如果要在“文件”->“新建”的菜單項上創建標記,首先需要找到菜單欄,然後找到它的子菜單(文件), 
最後找到它的菜單項(新建)。 
--- 
首先要獲取菜單欄,那麼要獲取一個指向菜單欄對象的指針,也就是框架窗口的,因爲菜單也是屬於框架窗 
口,可以在框架窗口的利用一個方法CMenu* GetMenu( ) const;獲取菜單欄的指針。 

利用這個函數GetMenu;就可以返回,得到一個指向CMenu對象的指針。 
在CMenu的一個函數GetSubMenu,能夠讓我們獲取子菜單。CMenu* GetSubMenu( int nPos ) const; 

當我們獲取到子菜單(找到它的樓層),之後就要對(它的房間)操作了。要做標記的菜單, 
可以用CMenu::CheckMenuItem。(放置,或移走一個標記,在菜單項上) 

UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck ); 
第一個參數取值的含義,由第二個參數決定。當第二個參數取數值爲MF_BYCOMMAND的時候,就標識第一個參 
數取菜單的ID號;取值爲MF_BYPOSITION的時候,第一個參數就要取菜單的索引號。   

MF_CHECKED(標記) or MF_UNCHECKED(不標記) with MF_BYPOSITION(索引號) or MF_BYCOMMAND(菜 
單的ID號)。可以進行不同的組合(MF_CHECKED | MF_BYPOSITION;MF_CHECKED | MF_BYCOMMAND)。 
--- 
給菜單項打上標記 
在CMainFrame::OnCreate輸入以下代碼 
GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED); 
//GetMenu是CWnd的成員函數,可以直接調用,它返回整個菜單欄的指針。 
//GetSubMenu獲取子菜單“新建”。CheckMenuItem爲菜單項“新建”打上標記。 
GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW,MF_BYCOMMAND | MF_CHECKED); 
//作用同上,只不過用“新建”菜單項的ID號訪問。 

=== 

設置缺省菜單項(字體會變粗) 
利用CMenu的成員函數SetDefaultItem。 
BOOL SetDefaultItem( UINT uItem, BOOL fByPos = FALSE ); 
UINT uItem,也是兩種訪問方式,用索引號訪問,第二個參數就設置TRUE;如果用ID號訪問,則爲FALSE。 

在CMainFrame::OnCreate輸入以下代碼 
GetMenu()->GetSubMenu(0)->SetDefaultItem(1,TRUE);//兩種訪問方式,作用一樣,這個爲索引號訪問 
GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN); 
//這個爲ID號訪問,第二個參數FALSE爲缺省值,可以不寫。 

=== 

圖形標記菜單 
利用CMenu的成員函數SetMenuItemBitmaps。 
BOOL SetMenuItemBitmaps( UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked, const 
CBitmap* pBmpChecked ); 
第二個參數選擇MF_BYCOMMAND,第一個參數要填ID號;第二個參數選擇MF_BYPOSITION,第一個參數要填索 
引號。後面兩個參數都是CBitmap的指針 

在CMainFrame::OnCreate輸入以下代碼(自己先創建好新的位圖IDB_BITMAP1,注意背景色不要是白色的) 
m_bitmap.LoadBitmap(IDB_BITMAP1);//加載位圖,m_bitmap爲CMainFrame成員函數,權限爲private。 
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0,MF_BYPOSITION,&m_bitmap,&m_bitmap); 

=== 

屏蔽菜單的功能,讓它不能用(屏蔽“打開”菜單項的功能,讓它不顯示“打開”對話框) 

可以利用一個函數EnableMenuItem。 
UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable ); 

首先需要在CMainFrame的構造函數中,設置m_bAutoMenuEnable=FALSE; 
(因爲msdn說明: NOTE: m_bAutoMenuEnable is set to FALSE in the constructor of CMainFrame) 

這句話又是什麼意思?在MFC中,一個菜單能使用和不能使用,MFC給我們提供了命令更新的機制。 
所有菜單的更新,都是由這種機制完成的。 

在CMainFrame::OnCreate輸入以下代碼: 
GetMenu()->GetSubMenu(0)->EnableMenuItem(1,MF_BYPOSITION | MF_DISABLED | MF_GRAYED); 
//MF_DISABLED屏蔽菜單項的功能,MF_GRAYED讓菜單項變灰 

=== 

取消和加載菜單(可以方便的設置,在資源裏面自己定義好的菜單,讓程序沒有菜單或設置新的菜單) 

可以利用一個函數SetMenu(CWnd的) 
BOOL SetMenu( CMenu* pMenu ); 
pMenu 
Identifies the new menu. If this parameter is NULL, the current menu is removed 
標識一個新的菜單,如果參數爲NULL,當前菜單被移走。 

在CMainFrame::OnCreate輸入以下代碼: 
SetMenu(NULL);//移走當前所有菜單 
CMenu menu; 
menu.LoadMenu(IDR_MAINFRAME);//加載自己定義的菜單,要先在資源裏面創建自定義的菜單 
SetMenu(&menu); 
menu.Detach();//將菜單句柄和c++對象斷開,這樣CMenu定義的局部變量在析構的時候,它就不會銷燬菜單 

=== 

***MFC對菜單項採用的“命令更新機制”(菜單項的ID在消息響應裏面,捕獲UPDATE_COMMAND_UI) 

這個機制,可以方便,對菜單項進行修改。(屏蔽菜單項,讓它能使用或不能使用) 

菜單項狀態的維護是依賴於UPDATE_COMMAND_UI消息,誰(菜單項的ID號)捕獲UPDATE_COMMAND_UI消息, 
MFC就在其中創建一個CCmdUI對象。 

在某個菜單項ID號捕獲的消息響應函數中(如:void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI) ) 
寫入如下代碼: 
pCmdUI->Enable();//缺省值爲TRUE,可以不填,如爲FALSE,那個菜單項就不能使用。 

=== 
增加右鍵彈出菜單 

第一種方法:讓MFC幫我們加 

選工程(Project)->增加到工程(Add to Project)->組件和控件(Components and Controls) 
->visual c++ components->添加pop-up menu->增加pop-up menu到CMenuView類中。 

第二種方法:手動自己創建(在view類) 

1 首先在資源裏編輯一個新的菜單。 
2 既然你是右鍵點擊之後彈出菜單,所以第一步就要捕獲右鍵點擊的消息WM_RBUTTONDOWN(在view類)。 
3 在右鍵點擊的消息響應函數OnRButtonDown中添加如下代碼: 
CMenu menu; 
menu.LoadMenu(IDR_MENU1);//加載你新建的菜單資源 
CMenu *pPopup=menu.GetSubMenu(0);//獲取子菜單,當然對於彈出菜單,它只有一個菜單項 
ClientToScreen(&point);//客服區座標轉換成屏幕座標 
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,this); 
//創建右鍵彈出菜單 

--- 

給彈出菜單的菜單項,做消息響應 

點擊菜單項的ID->建立類嚮導(Classwizard),之後會用對話框的一些選項,別理它,選取消。 
->在view類裏面,增加COMMAND 


=== 

***如何動態添加/刪除/插入菜單 

把工程關了。創建一個新的工程->MFCAppWizard[exe]->工程名Menu2->單文檔應用程序。 


之前我們添加菜單,是通過資源編輯器,自己手動添加的菜單。現在我們要通過代碼,動態的添加菜單。 
可以在CMainFrame::OnCreate中,動態增加菜單項。 

要動態增加菜單或菜單項,可以利用CMenu的成員函數AppendMenu。 
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ); 
第一個參數添加菜單的類型,第二個參數指示新的菜單項的命令ID,第三個參數爲菜單名稱。 
(詳細看MSDN) 

--- 

添加彈出菜單(子菜單) 

可以利用CMenu的成員函數CreatePopupMenu。 

在CMainFrame::OnCreate中添加以下代碼: 
CMenu menu; 
menu.CreatePopupMenu();//創建一個空的彈出菜單 
GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"WinSun"); 
//獲取菜單欄指針,然後將彈出菜單增加到,現有菜單的後面 
menu.Detach();//將菜單句柄和c++對象斷開,這樣CMenu定義的局部變量在析構的時候,它就不會銷燬菜單 
=== 

插入彈出菜單 
@在彈出菜單裏面,增加菜單項。(同樣可以採用AppendMenu) 

如果想在“編輯”和“查看”的兩個彈出菜單,插入新的彈出菜單。 

可以利用CMenu的成員函數InsertMenu。 
BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL 
); 
第一個參數:插入之後菜單項所在的ID號或索引號,由第二個參數決定訪問方式; 
第二個參數:菜單的訪問方式,ID號或索引號;還有要要插入的菜單類型 
第三個參數:如果nFlags參數設置爲MF_POPUP, the menu handle (HMENU) of the pop-up menu. 
第四個參數:菜單的名稱 

在CMainFrame::OnCreate中添加以下代碼: 
CMenu menu; 
menu.CreatePopupMenu();//創建一個空的彈出菜單 

GetMenu()->InsertMenu(2,MF_BYPOSITION | MF_POPUP,(UINT)menu.m_hMenu,"WinSun"); 
menu.AppendMenu(MF_STRING,111,"Hello"); 
//第一個參數指明,要增加的類型是菜單項;第二個參數爲菜單項ID,你可以隨便寫;第三個參數爲它名稱 

menu.AppendMenu(MF_STRING,112,"Weixin");//增加菜單項 
menu.AppendMenu(MF_STRING,113,"Mybole");//同上 
menu.Detach();//將菜單句柄和c++對象斷開,這樣CMenu定義的局部變量在析構的時候,它就不會銷燬菜單 

//如果要在其他的彈出菜單,怎樣增加菜單項? 
GetMenu()->GetSubMenu(你要增加菜單項的那個菜單,的索引號)->AppendMenu(MF_STRING,114,"Mybole"); 

//如果要在其他的彈出菜單,它的菜單項之間,怎樣增加菜單項? 
GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN,MF_STRING | MF_BYCOMMAND,115,"Mybole123"); 

=== 
刪除子菜單(“編輯”) 

可以利用CMenu的成員函數DeleteMenu.刪除指定菜單項,或者彈出菜單。 
就看你用什麼,調用DeleteMenu。如果用菜單欄的指針GetMenu()調用DeleteMenu,刪除的是彈出菜單,如 
果你用子菜單GetSubMenu的指針,調用DeleteMenu,刪除的是菜單項。 

BOOL DeleteMenu( UINT nPosition, UINT nFlags ); 
參數:菜單項或彈出菜單,所在的ID號或索引號,由第二個參數決定訪問方式; 

在CMainFrame::OnCreate中添加以下代碼: 
GetMenu()->DeleteMenu(1,MF_BYPOSITION);//刪除彈出菜單 
GetMenu()->GetSubMenu(1)->DeleteMenu(ID_FILE_SAVE,MF_BYCOMMAND);//刪除指定菜單項 
=== 

如何爲動態增加的菜單,或菜單項做命令響應 

我們在資源編輯器,增加的菜單可以通過類嚮導,做命令響應。那麼動態增加的菜單怎麼辦了? 
當然,我們可以通過手動添加。 

首先創建菜單的資源ID,在File View頭文件Resource.h,如下格式: 
#define   IDM_...       111 
#define   IDM_...       112 

做一個菜單命令的響應函數,它的添加和消息映射是一樣的。 
3個步驟: 

1 在類的頭文件(這裏是CMainFrm.h),做命令函數的原型。 
DECLARE_MESSAGE_MAP()之前,//{{AFX_MSG(CMainFrame)和//}}AFX_MSG之後:afx_msg void OnHello(); 
2 消息原型寫完,接下來就是消息映射。在源文件(.cpp)當中,在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP() 
之間,加入:(對於命令消息,我們通過ON_COMMAND這個宏來完成)ON_COMMAND(IDM_HELLO,OnHello) 
3 消息響應函數的實現部分:在最後面寫(#endif //_DEBUG的後面) 
void CMainFrame::OnHello 

//響應代碼...... 



=== 

編寫一個電話本程序 

我們想要完成的功能:鍵盤輸入人名,空格之後+上電話號碼,回車之後,把人名添加到菜單項。當點擊彈 
出菜單時PhoneBook,把人名和電話號碼顯示在屏幕。 

1 首先在view類捕獲WM_CHAR消息。 

2 在view類,增加4個成員變量:第一個:類型int,名稱m_nIndex,權限private; 
第二個:類型CMenu,名稱m_menu,權限private; 
第三個:類型CString,名稱m_strLine,權限private;//存儲輸入的字符串 
第四個:類型CStringArray,名稱m_strArray,權限,public。 
   
3 在view類的構造函數賦初值,m_nIndex=-1;m_strLine="";(將字符串初始化爲空) 

4 在View::OnChar中添加如下代碼。(所有的鍵盤輸入,都會通過OnChar這個消息來捕獲) 
CClientDC dc(this); 
if(0x0d==nChar)//如果回車等於nChar 
   { 
  if(0==++m_nIndex)//如果是第一次,就創建菜單 
    { 
     m_menu.CreatePopupMenu();//創建彈出菜單 
     GetParent()->GetMenu()->AppendMenu(MF_POPUP, 
                              (UINT)m_menu.m_hMenu,"PhoneBook"); 
   //將彈出菜單,添加到菜單欄上,注意要先獲取框架類的指針   
             GetParent()->DrawMenuBar();//重繪改變的菜單欄,否則新增的彈出菜單有問題 
    } 
     m_menu.AppendMenu(MF_STRING,IDM_PHONE1+m_nIndex, 
   m_strLine.Left(m_strLine.Find(' '))); 
//增加菜單項,以輸入的人名爲菜單項名字,m_strLine.Find查找空格,m_strLine.Left是截取空格左邊的 
字符串(即人名) 
    m_strArray.Add(m_strLine);//用CStrArray保存CString對象,這樣人名和號碼都保存在 
這個集合類上面 
    m_strLine.Empty();//清空上次輸入的字符串 
    Invalidate();//擦除先前的文字,防止重疊顯示。這個函數讓整個客服端區域背景無效 
   } 
      else 
   { 
    m_strLine+=nChar; 
       dc.TextOut(0,0,m_strLine);//輸出字符串 
   } 
5 你點菜單項的時候,要顯示人名和電話號碼,所以要對菜單項進行響應 

(一)先在資源編輯器上添加一個菜單,其彈出的菜單項ID號分別爲IDM_PHONE1, IDM_PHONE2, IDM_PHONE3 

(二)在Rsource.h中添加 
  #define IDM_PHONE1 201 
  #define IDM_PHONE2 202 
  #define IDM_PHONE3 203 

(三)回到菜單上面的資源編輯器,利用ClassWizard給菜單項添加響應函數,並編輯函數。 
在view類,增加的命令響應函數COMMAND。 

(四)最後回到菜單的資源編輯器,把先前增加的菜單刪除。 

(五)把剛纔加的,在view類構造函數的消息響應函數ON_COMMAND(IDM_PHONE3, OnPhone3),拿到2個註釋 
宏的外面,即END_MESSAGE_MAP()的前面,否則會有一些不必要的麻煩。 

6 在新增加的響應函數(第一種方法),寫上以下代碼: 
第一個OnPhone1: 
CClientDC dc(this); 
dc.TextOut(0,0,m_strArray.GetAt(1); 
第二個OnPhone2: 
CClientDC dc(this); 
dc.TextOut(0,0,m_strArray.GetAt(2); 
第二個OnPhone3: 
CClientDC dc(this); 
dc.TextOut(0,0,m_strArray.GetAt(3); 


由框架類捕獲菜單響應(第二種方法),而不是由View處理,可以節約代碼,而且更聰明 

首先在框架類,增加虛函數OnCommand。 

在其中添加如下代碼: 
   
  int MenuCmdId=LOWORD(wParam); 
  CMenu2View *pView=(CMenu2View*)GetActiveView();//獲取視類指針GetActiveView。 
  if(MenuCmdId>=IDM_PHONE1 && MenuCmdId<IDM_PHONE1+pView->m_strArray.GetSize()) 
  { 
  CClientDC dc(pView); 
  dc.TextOut(0,0,pView->m_strArray.GetAt(MenuCmdId-IDM_PHONE1)); 
  return TRUE; 
   
  } 
備註:記得在MainFrm.cpp添加頭文件:#include "Menu2Doc.h";#include "Menu2View"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章