VC++中动态生成菜单技巧

来源:互联网 发布:html5工资 知乎 编辑:程序博客网 时间:2024/05/02 06:12
下载源代码
一.前言
    在实际运用中,经常需要根据操作来增减菜单和菜单项。在VC++开发环境下,动态生成菜单的方法有多种。例如:可以利用资源编辑器创建菜单资源,然后在程序运行中动态加入菜单,这种动态生成菜单的方法比较常见,运用比较多。用这种方法动态增加菜单时,首先需要在Resource.h中添加菜单ID;由于是动态生成的菜单选项,所以要实现它的功能就不能在ClassWizard中映射函数了,需要在头文件中手动添加消息函数原型,在代码文件中手动添加消息映射和添加消息响应函数。动态生成菜单的另一种方法,不能事先对每个菜单ID进行定义,比如从数据库中读出的每条记录内容动态添加为菜单项,菜单项的数量不是固定的,可以在动态添加菜单项时使菜单项的ID顺序递增;对菜单项的消息响应不能事先写出响应代码,而需要根据菜单ID动态响应函数。
二.菜单相关知识
2.1 常用菜单操作函数
文中涉及到的VC++中常见的菜单只要操作如下:
GetMenu() - 获得与框架窗口相链接的菜单。
InsertMenu() – 在指定位置插入新的菜单项,其他的选项向下移。
GetSubMenu() – 获得子菜单指针。
GetMenuItemCount() – 得到菜单下的菜单项的个数。
AppendMenu() – 添加一个新菜单。
GetMenuString() – 获得指定菜单项的标记。
DeleteMenu() – 删除菜单。
2.2 菜单消息处理
    Windows消息分为3类:标准Windows消息、命令消息、控件通知消息。标准消息指除WM_COMMAND之外,所以以WM_前缀开始的消息,包括键盘消息和窗口消息等;命令消息,指来自菜单、快捷键、工具栏按钮等用户界面对象发出的WM_COMMAND消息。其中,在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。控件通知消息,是对控件操作而引起的消息,是控件和子窗口向其父窗口发出的WM_COMMAND通知消息。
菜单命令则属于命令消息,一个菜单命令可以映射到框架类、视图类或文档类的某一个成员函数,但不能同时映射到多个成员函数上。即使将一个菜单命令同时映射到多个不同的成员函数上,同时只有一个成员的映射是有效的。在MFC文档/视图结构中映射有效的优先级高低顺序为视图类、文档类、框架类。
菜单消息一旦在其中一个类中响应则不再在其他类中查找响应函数。
具体来说,菜单命令消息路由过程是这样的:当点击一个菜单项的时候,最先接受到菜单项消息的是CMainFrame框架类,CMainFrame框架类将会把菜单项消息交给它的子窗口View类,由View类首先进行处理;如果View类检测到没对该菜单项消息做响应,则View类把菜单项消息交由文档类Doc类进行处理;如果Doc类检测到Doc类中也没对该菜单项消息做响应,则Doc类又把该菜单项消息交还给View类,由View类再交还给CMainFrame类处理。如果CMainFrame类查看到CMainFrame类中也没对该消息做响应,则最终交给App类进行处理。而且一个消息一旦在某个类中被响应过,则不再接着传递。
三.编程实例
3.1 菜单实例工程
(1)新建工程
    VC++中新建工程DynamicMenu:第一步选择“单文档“,接下来几步不用修改使用默认设置,到最后一步将Base class下拉菜单选”CFormView“,即建立基于FormView的MFC运用程序。本项目中3.3节以动态创建控件为例,而视图中FormView可以放置控件,因此选FormView。
