第6课,菜单及与菜单相关的编程:
新建一个单文档MFC程序。
消息响应的四个顺序:VIEW类,DOC类,框架类,APP类。
n 标准消息
除WM_COMMAND之外,所有以WM_开头的消息。
从CWnd派生的类,都可以接收到这类消息。
n 命令消息
来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。
从CCmdTarget派生的类,都可以接收到这类消息。
n 通告消息
由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。
从CCmdTarget派生的类,都可以接收到这类消息。
从CWnd派生的类可以接收标准消息和命令消息和通告消息。
菜单消息路由过程:view—doc—view—frame—app
下面是菜单:
子菜单,菜单项。
在int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)中添加:
GetMenu()->GetSubMenu(0)->CheckMenuItem( 0, MF_BYPOSITION|MF_CHECKED );
此句是给文件/新建做个标记。
下面介绍缺省菜单项:
GetMenu()->GetSubMenu(0)->SetDefaultItem( 1,TRUE);缺省项为粗体显示。缺省项在一个子菜单中只能设置一个,分格栏也占一个ID号。
下面创建一个图形标记菜单:
CBitmap *bp1=new CBitmap;
CBitmap *bp2=new CBitmap;
bp1->LoadBitmap(IDB_BITMAP1);
bp2->LoadBitmap(IDB_BITMAP2);
//下面的程序用来得到标记图标的大小,以此来做图
// int x=GetSystemMetrics(SM_CXMENUCHECK);
// int y=GetSystemMetrics(SM_CYMENUCHECK);
// CString str;
// str.Format("x=%d,y=%d",x,y);
// MessageBox(str);//宽度和高度都应为13
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(1,MF_BYPOSITION,bp1,bp2);
下面让菜单项不能使用。
GetMenu()->GetSubMenu(0)->EnableMenuItem(ID_FILE_OPEN,
MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
当然要加上下面的代码,否则程序不能运行正常,达到预期效果:
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
m_bAutoMenuEnable=FALSE;
}
但是m_bAutoMenuEnable不建议改变它的值,因为它是系统自动改动的。
将整个菜单取消掉:
SetMenu( NULL );
下面是再创建:
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();
MFC对菜单项所采用的命令更新机制。
原先在菜单中的剪切菜单项是不能用的,下面代码让它可以使用。(CN_UPDATE_COMMAND_UI)
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable();
}
更新命令UI处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目。
下面创建一个右键弹出菜单功能:通过插入右键弹出控件
也可以自己创建,先创建一个菜单项。
void CTestView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CMenu *pmenu=menu.GetSubMenu(0);
ClientToScreen(&point);
pmenu->TrackPopupMenu(TPM_LEFTALIGN |TPM_RIGHTBUTTON, point.x+5,
point.y+5, this);
CView::OnRButtonDown(nFlags, point);
}
此时弹出菜单的菜单项在框架类中是不能响应的。因为弹出菜单的拥有者是VIEW类,因此只能是VIEW类对弹出菜单进行响应。如果要想让框架类对弹出菜单也进行响应,要修改弹出菜单的拥有者窗口。
下面动态添加,删除菜单。
为框架类增加:
private:
CMenu m_popmenu;
在int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)中:
m_popmenu.CreatePopupMenu();
GetMenu()->AppendMenu(MF_POPUP,(UINT)m_popmenu.m_hMenu,"popupmenu");
GetMenu()->InsertMenu(1,MF_BYPOSITION,(UINT)m_popmenu.m_hMenu,"insertmenu");
在资源中的STRING项中增加:IDM_POPMENU1--IDM_POPMENU3的说明。
m_popmenu.AppendMenu(MF_STRING,IDM_POPMENU1,"popmenu1");
m_popmenu.AppendMenu(MF_STRING,IDM_POPMENU2,"popmenu2");
m_popmenu.AppendMenu(MF_STRING,IDM_POPMENU3,"popmenu3");
GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,111,"filemenu3");
GetMenu()->GetSubMenu(0)->InsertMenu(2,MF_BYPOSITION|MF_STRING,112,"fileinsert");插入菜单。
下面删除菜单:
GetMenu()->DeleteMenu(1,MF_BYPOSITION);
GetMenu()->GetSubMenu(0)->DeleteMenu(2,MF_BYPOSITION);//删除菜单子项。
现在对这些动态创建的菜单进行命令响应。
GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,111,"filemenu3");
对这个111进行响应。因为没有对它进行ID号的说明,要自己手动添加:
在Resource.h中定义如下:
#define IDM_FILEMENU3 111 //这样的话,就有了一个ID号。
然后将此句改成有ID号的:
GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,IDM_FILEMENU3,"filemenu3");
在框架类的头文件中加上:afx_msg void OnFileMenu3();
在框架类的源文件中:ON_COMMAND(IDM_FILEMENU3,OnFileMenu3)
void CMainFrame::OnFileMenu3()
{
MessageBox("Onfilemenu3");
}
这样就实现了函数的实现。
下面对IDM_POPMENU1(我们自己给出的ID号的菜单增加命令响应)
和上面的实现一样:三步骤:头文件中一处,源文件中两处。
下面编写一个电话本程序:
输入一个人名+空格+电话号码,动态增加菜单,人名作为动态菜单项增加进去,当点击增加的弹出菜单时,显示人名+空格+电话号码。
为CTestView增加:
private:
int m_index; 初始化为0
CMenu m_menu;
CString m_str;
public:
CStringArray m_strArray;
给菜单添加四个菜单项,分别是IDM_PHONE1---IDM_PHONE4,在Resource.h中定义它们的值。
#define IDM_PHONE1 201
#define IDM_PHONE2 202
#define IDM_PHONE3 203
#define IDM_PHONE4 204
在VIEW类中添加消息响应。然后把它们删除。
这是在VIEW类的源文件中:
BEGIN_MESSAGE_MAP(CTestView, CView)
//{{AFX_MSG_MAP(CTestView)
ON_WM_CHAR()
ON_COMMAND(IDM_PHONE1, OnPhone1)
ON_COMMAND(IDM_PHONE2, OnPhone2)
ON_COMMAND(IDM_PHONE3, OnPhone3)
ON_COMMAND(IDM_PHONE4, OnPhone4)
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
这样是不行的,因为是系统自己加的对IDM_PHONE的响应,所以在中间。我们把它们拿出来,改成下面的形式:
BEGIN_MESSAGE_MAP(CTestView, CView)
//{{AFX_MSG_MAP(CTestView)
ON_WM_CHAR()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
ON_COMMAND(IDM_PHONE1, OnPhone1)
ON_COMMAND(IDM_PHONE2, OnPhone2)
ON_COMMAND(IDM_PHONE3, OnPhone3)
ON_COMMAND(IDM_PHONE4, OnPhone4)
END_MESSAGE_MAP()
此时CLASSWIZARD中是看不到IDM_PHONE的。(在这里一定要拿出来,因为刚刚是CLASSWIZARD自己生成的。因为我们把菜单已经删除了,帮CLASSWIZARD会将命令响应删除)。头文件中可以拿出来,也可以不拿出来。
void CTestView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
CClientDC dc(this);
if (0X0d==nChar)
{
if (m_index++==0)//第一次回车
{
m_menu.CreatePopupMenu();
GetParent()->GetMenu()->AppendMenu(MF_POPUP,(UINT)m_menu.m_hMenu,"notebook");
GetParent()->DrawMenuBar();//当动态改变菜单时,要对菜单项进行重绘
}
int spaceaddr=m_str.Find(' ');
CString strname=m_str.Left(spaceaddr);
m_menu.AppendMenu(MF_STRING,IDM_PHONE1+(m_index-1),strname);
m_strArray.Add(m_str);
m_str.Empty();
Invalidate();//擦除窗口
}
else//输入数据
{
m_str+=nChar;
dc.TextOut(0,0,m_str);
}
CView::OnChar(nChar, nRepCnt, nFlags);
}
void CTestView::OnPhone1()
{
// TODO: Add your command handler code here
CClientDC dc(this);
dc.TextOut(0,0,m_strArray.GetAt(0));
}
void CTestView::OnPhone2()
{
// TODO: Add your command handler code here
CClientDC dc(this);
dc.TextOut(0,0,m_strArray.GetAt(1));
}
void CTestView::OnPhone3()
{
// TODO: Add your command handler code here
CClientDC dc(this);
dc.TextOut(0,0,m_strArray.GetAt(2));
}
void CTestView::OnPhone4()
{
// TODO: Add your command handler code here
CClientDC dc(this);
dc.TextOut(0,0,m_strArray.GetAt(3));
}
当然了,如果你想要显示更多 ,就可以依照刚刚的方法,来添加phone5,phone6。。。。。。
下面对菜单的响应由框架类响应。;
根据消息漏由,先由框架类交给VIEW类。
virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );
The framework calls this member function when the user selects an item from a menu, when a child control sends a notification message, or when an accelerator keystroke is translated.
给框架类的头文件加上 #include "TestView.h"(源文件参与编译,头文件不参与)
BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)
{
CTestView *pView = (CTestView*)GetActiveView();
if (LOWORD(wParam)<IDM_PHONE1+pView->m_strArray.GetSize() && LOWORD(wParam)>=IDM_PHONE1)
{
CClientDC dc(pView);
dc.TextOut(0,0,pView->m_strArray.GetAt(LOWORD(wParam)-IDM_PHONE1));
return TRUE;
}
return CFrameWnd::OnCommand(wParam,lParam);
}
在框架类当中去截获本身最先就由VIEW类响应的消息。(通过增加OnCommand来处理)。