DoubleLi

qq: 517712484 wx: ldbgliet

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

下载源代码

一.前言
    在实际运用中,经常需要根据操作来增减菜单和菜单项。在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小节适用于菜单项数量和菜单项显示名称不固定、经常变化的场合,只需将菜单项写入数据库,对数据库内容进行更新维护,免去了一般菜单资源更改时对应用程序代码重新编辑的繁琐。
posted on 2012-12-04 00:27  DoubleLi  阅读(926)  评论(0编辑  收藏  举报