(2)创建菜单资源
    在Resource中Menu下的菜单中,添加菜单“动态菜单示例“,下设两个菜单项,分别是”添加例一菜单““添加例二菜单”如图1所示。ID分别为ID_FIRSTMENU和ID_SECMENU。点击这两个菜单项,通过两种方式动态添加两个菜单。仅添加了菜单,并没有实现菜单的功能,即没有对应的命令处理函数与菜单项对应,因此,添加的菜单项是灰色的,即为当前不可用状态。添加新菜单项后,还应该为新的菜单项指定一个消息响应函数。
                        
                                                图一     新建两个菜单项

    通过MFC ClassWizard在View下为两菜单项添加消息响应函数,ID_FIRSTMENU的响应函数为void  CDynamicMenuView::OnFirstMenu(),ID_SECMENU的响应函数为void CDynamicMenuView::OnSecmenu()。
3.2 动态添加菜单
    本例适用于菜单项名称、数量事先固定,仅在需要时将事先定义好的菜单动态添加到主菜单中,不需要时删除即可。响应函数需要通过手动添加的,更改菜单项时需要重新修改代码。
本例功能:通过点击上述DynamicMenu工程中的菜单“动态菜单示例”下的“添加例一菜单”,添加新的动态菜单“动态菜单一“,点击其下的菜单项”First1“即进行菜单响应,弹出提示框,菜单项”First2“未定义消息响应则不可,如图2所示。
                        
                                                 图2     运行界面
主要编程步骤:建立菜单资源,手动加入菜单ID、消息映射、消息响应函数。
实现步骤:
(1)在以上DynamicMenu工程中的Resource.h中添加要生成的菜单ID,如:

