定制编辑框的上下文菜单
文/赵湘宁
上下文菜单的应用在基于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消息时对你有帮助。如有任何问题和建议,请使用这个电子邮件地址:zxn@hq.cninfo.net。
最后祝大家编程愉快!