上下文菜單的應用在基於Windows的應用程序中使用得越來越廣泛。本文針對WM_INITMENUPOPUP消息的處理機制談談如何在編輯框控制的上下文菜單上添加自己的菜單項。
剛開始的時候常常碰到一個問題,就是在編輯框上單擊鼠標右鍵時,程序並不產生WM_INITMENUPOPUP消息,原因我也說不清楚,也沒有找到說明這個問題的具體文檔資料。每當我子類化編輯框控制向標准的上下文菜單添加自己的菜單項時(如圖二),
圖二
總是要碰到上面這樣的問題。那麼到底該如何使用WM_INITMENUPOPUP處理機制實現自己的上下文菜單呢?
通常的方法是為編輯框控制實現WM_INITMENUPOPUP的消息處理,但前面說過,編輯框控制不發送WM_INITMENUPOPUP。編輯控制一定是以空的HWND句柄或者TPM_NONOTIFY調用TrackPopupMenu,TPM_NONOTIFY的作用是要菜單不發送通知。也有可能——只是猜測——Windows(r)通過降低消息的通行量來改善性能。很難再回憶起當年Windows1.0和 Windows 2.0 運行在640kb/8MHZ的機器上的情形!(那時候編輯框控制有上下文菜單嗎?誰還記得?)。
不管怎麼說,如果想要添加自己的菜單項到編輯框控制的上下文菜單。如何做呢?唉,是不是除了自己發明外就別無選擇了呢?天無絕人之路,本文將為你提供一個小類:CEitMenuHandler,有了它的話,一切都搞掂。你只要使用它就行了。為了顯示這個類的用法,我用以前的一個例子程序,將其中的編譯框控制修改了一下,在它的上下文菜單中寫進了三種文件類型的菜單項,見圖三。
圖三
使用CeitMenuHandler類有三件事情要做(如果要把設計菜單本身算在內的話,那就有四件事情要做):
第一,實例化處理器。
class CMyEdit : public CEdit { protected: CEditMenuHandler m_editMenuHandler; virtual void PreSubclassWindow(); };
第二,必須安裝第一部創建的處理器。你可以在OnCreate做,但如果編輯框控制是在對話框中,則在PreSubclassWindow中安裝,因為你不是正常地創建對話框控制:而是要進行子類化工作。安裝完處理器,還得傳遞一個上下文菜單的ID:
void CMyEdit::PreSubclassWindow() { //IDR EDITMENU 是我的上下文菜單 m_editMenuHandler.Install(this, IDR_EDITMENU); }
到了這一步,處理器已經安裝並且已經准備就緒,只需要調用兩個函數。OnUpdateEditCommand 更新菜單項;OnEditCommand 處理命令。
// CMyEdit 消息映射
ON_COMMAND_RANGE(ID_EDIT_FIRST, ID_EDIT_LAST, OnEditCommand) ON_UPDATE_COMMAND_UI_RANGE(ID_EDIT_FIRST, ID_EDIT_LAST, OnUpdateEditCommand) void CMyEdit::OnUpdateEditCommand(CCmdUI* pCmdUI) { m_editMenuHandler.OnUpdateEditCommand(pCmdUI)); } void CMyEdit::OnEditCommand(UINT nID) { m_editMenuHandler.OnEditCommand(nID); }
CEitMenuHandler把什麼事情都做了,處理剪切、復制、粘貼和其它操作。根據是文檔選中還是剪切板有內容等來使能或置灰(enables/disables)相應的菜單項。如果命令被處理,處理器函數返回TRUE;否則返回FALSE,如果你願意,你可以處理其它編輯命令。例如,CMyEdit有單獨的處理器處理TXT,BMP和JPG命令。
// CMyEdit中的消息映射
ON_COMMAND(ID_FILETYPE_TXT, OnFiletypeTXT) void CMyEdit::OnFiletypeTXT() { SetWindowText(_T("txt")); SetSel(0,-1); }
CMyEdit將編輯命令傳給CEitMenuHandler並自己處理剩下的事情。更新菜單項時也一樣。一切都進行得很順利。
其實,CEitMenuHandler的實現是有相當多的事情要做的,你仔細想一想,要完成提出的功能的話需要編寫不少的代碼。所幸的是CEitMenuHandler很聰明地重用了以前的一段代碼,一個叫做CPopupMenuInitHandler的類(有關這個類的描述請參考我的另外一篇文章),它對編輯菜單什麼操作也不做;其作用是讓你借MFC的CCmdUI菜單更新機制來更新任何窗口的上下文菜單。MFC有很棒的菜單更新機制全都在CFrameWnd中實現,所以只有框架窗口能使用它。如果你用某些其它類型的窗口——如編輯框控制,MFC是不會處理WM_INITMENUPOPUP消息為此來做一些CCmdUI的事情,真是個無賴!但是CPopupMenuInitHandler可處理任何窗口對象。它還依賴另外一個類:CSubclassWnd,這個類可以子類化任何CWnd對象。
CPopupMenuInitHandler代表你的窗口截獲WM_INITMENUPOPUP消息,並且還完成MFC菜單更新的工作。你的事情是實例化CPopupMenuInitHandler並安裝實例。然後你就可以添加ON_UPDATE_COMMAND_UI處理起來更新窗口的上下文菜單——就像框架窗口所做的那樣。有關CPopupMenuInitHandler的細節請參見另外一篇文章,或者參考本文提供的源代碼。
一旦有了CPopupMenuInitHandler類,CEitMenuHandler就簡單了,參見源代碼。
你只要提供一個菜單ID,當用戶在編輯框控制上單擊鼠標右鍵時,CEitMenuHandler便會顯示這個菜單(見圖三)。
剩下的事情非常簡單,OnUpdateEditCommand更新相應的菜單項,例子如下:
// 在CEditMenuHandler::OnUpdateEditCommand中
switch (nID) { case ID_EDIT_PASTE: pCmdUI->Enable(::IsClipboardFormatAvailable(CF_TEXT));
也就是說,當有文本需要粘貼時,CEditMenuHandler使能Paste命令。注意CeditMenuHandler希望你使用標准的MFC 菜單IDs,如ID_EDIT_CUT,ID_EDIT_COPY等。為了處理各自的命令。只要發送WM_CUT,WM_COPY等消息到編輯框控制,CEditMenuHandler就調用CEdit::Cut,CEdit::Copy之類的函數。其實說起來CEdit::Cut,Copy和Paste都是CWnd中的東西,因為任何窗口都能實現它們——但在實際應用中,真正實現它們的只有編輯框控制和組合框控制(組合框裡包含編輯框控制)。
希望本文在處理編輯框控制及WM_INITMENUPOPUP消息時對你有幫助。如有任何問題和建議,請使用這個電子郵件地址:[email protected]。
本文示例代碼或素材下載