创建了一个基于对话框的程序,加了一个popup菜单IDR_MENU1。
在CCheckMenuDlg中添加成员变量CMenu m_Menu;
BOOL m_bFirst;
初始化 m_Menu.LoadMenu(IDR_MENU1); m_bFirst=TRUE;
void CCheckMenuDlg::OnFirst()
{
if (m_bOnline)
{
m_Menu.CheckMenuItem(IDC_FIRST,MF_UNCHECKED | MF_BYCOMMAND);
m_bOnline = FALSE;
}
else
{
m_Menu.CheckMenuItem(IDC_FIRST, MF_CHECKED | MF_BYCOMMAND);
m_bOnline = TRUE;
}
如果在菜单项中选择了checked项,菜单项前面会出现对号,点击后消除不掉对号。
若是不选择checked项,点击后也加不上对号,应该怎么办呢?
看了很多之前的帖子,大家都说这么做,我的程序怎么没反应呢?
http://support.microsoft.com/kb/242577/en-us
void CTestDlg::OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex,BOOL bSysMenu)
{
ASSERT(pPopupMenu != NULL);
// Check the enabled state of various menu items.
CCmdUI state;
state.m_pMenu = pPopupMenu;
ASSERT(state.m_pOther == NULL);
ASSERT(state.m_pParentMenu == NULL);
// Determine if menu is popup in top-level menu and set m_pOther to
// it if so (m_pParentMenu == NULL indicates that it is secondary popup).
HMENU hParentMenu;
if (AfxGetThreadState()->m_hTrackingMenu == pPopupMenu->m_hMenu)
state.m_pParentMenu = pPopupMenu; // Parent == child for tracking popup.
else if ((hParentMenu = ::GetMenu(m_hWnd)) != NULL)
{
CWnd* pParent = this;
// Child windows don't have menus--need to go to the top!
if (pParent != NULL &&
(hParentMenu = ::GetMenu(pParent->m_hWnd)) != NULL)
{
int nIndexMax = ::GetMenuItemCount(hParentMenu);
for (int nIndex = 0; nIndex < nIndexMax; nIndex++)
{
if (::GetSubMenu(hParentMenu, nIndex) == pPopupMenu->m_hMenu)
{
// When popup is found, m_pParentMenu is containing menu.
state.m_pParentMenu = CMenu::FromHandle(hParentMenu);
break;
}
}
}
}
state.m_nIndexMax = pPopupMenu->GetMenuItemCount();
for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;
state.m_nIndex++)
{
state.m_nID = pPopupMenu->GetMenuItemID(state.m_nIndex);
if (state.m_nID == 0)
continue; // Menu separator or invalid cmd - ignore it.
ASSERT(state.m_pOther == NULL);
ASSERT(state.m_pMenu != NULL);
if (state.m_nID == (UINT)-1)
{
// Possibly a popup menu, route to first item of that popup.
state.m_pSubMenu = pPopupMenu->GetSubMenu(state.m_nIndex);
if (state.m_pSubMenu == NULL ||
(state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||
state.m_nID == (UINT)-1)
{
continue; // First item of popup can't be routed to.
}
state.DoUpdate(this, TRUE); // Popups are never auto disabled.
}
else
{
// Normal menu item.
// Auto enable/disable if frame window has m_bAutoMenuEnable
// set and command is _not_ a system command.
state.m_pSubMenu = NULL;
state.DoUpdate(this, FALSE);
}
// Adjust for menu deletions and additions.
UINT nCount = pPopupMenu->GetMenuItemCount();
if (nCount < state.m_nIndexMax)
{
state.m_nIndex -= (state.m_nIndexMax - nCount);
while (state.m_nIndex < nCount &&
pPopupMenu->GetMenuItemID(state.m_nIndex) == state.m_nID)
{
state.m_nIndex++;
}
}
state.m_nIndexMax = nCount;
}
}
大家都知道如果使用MFC操作弹出菜单(PopupMenu),可以使用ON_UPDATE_COMMAND_UI宏来处理菜单项的状态,例如在用户未进行复制操作的时候,禁用粘贴菜单项。
但是这种方法对于上下文菜单来说无效,其原因在于上下文菜单的资源一般是在菜单消失之后直接被销毁的,不像弹出菜单那样随着主窗体一直存在,因此只能在TrackPopupMenu函数执行之前使用CheckMenuItem函数设置需要的菜单项的状态:
void COwnerDrawMenuView::OnContextMenu(CWnd* pWnd, CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_CONTEXT);
CMenu *pContextMenu = menu.GetSubMenu(0);
// 设置菜单项的可用状态
pContextMenu->CheckMenuItem(m_shapeType, MF_BYPOSITION | MF_CHECKED);
pContextMenu->TrackPopupMenu(TPM_RIGHTBUTTON, point.x, point.y, this, NULL);
}
关于TrackPopupMenu函数,显示一个快捷菜单,一般点右键的消息响应中来创建一个快捷菜单,这个菜单就如同一个临时变量,创建了,相应了,然后消失了,下回出来的又是一个新的,有人可能碰到的问题的,在菜单项的更新函数中去setcheck某个菜单项,悲剧的是没反应,为什么呢,因为每次的快捷菜单都不是同一个
只能这样:
//代码片段
CPoint pt;
GetCursorPos(&pt);
//根据标识在菜单显示前来setcheck,这样看起来就像一直在处理一个一样
if(m_showBlood)//一个标识
pSubMenu->CheckMenuItem(IDC_xianxue,MF_BYCOMMAND|MF_CHECKED);
else
pSubMenu->CheckMenuItem(IDC_xianxue,MF_BYCOMMAND|MF_UNCHECKED);
SetForegroundWindow();//这个使得你在点击其他地方时快捷菜单会消失,很好哦
pSubMenu->TrackPopupMenu( TPM_LEFTALIGN|TPM_RIGHTBUTTON, pt.x ,pt.y ,this,NULL);
//--
1. 在菜单上加个子菜单TEST,然后在CMainFrame,CMenuApp,CMenuDoc,CMenuView这4个类中都添加响应WM_COMMAND命令消息的OnTest函数
测试表明
(1) 只会有一个OnTest能响应
(2)响应优先级 ,类的顺序是 CMenuView ---> CMenuDoc --->CMainFrame ---> CMenuApp,
意思是: CMenuView有OnTest()函数时,就它能响应,其他的都不会响应; 删除 CMenuView中的OnTest(),就只有CMenuDoc中的OnTest响应,其他的都不会响应.
2 Pop-up菜单项 也就是顶层的"文件","编辑","查看","帮助" 都设置为Pop-up,不能响应COMMAND消息; TEST没有设置为Pop-up,是可以响应COMMAND命令消息的.
3. CWnd与CCmdTarget
(1) CWnd是CCmdTarget的派生类; CCmdTarget是父类,CWnd是子类
(2) 在MFC类层次结构图中, CCmdTarget下面派生出的类只能响应COMMAND命令消息
(3) CWnd下面派生出的类 既可以响应COMMAND命令消息也可以响应其他标准消息.
4 COMMAND消息路由 :
MainFrame ----> View ---> Doc
App <--- MainFrame <---View <---Doc
5. 菜单的结构,子菜单的概念
6. GetMenu()->GetSubMenu(0),CheckMenuItem,SetDefaultItem等函数的调用......含有MenuItem 肯定是针对 子菜单项
//---
//---
//--
//
//------ 子菜单项勾选函数CheckMenuItem
//MenuItem:菜单项就是子菜单下面的各个选项,如"新建","打开","保存"等等.
// GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION|MF_CHECKED);////通过索引0访问"新建"菜单项MenuItem
// GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW,MF_BYCOMMAND|MF_CHECKED);//通过命令ID访问"新建"菜单项MenuItem
//------ 子菜单项设置为默认函数SetDefaultItem
// GetMenu()->GetSubMenu(0)->SetDefaultItem(0,true);//第二个参数是true,则第一个参数用索引
// GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_NEW);//第二个参数默认是false,则第一个参数ID
// GetMenu()->GetSubMenu(0)->SetDefaultItem(5,true); //"打印"是5不是4,因为分隔栏是要占索引的, 另外只能有一个缺省菜单项
//------ 子菜单项上添加位图(文字左边) SetMenuItemBitmaps
CString str;
str.Format("x=%d, y=%d",GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK));
//MessageBox(str);//x=13,y=13 系统子菜单项位图大小必须是13*13
m_bitmap.LoadBitmap(IDB_BITMAP1);
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0,MF_BYPOSITION,&m_bitmap,NULL); //要注意修改位图的底色,白色就看不见了.
//------ 禁用子菜单项
// m_bAutoMenuEnable 在CMainFrame()构造函数中设置其为m_bAutoMenuEnable =false 关闭MFC自动更新菜单的机制
GetMenu()->GetSubMenu(0)->EnableMenuItem(ID_FILE_OPEN,MF_DISABLED|MF_GRAYED);//禁用"打开"子菜单项
//GetMenu()->GetSubMenu(0)->EnableMenuItem(0,MF_BYPOSITION|MF_DISABLED|MF_GRAYED);//或者这样用子菜单项索引
//------ 删除整个菜单,和加载菜单
SetMenu(NULL); // 填参数NULL 就删除了整个菜单
//------加载菜单,可以加载自定义菜单资源
CMenu menu; // 这里有个隐藏的问题 就是menu是个局部变量,解决办法是(1)把他放到CMainFrame中作为成员变量
//(2) 就是下面的加上:menu.Detach();
menu.LoadMenu(IDR_MAINFRAME); //菜单资源ID
SetMenu(&menu);
menu.Detach();//将菜单句柄与CMenud对象menu脱离,这样局部变量menu析构是就不会这个菜单资源(因菜单资源已经不属于它了,析构不到)
1、在菜单项前添加标记
用到的函数
GetMenu() 作用 :获取菜单栏;GetSubMenu() 作用: 获取子菜单;
CheckMenuItem()作用:菜单标记;
函数功能:该函数取得与指定菜单项相联系的菜单标志。如果该菜单项打开了一个子菜单,该函数也返回子菜单里的菜单项数。
函数原型:DWORD CheckMenuItem(HMENU hmenu, UINT uIDCheckItem, UINT uCheck);
参数:
hmenu:含有其菜单项的标志将被提取得的菜单的句柄。一般为缺省值,可以省略。
uIDCheckItem:指定要修改的菜单项。这个参数取决于第三个参数,若第三个参数为MF_BYCOMMAND,则该参数是所选菜单项的ID号,若为MF_BYPOSITION,则该参数是所选菜单的索引值。
uCheck:表示标记的状态。此参数可取下列值之一:
MF_BYCOMMAND:表示参数uId给出菜单项的标识符。如果MF_BYCOMMAND和MF_BYPOSITION都没被指定,则MF_BYCOMMAND是缺省值。
MF_BYPOSITION:表示参数uId给出菜单项相对于零的位置。
其中MF_BYCOMMAND和MF_BYPOSITION均可以与菜单项相关的菜单标志一起使用。
返回值:如果指定的项不存在,返回值是OXFFFFFFFF;如果菜单项打开了一个子菜单,则返回值的低位含有与菜单相联系的菜单标志,高位含有子菜单的项数。否则,返回 值是莱单标志的掩码(布尔OR)。
下面列出与菜单项相关的菜单标志。
MF_CHECKED:放置选取标记于菜单项旁边(只用于下拉式菜单、子菜单或快捷菜单)。
MF_DISABLED:使菜单项无效。MF_GRAYED:使菜单项无效并交灰。MF_HILITE:加亮菜单项。
MF_MENUBARBREAK:对下拉式菜单、子菜单和快捷菜单,新列和旧列由垂直线隔开,其余功能同MF_MENUBREAK标志。
MF_MENUBREAK:将菜单项放于新行(对菜单条)或无分隔列地放于新列(对下拉式菜单、子菜单或快捷菜单)。
MF_SEPARATOR:创建一个水平分隔线(只用于下拉式菜单、子菜单或快捷菜单)。
MF_UNCHECKED: 相当于MF_CHECKED 的反作用,取消放置于菜单项旁边的标记。
例如:对文件-新建菜单前添加一个标记,可在CMainFrame类 oncreat()下添加如下代码:
GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION|MF_CHECKED);
或GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW,MF_BYCOMMOND|MF_CHECKED);
2、创建缺省菜单项
用到的函数:
SetDaultItem()
函数原型:BOOL SetDefaultItem(UINT UItem,BOOL fByPos = FALSE)
第二个参数缺省值是FALSE,若第二个参数是FALSE,则第一个参数是菜单项的ID,若若第二个参数是TRUE,则第一个参数是菜单项的索引值;
例如:将文件-打开设置为缺省菜单,可在CMainFrame类 oncreat()下添加如下代码:
GetMenu()->GetSubMenu(0)->SetDefaultItem(1,TRUE);
或GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN,FALSE);
或GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN);
注意:在一个菜单项中只能有一个缺省菜单
3、创建图形标记菜单
用到的函数:SetMenuItemBitmaps
函数原型:BOOL SetMenuItemBitmaps( UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked, const CBitmap* pBmpChecked );
第一个参数取决于第二个参数,若第二个参数是MF_BYPOSITION,第一个参数则为菜单项的索引,若第二个参数是MF_BYCOMMOND,第一个参数则为菜单项的ID.第三个参数是选择菜单项时菜单项前标记的位图,第四个参数是不选菜单项时它前边的位图标记。
添加步骤
a、新建一个bitmap
b、在CMainFrame中添加成员变量 CBitmap bitmap;
c、在oncreat中添加代码:
bitmap.LoadBitmap(IDB_BITMAP1);
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0,MF_BYPOSITION,&bitmap,&bitmap);
注意:图形标记菜单位图bitma的大小,可以先获取bitmap大小,通过以下代码:
GetSystemMetrics()是获取系统信息;参数是指想获取关于哪方面的信息
可以先获取图形标记菜单位图的大小
CString str;
str.Format("x = %d,y = %d",GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK));
MessageBox(str);
其中GetSystemMetrics(SM_CXMENUCHECK))和GetSystemMetrics(SM_CYMENUCHECK))分别是获取图形标记菜单位图的宽度和高度;
通过 MessageBox输出。
4、使菜单不可用
用到的函数:
EnableMenuItem()
函数原型:BOOL EnableMenuItem(UINT uIDEnableItem, UINT uEnable);
作用:允许或禁止指定的菜单条目
但是在用EnableMenuItem()禁止指定的菜单条目时,应先在构造函数中赋值 m_bAutoMenuEnable = FALSE;
例如: 将文件-保存设为不可用
CMainFrame::CMainFrame()
{
m_bAutoMenuEnable = FALSE;
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
。。。
GetMenu()->GetSubMenu(0)->EnableMenuItem(2,MF_BYPOSITION|MF_GRAYED);
或GetMenu()->GetSubMenu(0)->EnableMenuItem(ID_FILE_SAVE,MF_BYCOMMAND|MF_GRAYED);
}
(注意,若用索引,只能设置菜单的可用与不可用,而不能控制工具栏中图标的可用和不可用,但是,用ID号可以使控制菜单项状态与工具栏状态一致)
5、取消和添加整个菜单
取消菜单:
在oncreat中添加代码 SetMenu(NULL);即可
添加菜单:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
。。。
CMenu menu; //定义变量
menu.LoadMenu(IDR_MAINFRAME); //加载菜单
SetMenu(&menu); //添加菜单
menu.Detach(); //将菜单句柄和c++断开,必须有。
}
命令更新机制:
菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息,谁捕获了CN_UPDATE_COMMAND_UI消息,MFC就在其中创建了一个CCmdUI对象。可以通过类向导在消息映射中添加ON_UPDATE_COMMAND_UI宏来捕获CN_UPDATE_COMMAND_UI消息。
在后台所作的工作是:操作系统发出WM_INITMEUPOPUP消息,然后由MFC基类如CFrameWnd接管。他创建一个CCmdUI对象,并与第一个菜单项相关联,调用对象的一个成员函数,DoUpdate()。这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针。同一个CCmdUI对象就设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项。
更新命令UI处理程序仅应用于弹出式菜单项的项目,不能应用于永久显示的顶级菜单项目。
利用这样的更新机制使菜单项能或不能使用可以用一下的方法:
1、查看——建立类向导
2、在object ids 中选择ID_FILE_NEW 在message中选择ON_UPDATE_COMMAND_UI,点击Addfunction。点击Edit。在函数中输入代码:
pCmdUI->Enable(FALSE);
3、运行,可以看到菜单上新建已经禁用。
为了保证菜单项和工具栏中的工具相一致,一般用ID进行索引。
6、右键弹出菜单功能
工程——增加到工程——组建和控件——visual c++ components——pop-up-menu——insert——确定——add pop-up-menu to 中选择cmainmenu——关闭;
运行,右击鼠标,弹出菜单;
用到的函数:
trackpopupmenu()
函数功能:该函数在指定位置显示快捷菜单,并跟踪菜单项的选择。快捷菜单可出现在屏幕上的任何位置。
函数原型:BOOL TrackPopupMenu(HMENU hMenu,UINT uFlags,int x,int y,int nReserved,HWND hWnd,CONST RECT* prcRect);
hMenu:被显示的快捷菜单的句柄。此句柄可为调用CreatePopupMenu创建的新快捷菜单的句柄,也可以为调用GetSubMenu取得的与一个已存在菜单项相联系的子菜单的句柄。
uFlags:一种指定功能选项的位标志。用下列标志位之一来确定函数如何水平放置快捷菜单:
TPM_CENTERALIGN:若设置此标志,函数将按参数x指定的坐标水平居中放置快捷菜单。
TPM_LEFTALIGN:若设置此标志,函数使快捷菜单的左边界与由参数X指定的坐标对齐。
TPM_RIGHTALIGN:若设置此标志,函数使快捷菜单的右边界与由参数X指定的坐标对齐。
用下列标志位之一来确定函数如何垂直放置快捷菜单:
TPM_BOTTOMALIGN:若设置此标志,函数使快捷菜单的下边界与由参数y指定的坐标对齐。
TPM_TOPALIGN:若设置此标志,函数使快捷菜单的上边界与由参数y指定的坐标对齐。
TPM_VCENTERALIGN;若设置此标志,函数将按参数y指定的坐标垂直居中放置快捷菜单
用下列标志位之一来确定在菜单没有父窗口的情况下用户的选择:
TPM_NONOTIFY:若设置此标志,当用户单击菜单项时函数不发送通知消息。
TPM_RETURNCMD;若设置此标志;函数将用户所选菜单项的标识符返回到返回值里。
(补充:当TrackPopupMenu的返回值大于0,就说明用户从弹出菜单中选择了一个菜单。以返回的ID号为参数wParam的值,程序给自己发送了一个WM_SYSCOMMAND消息)
用下列标志位之一来确定在快捷菜单跟踪哪一个鼠标键:
TPM_LEFTBUTTON:若设置此标志,用户只能用鼠标左键选择菜单项。
TPM_RIGHTBUTTON:若设置此标志,用户能用鼠标左、右键选择菜单项。
X:在屏幕坐标下,快捷菜单的水平位置。
Y:在屏幕坐标下,快捷菜单的垂直位置。
NReserved:保留值,必须为零。
HWnd:拥有快捷菜单的窗口的句柄。此窗口接收来自菜单的所有消息。函数返回前,此窗口不接受来自菜单的WM_COMMAND消息。
如果在参数uFlags里指定了TPM_NONOTIFY值,此函数不向hWnd标识的窗口发消息。 但必须给hWnd里传一个窗口句柄,可以是应用程序里的任一个窗口句柄。
PrcRect:未用。
返回值:如果在参数uFlags里指定了TPM_RETURNCMD值,则返回值是用户选择的菜单项的标识符。如果用户未作选择就取消了菜单或发生了错误,则退回值是零。如果没在参数uFlags里指定TPM_RETURNCMD值,若函数调用成功,返回非零值,若函数调用失败,返回零。若想获得更多的错误信息,清调用GetLastError
手动添加弹出菜单方法:
1、在menu中添加菜单
2、在从view中添加句柄,在window消息句柄中选择WM_RBUTTONDOWN,添加句柄。
3、在OnRButtonDown中添加代码如下:
void CMenuView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CMenu menu;
menu.LoadMenu(IDR_MENU1); //加载弹出菜单
CMenu *pPopup = menu.GetSubMenu(0);
ClientToScreen(&point); //将客户区窗口转为屏幕窗口
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,
GetParent()); //GetParent()是指向cmainframe的指针,这样可以响应在cmainframe中添加的消息响应函数。
CView::OnRButtonDown(nFlags, point);
}
4、运行。
动态添加、删除、操作菜单:
用到的函数 AppendMenu(在现有菜单后添加菜单) creatpopupmenu(创建空的弹出菜单,将他和CMENU的一个对象关联起来)
eg:添加一个弹出菜单:
eg:插入一个弹出菜单:
InsertMenu
eg:删除一个菜单
DeleteMenu
手动添加响应函数:
1、定义ID号
在resource中的Header File下的Resource.h中添加
#define IDM_HELLO 111(定义ID号)
2. 添加函数
a、在头文件中写:afx_msg void OnHello() //消息原型
b、ON_COMMAND (IDM_HELLO,OnHello() //添加消息响应
c、添加函数
void CMainFrame::OnHello()
{
MessageBox("hello!");
}
d、运行,就可以发现winsun下的hello是可用的
ModifyMenu的用法
关于这个API,MSDN讲得实在有点晦涩,这里总结一下。
假设要更改以下菜单的Disable Sampling项文本为EnableSampling
首先应该获取submenu父菜单的句柄,假设submenu的资源ID号为IDR_MENU2,使用以下代码:
1 hMenu = LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(IDR_MENU2));
2 hMenu = GetSubMenu(hMenu, 0);
然后,因为submenu父菜单的起始索引为0,所以Disable Sampling的索引为5,因为根据索引(编号)来修改菜单,所以ModifyMenu这个API的第三个参数uFlags应该包括MF_BYPOSITION,又因为需要修改的是菜单文本,uFlags应该包括MF_STRING。
ModifyMenu的第四个参数(uIDNewItem)允许修改菜单的ID号(菜单资源标示符),由于菜单ID号是跟菜单命令(WM_COMMAND)相关联的,修改菜单ID号会影响菜单命令的正常执行, 所以这里不需要修改菜单的ID号,第四个参数应该填写在资源编辑器中创建菜单时输入的ID号。
所以,将Disable Sampling菜单项的文本修改为Enable Sampling的代码应该如下所示:
1 char *pszMenuText = "Enable Sampling";
2 ModifyMenu(hMenu, 5, MF_STRING | MF_BYPOSITION,
3 ID_SUBMENU_DISABLESAMPLING, pszMenuText);