如何触发右键菜单的行为

类别:VC语言 点击:0 评论:0 推荐:

虽然这是一个很简单的操作,但却耗费了我不少的时间,主要是我对消息机制没有充分的理解吧,最后终于在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)
注意到了么,上面的宏和我用第一种方法添加的宏有所不同,这种宏可以处理一定范围内的消息,其前两个参数就是要处理的消息的范围,这种机制也很容易理解。

本文地址:http://com.8s8s.com/it/it336.htm