巧用boost库实现字符串映射消息处理函数
Boost库实在是一个博大精深的库。经过实验,我发现巧用boost::bind和boost::function可以实现一些巧妙的设计。
编写过MFC程序的朋友都知道,通过资源编辑器编辑菜单项,设定菜单项ID,然后MFC内部通过该ID来映射菜单命令处理函数和菜单界面更新函数。我设想撇开MFC的资源编辑,在MFC程序中通过xml文件来进行界面配置,然后利用字符串来映射消息处理函数。借助boost库,我实现了这一设想。具体就是在xml文件指定一个字符串,然后通过这个字符串就能找到它的命令消息处理函数和界面更新命令消息处理函数。
下面具体谈谈怎么实现这一设想。我需要解决的是两大问题:一是通过xml文件来动态创建界面;二是根据xml文件里指定的菜单项字符串找到它的命令消息处理函数。
第一个问题相对简单。首先我谈谈我的系统界面配置xml文件的设计。我的系统界面配置xml文件比较简单,具体如下:
view plaincopy to clipboardprint?
<?xml version="1.0" encoding="GB2312" standalone="no" ?>
<BoostBind>
<Framework AppName="boost库测试程序 V1.0" company="BigHardware company" url="http:www.BigHardware.com" />
<UIDescription>
<AppMenuBar valid="1" caption="文件(F)">
<MenuItem valid="1" identity="file_new" caption="新建" />
<MenuItem valid="1" identity="file_open" caption="打开" />
</AppMenuBar>
<AppMenuBar valid="1" caption="编辑(E)">
<MenuItem valid="1" identity="edit_copy" caption="拷贝" />
<MenuItem valid="1" identity="edit_paste" caption="粘贴" />
</AppMenuBar>
</UIDescription>
</BoostBind>
<?xml version="1.0" encoding="GB2312" standalone="no" ?>
<BoostBind>
<Framework AppName="boost库测试程序 V1.0" company="BigHardware company" url="http:www.BigHardware.com" />
<UIDescription>
<AppMenuBar valid="1" caption="文件(F)">
<MenuItem valid="1" identity="file_new" caption="新建" />
<MenuItem valid="1" identity="file_open" caption="打开" />
</AppMenuBar>
<AppMenuBar valid="1" caption="编辑(E)">
<MenuItem valid="1" identity="edit_copy" caption="拷贝" />
<MenuItem valid="1" identity="edit_paste" caption="粘贴" />
</AppMenuBar>
</UIDescription>
</BoostBind>
简单解释就是:Framework节点保存的是程序的基本信息,包括包含了程序名、公司名和公司网址;接着就是UIDescription--界面描述,主要包括全部菜单栏信息和菜单项信息。
对应这个系统配置xml文件,我们需要在程序中定义相应的结构体,具体如下:
view plaincopy to clipboardprint?
#include <string>
using std::string;
#include <vector>
using std::vector;
/*! \struct MyAppInfo BaseStruct.h
* \brief 程序信息结构体.
*
* 包含了程序名、公司名和公司网址
*/
struct MyAppInfo
{
MyAppInfo()
{
m_strAppName = _T("");
m_strCompanyName = _T("") ;
m_strUrl = _T("") ;
}
/**
* \brief 应用程序名。
*/
string m_strAppName;
/**
* \brief 公司名。
*/
string m_strCompanyName;
/**
* \brief 公司网址。
*/
string m_strUrl;
};
/*! \struct MenuItem BaseStruct.h
* \brief 菜单项信息结构体.
*
* 包含了菜单是否有效,菜单标识符和菜单名
*/
struct MenuItem
{
MenuItem()
{
m_strvalid = _T("");
m_strID = _T("");
m_strCaption = _T("");
}
/**
* \brief 菜单项是否有效,1为有效,0为无效。
*/
string m_strvalid;
/**
* \brief 菜单标识,用于映射消息处理函数。
*/
string m_strID;
/**
* \brief 菜单项名。
*/
string m_strCaption;
};
/*! \struct AppMenuBar BaseStruct.h
* \brief 菜单栏信息结构体.
*
* 包含了菜单栏是否有效、菜单栏名和菜单项数组
*/
struct AppMenuBar
{
AppMenuBar()
{
m_Valid = _T("");
m_strCaption = _T("");
}
/**
* \brief 菜单栏是否有效,1为有效,0为无效。
*/
string m_Valid;
/**
* \brief 菜单栏名。
*/
string m_strCaption;
/**
* \brief 对应的菜单项数组。
*/
vector<MenuItem> m_MenuItemVec;
};
#include <string>
using std::string;
#include <vector>
using std::vector;
/*! \struct MyAppInfo BaseStruct.h
* \brief 程序信息结构体.
*
* 包含了程序名、公司名和公司网址
*/
struct MyAppInfo
{
MyAppInfo()
{
m_strAppName = _T("");
m_strCompanyName = _T("") ;
m_strUrl = _T("") ;
}
/**
* \brief 应用程序名。
*/
string m_strAppName;
/**
* \brief 公司名。
*/
string m_strCompanyName;
/**
* \brief 公司网址。
*/
string m_strUrl;
};
/*! \struct MenuItem BaseStruct.h
* \brief 菜单项信息结构体.
*
* 包含了菜单是否有效,菜单标识符和菜单名
*/
struct MenuItem
{
MenuItem()
{
m_strvalid = _T("");
m_strID = _T("");
m_strCaption = _T("");
}
/**
* \brief 菜单项是否有效,1为有效,0为无效。
*/
string m_strvalid;
/**
* \brief 菜单标识,用于映射消息处理函数。
*/
string m_strID;
/**
* \brief 菜单项名。
*/
string m_strCaption;
};
/*! \struct AppMenuBar BaseStruct.h
* \brief 菜单栏信息结构体.
*
* 包含了菜单栏是否有效、菜单栏名和菜单项数组
*/
struct AppMenuBar
{
AppMenuBar()
{
m_Valid = _T("");
m_strCaption = _T("");
}
/**
* \brief 菜单栏是否有效,1为有效,0为无效。
*/
string m_Valid;
/**
* \brief 菜单栏名。
*/
string m_strCaption;
/**
* \brief 对应的菜单项数组。
*/
vector<MenuItem> m_MenuItemVec;
};
程序定义了一个基于tinyxml库的xml文件解析类CXmlParse,关于如何解析xml文件本博客已经有过详细介绍,具体参考如下文章:
TinyXml快速入门(一)
TinyXml快速入门(二)
TinyXml快速入门(三)
就TinyXml使用答复一位网友
将TinyXml快速入门的接口面向对象化
具体做法是在应用程序类里定义两个变量:
view plaincopy to clipboardprint?
/**
* \brief 用于保存程序信息的变量。
*
*/
MyAppInfo m_Info;
/**
* \brief 用于保存程序信息的变量。
*
*/
CXmlParse *m_pSysSetting;
/**
* \brief 用于保存程序信息的变量。
*
*/
MyAppInfo m_Info;
/**
* \brief 用于保存程序信息的变量。
*
*/
CXmlParse *m_pSysSetting;
在程序初始化实例函数InitInstance()里调用CXmlParse类的接口来解析系统配置文件。根据解析系统xml文件来动态创建菜单栏的代码也比较简单,集中在GetAllmenubar和MainMenubarCreate两个函数,具体见上传的代码,这里不作赘述。
下面我具体谈谈如何通过xml文件中指定的菜单ID字符串来映射它的消息处理函数,概括来说是通过一个函数来实现的。
view plaincopy to clipboardprint?
/**
* \brief 菜单命令消息处理函数指针。
*
*/
typedef boost::function<void ( )> CmdFunction;
/**
* \brief 菜单界面更新命令消息处理函数指针。
*
*/
typedef boost::function<void (CCmdUI*)> UpdateCmdUIFunction;
/**
* \brief 菜单命令消息处理函数指针。
*
*/
typedef boost::function<void ( )> CmdFunction;
/**
* \brief 菜单界面更新命令消息处理函数指针。
*
*/
typedef boost::function<void (CCmdUI*)> UpdateCmdUIFunction;
在框架类里定义一个map,菜单ID作为键,消息处理函数指针作为键值:
view plaincopy to clipboardprint?
/**
* \brief 保存所有命令消息处理函数指针的map。
*/
CUICommandMap m_appCommands;
/**
* \brief 保存所有命令消息处理函数指针的map。
*/
CUICommandMap m_appCommands;
然后在创建菜单的时候将消息处理函数指针添加进这个map:
view plaincopy to clipboardprint?
void CMainFrame::AddToMessageMap(UINT uMenuID,CmdFunction cmd, UpdateCmdUIFunction UpdateUICmd)
{
if(cmd.empty() && UpdateUICmd.empty())
return;
m_appCommands[uMenuID] = std::make_pair(cmd, UpdateUICmd);
}
void CMainFrame::MainMenubarCreate()
{
// 删除所有的默认的系统菜单
CMenu *pMenu = GetMenu();
if (NULL!=pMenu)
{
DelAllMenu(pMenu->GetSafeHmenu());
}
m_menu.CreateMenu();
// 给CmdID赋值为系统起始菜单ID值
int CmdID = SYS_COMMAND_BEGIN;
int MenubarSize = m_MenubarVec.size();
for (int i=0;i<MenubarSize;i++)
{
// 创建一个弹出菜单栏
CMenu popmenu;
popmenu.CreatePopupMenu();
int ItemSize = m_MenubarVec[i].m_MenuItemVec.size();
for (int j =0;j<ItemSize;j++)
{
// 增加一个菜单项
popmenu.AppendMenu(MF_ENABLED|MF_STRING,CmdID,m_MenubarVec[i].m_MenuItemVec[j].m_strCaption.c_str());
// 根据不同的菜单添加不同的消息命令处理函数
if (string(_T("file_new"))==m_MenubarVec[i].m_MenuItemVec[j].m_strID)
{
AddToMessageMap(CmdID,boost::bind(&CMainFrame::OnFileNew,this),boost::bind(&CMainFrame::OnUpdateFileNew,this,_1));
}
if (string(_T("file_open"))==m_MenubarVec[i].m_MenuItemVec[j].m_strID)
{
AddToMessageMap(CmdID,boost::bind(&CMainFrame::OnOpenFile,this),boost::bind(&CMainFrame::OnUpdateOpenFile,this,_1));
}
if (string(_T("edit_copy"))==m_MenubarVec[i].m_MenuItemVec[j].m_strID)
{
AddToMessageMap(CmdID,boost::bind(&CMainFrame::OnEditCopy,this),boost::bind(&CMainFrame::OnUpdateEditCopy,this,_1));
}
if (string(_T("edit_paste"))==m_MenubarVec[i].m_MenuItemVec[j].m_strID)
{
AddToMessageMap(CmdID,boost::bind(&CMainFrame::OnEditPaste,this),boost::bind(&CMainFrame::OnUpdateEditPaste,this,_1));
}
CmdID++;
}
// 将弹出菜单栏插入到主菜单
m_menu.AppendMenu(MF_POPUP,(UINT)popmenu.m_hMenu,m_MenubarVec[i].m_strCaption.c_str());
popmenu.Detach();
}
// 设置为系统主菜单
SetMenu(&m_menu);
}
void CMainFrame::AddToMessageMap(UINT uMenuID,CmdFunction cmd, UpdateCmdUIFunction UpdateUICmd)
{
if(cmd.empty() && UpdateUICmd.empty())
return;
m_appCommands[uMenuID] = std::make_pair(cmd, UpdateUICmd);
}
void CMainFrame::MainMenubarCreate()
{
// 删除所有的默认的系统菜单
CMenu *pMenu = GetMenu();
if (NULL!=pMenu)
{
DelAllMenu(pMenu->GetSafeHmenu());
}
m_menu.CreateMenu();
// 给CmdID赋值为系统起始菜单ID值
int CmdID = SYS_COMMAND_BEGIN;
int MenubarSize = m_MenubarVec.size();
for (int i=0;i<MenubarSize;i++)
{
// 创建一个弹出菜单栏
CMenu popmenu;
popmenu.CreatePopupMenu();
int ItemSize = m_MenubarVec[i].m_MenuItemVec.size();
for (int j =0;j<ItemSize;j++)
{
// 增加一个菜单项
popmenu.AppendMenu(MF_ENABLED|MF_STRING,CmdID,m_MenubarVec[i].m_MenuItemVec[j].m_strCaption.c_str());
// 根据不同的菜单添加不同的消息命令处理函数
if (string(_T("file_new"))==m_MenubarVec[i].m_MenuItemVec[j].m_strID)
{
AddToMessageMap(CmdID,boost::bind(&CMainFrame::OnFileNew,this),boost::bind(&CMainFrame::OnUpdateFileNew,this,_1));
}
if (string(_T("file_open"))==m_MenubarVec[i].m_MenuItemVec[j].m_strID)
{
AddToMessageMap(CmdID,boost::bind(&CMainFrame::OnOpenFile,this),boost::bind(&CMainFrame::OnUpdateOpenFile,this,_1));
}
if (string(_T("edit_copy"))==m_MenubarVec[i].m_MenuItemVec[j].m_strID)
{
AddToMessageMap(CmdID,boost::bind(&CMainFrame::OnEditCopy,this),boost::bind(&CMainFrame::OnUpdateEditCopy,this,_1));
}
if (string(_T("edit_paste"))==m_MenubarVec[i].m_MenuItemVec[j].m_strID)
{
AddToMessageMap(CmdID,boost::bind(&CMainFrame::OnEditPaste,this),boost::bind(&CMainFrame::OnUpdateEditPaste,this,_1));
}
CmdID++;
}
// 将弹出菜单栏插入到主菜单
m_menu.AppendMenu(MF_POPUP,(UINT)popmenu.m_hMenu,m_MenubarVec[i].m_strCaption.c_str());
popmenu.Detach();
}
// 设置为系统主菜单
SetMenu(&m_menu);
}
最后重载框架类的OnCmdMsg函数,根据菜单ID值调用map里的函数:
view plaincopy to clipboardprint?
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
CUICommandMap::iterator it = m_appCommands.find(int(nID));
if(it == m_appCommands.end())
return FALSE;
// 寻找合适的合适的消息处理函数
if(nCode == CN_COMMAND && (!(*it).second.first.empty()) && pExtra==NULL)
(*it).second.first();
else if(nCode == CN_UPDATE_COMMAND_UI && (!(*it).second.second.empty()))
(*it).second.second((CCmdUI*)pExtra);
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
CUICommandMap::iterator it = m_appCommands.find(int(nID));
if(it == m_appCommands.end())
return FALSE;
// 寻找合适的合适的消息处理函数
if(nCode == CN_COMMAND && (!(*it).second.first.empty()) && pExtra==NULL)
(*it).second.first();
else if(nCode == CN_UPDATE_COMMAND_UI && (!(*it).second.second.empty()))
(*it).second.second((CCmdUI*)pExtra);
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
程序用到的第三方库包括:tinyxml 2.5.3和boost 1.34。相关源码已上传到联合程序开发网:boost库测试下载 。