#define  ID_FIRST1  32733  //手动加入的菜单ID_FIRST1#define  ID_FIRST2  32734  //手动加入的菜单ID_FIRST2
(2)在MainFrm.h中手动添加命令响应函数原型。
Protected://{{AFX_MSG(CMainFrame)afx_msg  int  OnCreate(LPCREATESTRUCT lpCreateStruct);//NOTE – the ClassWizard will add and remove member functions here.//DO NOT EDIT what you see in these blocks of generated code!//}}AFX_MSGAfx_msg void OnFIRST1();//手动添加的消息响应函数宏DECLARE_MESSAGE_MAP()
(3)手动添加消息映射,在MainFrm.cpp用ON_COMMAND宏关联消息响应函数
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)//{{AFX_MSG_MAP(CMainFrame)// NOTE - the ClassWizard will add and remove mapping macros here.//DO NOT EDIT what you see in these blocks of generated code !ON_WM_CREATE()//}}AFX_MSG_MAPON_COMMAND(ID_FIRST1,OnFIRST1)//手动添加IDM_HELLO到函数OnHello的映END_MESSAGE_MAP()
(4)新增菜单,添加的菜单项往往是灰色的,不可用,只有为此菜单项定义有相信的响应函数才会可用。为ID_FIRST1菜单项定义了消息响应函数,则菜单项为可用。没有为ID_FIRST2定义消息响应函数,则定义的菜单色为灰色。在MainFrm.cpp中手动编写消息响应函数如下:
void   CMainFrame::OnFIRST1(){   MessageBox(“动态菜单示例1!”);}
(5)点击”动态菜单示例“下的”添加例一菜单“时,在菜单末尾添加”动态菜单一“。
通过MFC ClassWizard在CDynamicMenuView下为菜单项ID_FIRSTMENU添加消息响应函数如下:
void CDynamicMenuView::OnFirstmenu() {  // TODO: Add your command handler code here  CMenu menu;   menu.CreatePopupMenu();  //创建空菜单  GetParent()->GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"动态菜单一");  //把菜单添加到现有菜单末尾  menu.AppendMenu(MF_STRING|MF_ENABLED,ID_FIRST1,"First1");  menu.AppendMenu(MF_STRING|MF_ENABLED,ID_FIRST2,"First2");  menu.Detach();    GetParent()->DrawMenuBar(); //重绘菜单 }
3.3 将数据库中读出的记录动态添加为菜单项
    本列适用于菜单项数量和菜单项显示名称事先不固定的场合,这需将菜单项写入数据库,对数据库内容进行更新维护即可。在需要时读取数据库中记录,动态添加为菜单项。
本列功能:通过点击上述DynamicMenu工程中的菜单“动态菜单示例“下的”添加例二菜单“,即在菜单末尾添加新的动态菜单”动态菜单二“,其中的菜单项由数据库中读出记录内容添加而成,点击各菜单项即进行菜单响应,如图3.
                        
                                                图三    运行界面


    点击新添加的菜单项,根据菜单项的名称和当前数据库中的记录内容,动态建立控件。如菜单项名为“文本框“,数据库中当前记录中类型字段contype为”文本“,当点击该菜单项时在程序运行界面中动态建立一文本框。如为”下拉框“,数据库中当前记录中类型字段contype为”选择“,点击该菜单项时在程序运行界面中动态建立一下拉框。
编程步骤:建立数据库,其中的记录即为菜单二的菜单项;动态添加菜单项时使菜单项的ID顺序递增;菜单项数据量不固定,对每个菜单项的消息响应也不能事先写出响应代码,需要动态响应函数,因此根据菜单ID并结合数据库记录内容在菜单项响应函数中进行功能分类并响应。
实现步骤:
(1)建立Access数据库menu,表名dynamicmenu,表中设两个字段分别是myname、contype,字段类型均为“文本“。表中添加两条记录:文本框,文本;下拉框,选择,如图4所示。以下根据contype的值动态建立控件,并将myname值作为控件中显示内容。
                        
                                                图4     表结构与数据
(2) 在dynamicmenu工程中连接数据库。
在DynamicMenu.cpp文件的InitInstance()中添加如下连接代码:
   BOOL  CDynamicMenuApp::InitInstance()   {           …           m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=menu.mdb","","",adModeUnknown);           …   } 

(3)在CDynamicMenuView中设置全局变量flag作为判断是否添加了“示例菜单二“的标志,并在CDynamicMenuView的构造函数中设置初值为false,代码如下:
CDynamicMenuView::CDynamicMenuView(): CFormView(CDynamicMenuView::IDD){   //{{AFX_DATA_INIT(CDynamicMenuView)   // NOTE: the ClassWizard will add member initialization here   //}}AFX_DATA_INIT   // TODO: add construction code here    flag=false;}
(4)点击菜单项“添加例二菜单“时,从数据库中读取记录,添加为动态菜单项,菜单添加成功后将flag置为true,便于在菜单响应时进行判断。
通过MFC ClassWizard在CDynamicMenuView下为菜单ID_SECMENU添加消息响应函数如下:
void CDynamicMenuView::OnSecmenu()//读取数据库中的记录,添加动态菜单项 { CMenu menu;           menu.CreatePopupMenu();  //创建空菜单          GetParent()->GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"动态菜单二");  //把菜单添加到现有菜单末尾int j=8999;_RecordsetPtrm_pRecordset;m_pRecordset.CreateInstance(__uuidof(Recordset));try{m_pRecordset->Open("SELECT * FROM DynamicMenu",// 查询DemoTable表中所有字段m_pConnection.GetInterfacePtr(),  // 获取库接库的IDispatch指针adOpenDynamic,adLockOptimistic,adCmdText);}catch(_com_error *e){AfxMessageBox(e->ErrorMessage());}if(!m_pRecordset->BOF)m_pRecordset->MoveFirst();else{AfxMessageBox("没有记录!!");return;}         //读取记录,添加为菜单项m_pRecordset->MoveFirst();while(!m_pRecordset->adoEOF){           j=j+1;//菜单项ID递增  _variant_tvar;  var=m_pRecordset->GetCollect("myname");//读取记录值  CString  str=vtos(var);          //添加为菜单项          menu.AppendMenu(MF_STRING| MF_ENABLED | MF_CHECKED,j,str);          m_pRecordset->MoveNext();}menu.Detach();GetParent()->DrawMenuBar();  flag=true;//菜单添加完毕,设置菜单项添加标志,便于在菜单消息响应中判断}

(5)添加菜单项消息响应函数。
    MFC确定一个Command ID 是否有Handler与之对应是通过OnCmdMsg(UINT nID, int nCode, void *pExtra, AFX_CMDHANDLERINFO* pHandlerInfo),然后根据返回值来确定的。返回值为true,表示有对应的处理函数;返回值为false,表示没有。其中参数nID就是发送过来的消息ID号,对于菜单,就是菜单的ID;参数nCode为0表示是命令消息,如单击菜单项发出的消息,为-1就表示是UPDATE_COMMAND_UI消息;参数pHandleInfo不为NULL时,表示在检测;当其为NULL时表示是在路由这个命令消息,希望能得到处理。
重载了CDynamicMenuView的OnCmdMsg()函数,因为要在CDynamicMenuView中处理这些动态添加的菜单项。通过calss wizard,为CDynamicMenuView添加消息响应函数OnCmdMsg,添加代码如下:
BOOL CDynamicMenuView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) {// TODO: Add your specialized code here and/or call the base classif(flag)//判读是否添加“示例菜单二“{                  //得到“示例菜单二“的菜单项总个数int n = GetParent()->GetMenu()->GetSubMenu(6)->GetMenuItemCount() - 1;if (0 == nCode)//判断是否是命令消息{                          //判断当前单击的菜单ID是否在动态菜单项的范围之内if (GetParent()->GetMenu()->GetSubMenu(6)->GetMenuItemID(0) <= nID&& nID<=GetParent()->GetMenu()->GetSubMenu(6)->GetMenuItemID(n)){if (pHandlerInfo==NULL ){                       CString strMenuName;  //菜单项名                               GetParent()->GetMenu()->GetMenuString(nID,strMenuName,MF_STRING);//根据ID得到菜单项名              Handler(strMenuName); }return true;}}}return CFormView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);}
其中Handler(strMenuName)是用户自定义的菜单项单击的响应代码,参数为当前点击的菜单项的名称,根据菜单项动态建立控件,主要代码如下:
void CDynamicMenuView::Handler(CString strMenuName){     _RecordsetPtr m_pSet2;     m_pSet2.CreateInstance(__uuidof(Recordset));     CString strsql2="SELECT * FROM DynamicMenu WHERE myname='"; strsql2=strsql2+strMenuName+"'";     if(m_pSet2!=NULL)     {         if(m_pSet2->State)    m_pSet2->Close();         m_pSet2= NULL;      }      m_pSet2.CreateInstance(__uuidof(Recordset));      try       {             m_pSet2->Open((_bstr_t)strsql2, // 查询DemoTable表中所有字段          m_pConnection.GetInterfacePtr(), // 获取库接库的IDispatch指针  adOpenDynamic,adLockOptimistic,adCmdText);      }      catch(_com_error e)      {          AfxMessageBox("对象数据库操作失败!!");          //return NULL;      }      if(!m_pSet2->BOF)         m_pSet2->MoveFirst();      else      {            MessageBox(strMenuName);         AfxMessageBox("没有符合条件的关联对象记录!!");         //return NULL;       }         CString ctype=vtos(m_pSet2->GetCollect("contype"));        m_pSet2->Close();       if(ctype=="选择")       {            CDC *pDC=GetDC();            combox= new CComboBox;            combox->Create(WS_CHILD|WS_VISIBLE| CBS_DROPDOWN,CRect((260),(80),(330),(100)), this, 1999);            combox->SetWindowText(strMenuName);        }        else  if (ctype=="文本")        {            CDC *pDC=GetDC();            number= new CEdit;            number->Create(WS_VISIBLE| WS_CHILD | WS_BORDER , CRect((260),(40),(330),(60)), this, 2000);            //number->MoveWindow((30),(80),(70),(20));            //number->ShowWindow(SW_SHOW);            number->SetWindowText(strMenuName);         }}
四.小结
    通过两个实例在VC++6.0中实现动态生成菜单,3.2小节适用于需要添加的菜单项比较固定,需要预先建立菜单项ID,响应函数事先确定并进行手动添加,修改时涉及代码。3.3小节适用于菜单项数量和菜单项显示名称不固定、经常变化的场合,只需将菜单项写入数据库,对数据库内容进行更新维护,免去了一般菜单资源更改时对应用程序代码重新编辑的繁琐。

原创粉丝点击