一个菜单拦可以有若干个子菜单,一个子菜单又可以有若干个菜单项等。对菜单栏的子菜单由左至右建立从0开始的索引。对特定子菜单的菜单项由上至下建立了从0开始的索引。访问子菜单和菜单项均可以通过其索引或标识(如果有标识的话)进行。
菜单只有一个! 子菜单 ! 菜单项 !索引都是从0开始
//菜单的操作是在 框架类(Frame) 进行的 :
定位菜单的方法及有关函数:
第一步获得菜单:
CWnd::CMenu* GetMenu( ) const; //指针
第二步获得子菜单:
CMenu::CMenu* GetSubMenu( int nPos ) const;
参数 nPos 是子菜单的位置(索引)从0开始;
第三步对菜单项操作~
可以一起使用 GetMenu()->GetSubMenu(0)->?
菜单的特殊操作
1 给菜单前打对号 (标记菜单项)
函数 UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );
nIDCheckItem 是与 nCheck 相关的参数取值
nCheck 是 MF_BYCOMMAND or MF_BYPOSITION 和 MF_CHECKED or MF_UNCHECKED
的和
2 给菜单前添位图 (标记菜单的功能等)
函数 BOOL SetMenuItemBitmaps( UINT nPosition, UINT nFlags,
const CBitmap* pBmpUnchecked,
const CBitmap* pBmpChecked );
nPosition 与 nFlags 相关 nFlags 为 MF_BYCOMMAND 或 MF_BYPOSITION
注意CBitmap 是资源定义的应该是类变量 而不是 局部变量
CBitmap 13*13 才能符合标记大小
技巧:给出一个求 系统中定义大小的函数 int GetSystemMetrics(int nIndex )
nIndex 为系统的ID 查询MSDN (记得 GetTextMetrics 是文本信息 字体的平均宽度和高度的求法)
CString :: Format("%d,%d")格式处理内容到 str字串中 和 C中的printf();用法差不多~!
3 标明菜单可用不可用(变灰+是否真的可点击来发送命令消息)
使用函数CMenu::UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );
同样参数 nIDEnableItem 与参数 nEnable 有关
nEnable MF_BYCOMMAND or MF_BYPOSITION 与 MF_DISABLIE or MF_ENABLE 也可以变为灰色 MF_GRAYED
需要注意的是 想让手动对菜单项进行操作 需要在(Frame)构造函数中将一个值( m_bAutoMenuEnable )
初始化为 FALSE ! m_bAutoMenuEnable 提供了MFC自动识别是否菜单项可用(通过检测是否为MFC添加了
消息响应函数)!!
4 加载新的菜单 或 更换新的菜单
有关函数 SetMenu(CMenu *menu);返回值为 修改之前的菜单 ;注意是指针!!
忽忽 使用SetMenu(NULL); 就可以取消菜单哦~
不要忘了 Menu 也是资源 需要定义在类成员中 不然要出错的!!!
使用Menu.LoadMenu( IDNo. );
MFC的菜单消息更新机制~(可以通过此来修改菜单是否可用等)
菜单的运行是一个快速循环的过程,依赖于CN_UPDATE_COMMAND_UI消息 谁捕获了CN_UPDATE_COMMAND_UI消息
MFC将会创建一个对象 CCmdUI (UI是用户接口的英文缩写 User-I****记不住了) CCmdUI 先与子菜单索引位
置0,菜单项索引位置0开始 循环一次向下推进一次(当然CPU的速度很快 几乎就是一瞬间的事情)而我门通过
对CN_UPDATE_COMMAND_UI消息的捕获 来控制菜单的属性;
实现的方法:通过手工或 Classwizard 来添加一个命令ID的响应 (不要使用原来用的COMMAND而使用UPDATE_COMMAND_UI) 利用所添加的函数 传进来的参数 pCmdUI(是指针)来进行属性的操作!
//工具栏图标的索引记数顺序是:从做至右从0开始,分隔符也算索引号
//当工具栏与菜单ID一样时 就可以实现其快捷方式(点击后产生相同的消息映射)
提示 可以去看看 pCmdUI 中的成员!
增加右键弹出菜单的功能(俩种方法)
1 通过编译器来添加右键菜单
点击VC6.0的Project(工程)->Add to Project(增加到工程)->Components and Controls.....
观察它为我门增加了什么进入代码:A,一个菜单资源;B,在派生View类中增加OnContextMenu()函数
(至于怎么对右键进行消息扑获 未研究清楚)
所以我门使用对右键的消息响应 在其中增加菜单就可以手动来加入菜单!
2 手动来加入右键POP菜单!
先添加一个对右键响应的消息处理 OnRButtonDown函数
在OnRButtonDown函数内 例子代码:
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu *pPopup=menu.GetSubMenu(0);
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
this);
对函数 TrackPopupMenu()说明:
CMenu::BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );
参数:
nFlags 是指一个右键的菜单的弹出方式(包括他与鼠标的位置等,习惯参数为TPM_LEFTALIGN | TPM_RIGHTBUTTON 意思是 在鼠标右边 向下弹出!)
x,y 是坐标点 (注意是 屏幕的坐标点而不是客户区的坐标 需要使用
void ClientToScreen( LPPOINT lpPoint ) const来转换成客户区!
CWnd* pWnd 是指这个菜单的拥有者 使用 this 就可以
lpRect 是点击多大区域有效 (NULL)
对ClientToScreen()分析 是 CWnd 的成员函数
是将 现在的Client 坐标 转化为 Screen 坐标!
而 TrackPopupmenu()是使用 Screen 坐标的!
对 Popup 菜单的响应顺序 View -> Frame
技巧: 得到父窗口指针 使用函数 Cwnd::CWnd* GetParent( ) const;
有关于资源声明成类成员的的另一种发现::
之所以声明资源为类成员 是因为当他是局部的变量时 在执行完有关函数
后,会被析构(释放空间) 而发现了一个大部分资源类都有的函数 Detach()
可以把资源与相关的句柄断开 使他不会发生析构 动态添加菜单等资源时,必须这
么使用 而不是声明资源为类的成员~;(那就不叫动态了!!)
菜单的动态操作:
有关函数:
在菜单尾部添加
CMenu::BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );
CMenu::BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );
nFlags 是所要添加菜单的种类
nIDNewItem 将会根据 nFlags 的变化而变化
这是一个很复杂的函数 在教程里我门使用这个函数:
nFlags 为一个Popup的弹出菜单 时 nIDNewItem 是弹出菜单的句柄 menu.m_hMenu 但是参数要求是一个
UINT 类型 我门需要强制转换一下(原理不祥!)
CMenu::CreatePopupMenu() 创建一个空的弹出菜单 与 菜单资源相关连~
在原有菜单后添加弹出(Popup)菜单的完整代码:
CMenu menu; //先要创建一个新的资源菜单 然后使用这个资源加载在原来菜单的后面
menu.CreatePopupMenu();
GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"daigong"); // 这里因为是添加Popup菜单
// 当然指获取 菜单的指针就OK
// 而不是子菜单
menu.Detach(); //使用了Detach 不让资源析构
在菜单间插入新的菜单:
函数:CMenu::BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );
nFlags决定了nPosition 的取值(命令or索引号)也控制了插入的形式
AppendMenu()也好 InsertMenu()也好 都需要为新的菜单项给定一个 ID号 以便用来以后的消息响应!
还有就是对什么形式的菜单操作就获取什么菜单的对象或指针 (例:增加Popup菜单需要 GetMenu()
而 增加子菜单中的菜单项则需要 GetMenu()->GetSubMenu( nPos )来调用函数)
删除菜单
函数
CMenu::BOOL DeleteMenu( UINT nPosition, UINT nFlags );
同上 不用在说了吧 T_T
手动添加消息响应函数(重要哦 可以不依赖编译器的方法!!!)
1 先要定义命令的ID 在FileView里头文件 Resource.h 中添加 例:#define IDM_DAIGONG 117
如果原来有ID 那么 就不需要添加了;
2 找到相应的头文件(通常是Frame类中因为对是对菜单进行消息响应么!)
找到(一般会在头文件的最后)
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
DECLARE_MESSAGE_MAP()
};
来添加消息函数原形:afx_msg void OnDaigong();
代码变为:
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 OnDaigong(); //后来增加的代码
DECLARE_MESSAGE_MAP()
};
3 添加消息映射!
在代码区(.cpp文件)里 找到:(通常是在前面)
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
END_MESSAGE_MAP()
添加形式: ON_COMMAND(ID号,命令响应函数名)
这里要说明很多问题!! 第1 第二个参数上命令响应函数的名字 是没有()的
第2 这个声明是结尾没有任何标点符号的(别以为我门写!!)
代码:
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_EDIT_COPY,OnEDIT_COPY) //后来添加的! 可要没有标点哦!
END_MESSAGE_MAP()
4 在CPP文件里写函数体
void CMainFrame::OnEDIT_COPY()
{
MessageBox("OK");
}
技巧:如果忘记了怎么写消息映射 可以使用ClassWzard 添加一个消息响应函数 用此来引导我门去写
消息响应 不过要注意一点!!::在CPP文件中消息映射放到宏外(注意一定要放到宏外面,因为CLASSWIZARD发现菜单删除了,同时要把其宏对里的消息映射也删除掉的)!