虽然这是一个很简单的操作,但却耗费了我不少的时间,主要是我对消息机制没有充分的理解吧,最后终于在codeproject中找到了类似的例子,自己也顿开毛塞,写这篇文章的目的是对前面做的工作的一个总结,也想将自己的一些经验和大家分享,避免重走弯路。 要实现标题中所说的功能,首先要了解Windows的消息机制,看了一篇专门讲述消息机制的文章,觉得很好,就将其中重要部分摘出来作为本文的铺垫(这篇文章来自雷神的《跟我学MFC教程》,写的确实很不错,本文大部分是引用自这个教程中对消息的描述,当然也加入了一些自己的理解),本文共分三个部分,第一部分讲述了消息的基本概念,第二部分讲述了如何添加自定义消息,第三部分讲述了如何实现触发右键菜单动作,如果您对本文的实现部分不太了解,可以好好看看本文前面的部分,一定会有收获。 一、消息的概念 消息简单的说就是指通过输入设备向程序发出指令要执行某个操作(说的通俗点就是类似某个人要干某项工作,他先给负责人打个招呼,告诉他要做相应的工作了,那么这个招呼就是消息,而相应的工作就是消息处理函数)。在SDK中消息其实非常容易理解,当窗口建立后便会有一个函数(窗口处理函数)开始执行一个消息循环,我们还可以清楚的看到消息处理的脉络。一个switch case语句就可以搞定,消息循环直到遇到WM_QUIT消息才会结束,其余的消息均被拦截后调用相应的处理函数。但在封装了API的MFC中,消息似乎变的有些复杂了,我们看不到熟悉的switch case语句了,取而代之的是一个叫消息映射的东西。为什么MFC要引入消息映射机制,你可以想象一下,在现在的程序开发活动中,你的一个程序是否拥有多个窗体,主窗口就算只有一个,那菜单、工具条、控件这些都是子窗口,那我们需要写多少个switch case,并且还要为每个消息分配一个消息处理函数,这样做是多么的复杂呀。因此MFC采用了一种新的机制。利用一个数组,将窗口消息和相对应的消息处理函数进行映射,你可以理解成这是一个表。这种机制就是消息映射。这张表在窗口基类CWnd定义,派生类的消息映射表如果你没有动作它是空的,也就是说如果你不手工的增加消息处理函数,则当派生窗口接受一个消息时会执行父类的消息处理函数。这样做显然是高效的。 二、如何添加自己的消息 一个标准的消息处理程序是这个样子的 在 CWnd 类中预定义了标准 Windows 消息 (WM_XXXX WM是WINDOW MESSAGE的缩写) 的默认处理程序。类库基于消息名命名这些处理程序。例如,WM_PAINT 消息的处理程序在 CWnd 中被声明为: afx_msg void OnPaint(); afx_msg 关键字通过使这些处理程序区别于其他 CWnd 成员函数来表明 C++ virtual 关键字的作用。但是请注意,这些函数实际上并不是虚拟的,而是通过消息映射实现的。我们在本文的一开始便说明了为什么要这样做。 所有能够进行消息处理的类都是基于CCmdTarget类的,也就是说CCmdTarget类是所有可以进行消息处理类的父类。CCmdTarget类是MFC处理命令消息的基础和核心。 若要重写基类中定义的处理程序,只需在派生类中定义一个具有相同原型的函数,并创建此处理程序的消息映射项。我们通过ClassWizard可以建立大多数窗口消息或自定义的消息,通过ClassWizard可以自动建立消息映射,和消息处理函数的框架,我们只需要把我们要做的事情填空,添加你要做的事情到处理函数。这个非常简单,就不细说了。但是也许我们需要添加一些ClassWizard不支持的窗口消息或自定义消息,那么就需要我们亲自动手建立消息映射和消息处理的框架,通常步骤如下: 第一步:定义消息。Microsoft推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息。 #define WM_MYMESSAGE (WM_USER + 100) 第二步:实现消息处理函数。该函数使用WPRAM和LPARAM参数并返回LPESULT。 LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam) { // TODO: 处理用户自定义消息,填空就是要填到这里。 return 0; } 第三步:在类头文件的AFX_MSG块中说明消息处理函数: // {{AFX_MSG(CMainFrame) afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam); //}}AFX_MSG DECLARE_MESSAGE_MAP() 第四步:在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中。 ON_MESSAGE( WM_MYMESSAGE, OnMyMessage ) 其实消息类别可以分成多种,上面说的只是其中之一。有三种主要的消息类别:(以下部分摘自MSDN) 1、Windows 消息 此类消息主要包括以前缀 WM_ 开头的消息,WM_COMMAND 除外。Windows 消息由窗口和视图处理。此类消息往往带有用于确定如何处理消息的参数。 2、控件通知 此类消息包括从控件和其他子窗口发送到其父窗口的 WM_COMMAND 通知消息。例如,当用户在编辑控件 (Edit Control) 中执行可能更改文本的操作后,该编辑控件 (Edit Control) 将向其父级发送包含 EN_CHANGE 控件通知代码的 WM_COMMAND 消息。该消息的窗口处理程序以某种适当的方式响应此通知消息,例如在控件中检索该文本。 框架像传送其他 WM_ 消息一样传送控件通知消息。但是有一个例外的情况,即当用户单击按钮时由按钮发送的 BN_CLICKED 控件通知消息。该消息被作为命令消息特别处理,并像其他命令一样传送。 3、命令消息 此类消息包括用户界面对象(菜单、工具栏按钮和快捷键)发出的 WM_COMMAND 通知消息。框架处理命令的方式与处理其他消息不同,可以使用更多种类的对象处理命令。 Windows 消息和控件通知消息由窗口来处理(窗口是从 CWnd 类派生的类的对象)。包括 CFrameWnd、CMDIFrameWnd、CMDIChildWnd、CView、CDialog 以及从这些基类派生的您自己的类。这些对象封装了 HWND--Windows 窗口的句柄。 命令消息可以由范围更广的对象(文档、文档模板以及应用程序对象本身)处理,而不仅仅由窗口和视图处理。当某一命令直接影响到某个特定对象时,应当让该对象处理此命令。例如,“文件”菜单中的“打开”命令在逻辑上与应用程序相关联:该应用程序接收到此命令时会打开指定的文档。因此“打开”命令的处理程序是应用程序类的成员函数。 命令消息我们比较常见的便是菜单项和工具条了,大家可以看到他的消息映射宏和窗口消息不太一样,一般的形式是这样的 ON_COMMAND(id,memberFxn) 第一个参数是命令ID,一个ID号对应一个消息处理,当然你可以让多个ID共用一个处理函数。常见的应用例如:菜单项打开文档的ID和工具条按钮打开文档的ID同时使用一个处理函数,或者直接将它们的ID设成相同的。 还有一种消息叫通知消息。例如树型控件的等一些复杂的控件在单击后需要传递更多的信息,例如光标的位置和当前项的一个结构,所以MFC为控件的每个通知消息也定义了一个宏,它长成了这个样子: ON_CONTROL(EN_CHANGE,id,memberFxn) 还有很多种消息存在于MFC,宏定义有区别,大家可以触类旁通。 三、实现触发右键菜单的方法 上面都是引述,下面开始自己的工作了,要触发右键菜单的动作,只要定义一个相应自定义菜单的函数,并在消息处理循环中添加相应的消息处理函数就可以了,以下方法在vc6.0+windows2000环境下调试通过。 事实上有两种实现方式,第一种,是我自己的: 首先在资源编辑器中定义一个自定义菜单,将右键弹出菜单功能加入vc工程,具体的实现方法可以参见本人blog的上一篇文章《在列表框中添加右键菜单的方法》; 在要触发菜单的类(如mycontrol)中添加函数如下 void mycontrol::OnMyMessage() { MessageBox("test");//这是测试语句 } 最后就是在类的消息处理循环中添加如下宏: ON_COMMAND(ID_ MyMessage, OnMyMessage)// ID_ MyMessage是要处理的菜单项名称 一切ok了。 现在说说第二种方式,是在codeproject中看到的,具体的链接在(http://www.codeproject.com/useritems/SidebarMenu.asp): 他创建菜单的方式与我不同,是在程序中动态创建的,可以作为动态创建菜单的参考。 首先在类的初始化函数中加入如下语句(注意在类的头文件中定义了CsideBarMenu mnuSideBar,CsideBarMenu是作者自定义的一个类) mnuSideBar.CreatePopupMenu();//创建弹出菜单 mnuSideBar.AppendMenu(MF_STRING|MF_OWNERDRAW,10,"Item 1");//创建第一个菜单项 mnuSideBar.AppendMenu(MF_SEPARATOR|MF_OWNERDRAW,0,"");//创建分割条 mnuSideBar.AppendMenu(MF_STRING|MF_OWNERDRAW,11,"Item 2");//创建第二个菜单项 其中的参数10,0,11指的是菜单项对应的标志,也就是当用户选择了相应标志时就 触发相应的处理函数。 在设置右键弹出功能时,在mycontrol::OnRButtonDown(UINT nFlags, CPoint point)中写入如下语句: ClientToScreen(&point); mnuSideBar.TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,this,NULL); 然后添加一个自定义的消息处理函数MenuHandler如下,看到这里可以理解这是一种sdk程序,采用了比较原始的消息处理循环。 void mycontrol::MenuHandler(UINT id) { switch(id)//判断一下当前的选择项 {
case 10://根据用户的选择进行相应的处理 MessageBox("Item 1","SideBarMenu Demo"); break; case 11: MessageBox("Item 2","SideBarMenu Demo"); break; } } 最后,在消息处理循环中加入下面的宏就搞定了: ON_COMMAND_RANGE(10,11,MenuHandler) 注意到了么,上面的宏和我用第一种方法添加的宏有所不同,这种宏可以处理一定范围内的消息,其前两个参数就是要处理的消息的范围,这种机制也很容易理解。 
|