zoukankan      html  css  js  c++  java
  • VC++中动态生成菜单技巧

    下载源代码

    一.前言
        在实际运用中,经常需要根据操作来增减菜单和菜单项。在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_MSG
    Afx_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_MAP
    ON_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;
    	_RecordsetPtr	m_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_t	var;
    	  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 class
    	if(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小节适用于菜单项数量和菜单项显示名称不固定、经常变化的场合,只需将菜单项写入数据库,对数据库内容进行更新维护,免去了一般菜单资源更改时对应用程序代码重新编辑的繁琐。
  • 相关阅读:
    Samba 4.0 RC3 发布
    SymmetricDS 3.1.7 发布,数据同步和复制
    Express.js 3.0 发布,Node.js 的高性能封装
    GIFLIB 5.0.1 发布,C语言的GIF处理库
    jQuery UI 1.9.1 发布
    SVN Access Manager 0.5.5.14 发布 SVN 管理工具
    DynamicReports 3.0.3 发布 Java 报表工具
    HttpComponents HttpClient 4.2.2 GA 发布
    AppCan 2.0 正式发布,推移动应用云服务
    Ruby 2.0 的新功能已经冻结
  • 原文地址:https://www.cnblogs.com/lidabo/p/2800636.html
Copyright © 2011-2022 走看看