MFC Menu

这节我们要学习的是菜单,首先我们点击Resourse View 找到Menu 打开。我们看到一个菜单资源。VC++的资源编辑器给我们提供了所见即所得的编辑方式。

    我们会发现第一栏的菜单ID号是灰色的。而下边的ID号是可以输入的。我们发现有个Pop-up复选框。Pop-up复选框被选中的菜单被称为弹出菜单,是不能用来进行命令响应的。

    如果我们不希望这个菜单为弹出菜单,那么我们可以把Pop-up的钩去掉。然后我们输入一个ID号。我们用大写字母标识ID。我们的菜单项取ID号的时候,我们加一个IDM用来标识这是Menu资源。例如,图标IDI,光标IDC来表示。那么有了菜单项,我们就要为它进行命令响应了。选择View(查看)->ClassWizard(建立类向导)

我们选择IDM_TEST(这个是我们自己建的一个菜单ID),我们选择消息响应COMMAND,然后点击Add Function添加一个函数。编辑代码。在我们所有的类中,都可以响应菜单项。而哪一个类最先对菜单项进行响应呢?

我们对 CMenuApp类   CMenuDoc类  CMenuView类都进行菜单响应。当我们点击这个菜单的时候。只有CMenuView进行了菜单响应。我们把View注释掉,然后运行,发现 Doc类进行了响应。我们注释掉Doc类,然后运行。发生响应的是MainFram类发生响应。注释掉后,最后App类才进行响应。

    对消息来说,我们把它们分为3类,一类为标准消息,一类为命令消息,一类为通告消息。(从CWnd派生出来的类这3中消息都可以接收)

标准消息:

         除了WM_COMMAND戏外,所有以WM_开头的消息。从CWnd派生的类,都可以接收到这类消息。

命令消息:

         点击菜单项,加速键,或者工具栏按钮的消息,这类消息都是以WM_COMMAND呈现的。在MFC当中,通过菜单项的标识(ID)来区分不同的命令消息,在SDK中,通过消息的wParam参数识别。从CCmdTarget派生的类,都可以接收到这类消息。

 

    我们发现CWnd类是从CTmdTarget类中派生出来的。文档类(CDocument)和应用程序类(CWinAPP)是从CTmdTarget的类中派生出来的, 他们可以接收命令消息,但是他们不能接收标准消息。

通告消息:

    由控件产生的消息。例如,按钮的单击,列表框的选择等均产生此类消息。为的是向其父窗口(通常是对话框)通知事件的发生,这类消息以WM_COMMAND形式呈现。CTmdTarget的类中派生出来的, 他们可以接收命令消息.

       接收一个命令消息的时候,选择View(查看)->ClassWizard(建立类向导中) ,找到我们的ID号,在Messages:下选择COMMAND添加函数. 最先接收到命令消息的是框架类,它会把消息交给子窗口View类,

View类查找一下,如果没有对菜单命令进行响应,它会把命令交给文档类。Doc 进行处理。如果没有响应,文档类会把消息交还给View类,再交还给CMainFrame,查找,如果没有进行响应,那么最后交给App这个应用程序类进行处理。

 

标记菜单:

    标记菜单就是菜单项左边有个对号,这就是我们的标记菜单。如果我们想创建一个标记。我们在CMainFram OnCreate这个函数中完成。

什么是子菜单: 我们通常看到的一个整列的菜单,就是子菜单。菜单的第一个菜单项,索引为0;

我们利用一个方法获取框架窗口菜单栏的指针。

CMenu* GetMenu( ) const;

它的返回值是一个指向CMenu的一个指针,CMenu封装了跟菜单有关的操作。

那么我们看一下CMenu

在这个函数中,它给我们提供了一个函数,可以获取子菜单。

CMenu* GetSubMenu( int nPos ) const;

他有一个参数,他们根据我们菜单的弹出位置去访问。

他们两个函数返回的指针是不同的。GetMenu返回的是指向菜单栏的指针  GetSubMenu它返回的是指向子菜单的指针,我们要做一个复选的标记菜单,我们用

UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );

第一参数的含义是由第二个参数决定的。 获得菜单项的索引 或者ID号。

例:

GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW,MF_BYCOMMAND|MF_CHECKED);

 

缺省菜单项:

   有时候我们看到子菜单下边会有一个菜单项是以粗体的形式显示的。这就是缺省菜单项。一个子菜单只能有一个缺省菜单项。

BOOL SetDefaultItem( UINT uItem, BOOL fByPos = FALSE );

这个来获取菜单。

例:GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN,FALSE);

 

图形标记菜单的创建:

CMenu中有这样的一个菜单函数:

BOOL SetMenuItemBitmaps( UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked, const CBitmap* pBmpChecked );

后便两个参数都是CBitmap的指针,一个是我们选中的时候的一个位图,一个是我们取消时候的一个位图。

我们在资源里new一个位图。

我们为CMainFrame加一个成员变量m_bitmap;

m_bitmap.LoadBitmap(IDB_SB); //加载资源里的位图
GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0,MF_BYPOSITION,&m_bitmap,&m_bitmap);

当我们运行的时候我们会发现,不是我们想要的位图,这是什么原因呢? 原因就是我们自己做的位图太大了。

这时候我们需要用一个函数去获取菜单标记的大小。

int GetSystemMetrics(
  int
nIndex   // system metric or configuration setting to retrieve
);

获取系统的一些信息的度量。

SM_CXMENUCHECK,
SM_CYMENUCHECK

这两个参数,是缺省标记位图的大小。

我们先定义一个CString的对象,输出显示出来

CString str;
str.Format("x=%d,y=%d",GetSystemMetrics(SM_CXMENUCHECK),GetSystemMetrics(SM_CYMENUCHECK));
MessageBox(str);

Format成员函数,可以把内容按照一定的格式,格式化到CString的对象当中。这里我们通过上边的函数,获取到了位图标记的大小。

 

屏蔽菜单项:

我们可以利用CMenu的一个函数

UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );

例:

GetMenu()->GetSubMenu(0)->EnableMenuItem(ID_FILE_OPEN,MF_BYCOMMAND|MF_DISABLED|MF_GRAYED);

//

GetMenu* mmenu=GetMenu();

GetSubMenu* submenu=mmenu->GetSubMenu(0);
submenu->EnableMenuItem(ID_FILE_OPEN,MF_BYCOMMAND|MF_DISABLED|MF_GRAYED);

当我们运行之后,会发现我们想要的效果并没有实现。

MFC给我们提供了一个命令更新的机制,所以我们的CMenu就不起作用了。

如果想要自己控制菜单项的使用不适用,就在CMainFrame的构造函数中将m_bAutoMenuEnable设置为FALSE。

 

如何将整个菜单取消掉:

我们用这样一个函数

BOOL SetMenu( CMenu* pMenu );

参数如果是NULL,程序中菜单被移走。

例:

SetMenu(NULL);
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);

上边的代码,我们把菜单移走,然后又重新加载显示了菜单。

在这里,CMenu对象是局部的对象,会导致程序出问题。

这个时候需要把CMenu设置为一个成员变量,或者利用 Detach这样一个函数。menu.Detach();

命令更新机制:

菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI这个消息,谁捕获了CN_UPDATE_COMMAND_UI这个消息,MFC就在其中创建一个CCmdUI对象。我们可以手工或者利用ClassWizard在消息映射中添加ON_UPDATE_COMMAND_UI宏来捕获CN_UPDATE_COMMAND_UI消息。

 

在我们的程序中,我们想要让某个菜单项可用或者不可用。我们可以在ClassWizard当中选择这个菜单项,在Messages:中选择UPDATE_COMMAND_UI增加一个函数,编辑代码。它会在消息映射中给我们增加一个ON_UPDATE_COMMAND_UI宏,这个宏就是用来处理CN_UPDATE_COMMAND_UI这个消息的。我们需要用命令跟新的函数进行处理。

CCmdUI这个类中提供了许多函数,一个菜单项是否可以使用,是否有标记,改变命令UI接口的文本。UI(user-interface)即用户接口。

 

在后台所做的工作是:操作系统发出WM_INITMENUPOPUP消息,然后MFC的基类,如CFrameWnd接管。它创建一个CCmdUI对象,并与第一个菜单相关联,调用对象的一个函数DoUpdate();这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针,当我们对于第一个菜单项更新完成以后,我们用CN_UPDATE_COMMAND_UI进行处理,有没有这样的一个宏对消息进行处理。如果有,就会执行相应的命令响应函数,我们可以用CCmdUI的指针调用SetCheck让他可以使用。更新完成只有,同一个CCmdUI对象就设置为与第二个菜单项相关联,这样的顺序进行,知道完成所有菜单项。

    更新命令UI处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项。

例: 

virtual void Enable( BOOL bOn = TRUE );

这个函数用来使菜单项可用。

CCmdUI有很多的成员变量,可以获取菜单的 索引   ID号等。

 

在做程序的时候,我们经常用到右键弹出菜单的功能。这些VC++都给我们做好了,我们点击工程(Project)->添加一个工程(Add To Project)->(Comporents and Controls)  我们选择Visual C++ Components文件中的

Pop-up Menu 点击插入, 插入到我们的CMenuView中。

 

BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );

这个函数可以显示弹出菜单。 我们看一下这个函数。第一个参数,菜单的弹出位置,例如鼠标的左边,右边,还是中间? x,y 是鼠标点击当前点的坐标。第四个参数,标识拥有这个菜单的窗口,最后一个缺省值,指示了一个矩形区域,设定我们点击的时候,在区域之外点击,菜单消失。

例:

我们需要把客户区坐标转换成屏幕坐标。

void ClientToScreen( LPPOINT lpPoint ) const;

可以完成客户区坐标与屏幕坐标的一个转换。

 

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,this);

 
 CView::OnRButtonDown(nFlags, point);
}

下面我们对菜单里的一个菜单项进行命令捕获。

发现,由于我们的指针为this,即指向View类的指针,所以框架类MainFrame是不能对这个消息进行相应。

对于一个子窗口来说,他有最先响应消息的权限。

 

动态的添加删除 响应一个菜单。

 

我们可以在CMainFrame OnCreate函数中添加菜单项。动态添加菜单,我们可以用CMenu的一个成员函数。

BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );

第一个参数可以去多个值,有参数列表。第二个参数指示了新的菜单项的命令ID,如果是弹出菜单,它就被设置成一个菜单句柄,我们菜单的句柄如何去获取,所有的资源相关的类,内部都有一个成员变量,保存了和他相关的句柄,当然我们的菜单资源也有一个成员变量,保存于他相关的菜单资源的句柄。。如果是分隔栏,那么它就忽略了。第三个参数,它指示菜单项的名称。

 

我们先创建一个弹出菜单。

BOOL CreatePopupMenu( );

 

例:

我们定义一个CMenu。

CMenu menu;
menu.CreatePopupMenu();

GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"SB"); //这个地方我们赋了一个句柄,但是他需要一个UINT这样的一个类型,我们需要在前边做一个强制转换。

//GetMenu()在这里的作用: 要AppendMenu 我们需要获取当前和我们这个框架装口相关联菜单栏的指针。

menu.Detach(); //因为我们在这里定义的是一个局部变量 CMenu,所以会发生错误,所以我们需要把它定义为成员变量,或者利用Detach这个函数,将句柄和我们的CMenu这个对象断开。

 

如果说我们要插入一个菜单:

    我们可以利用CMenu的一个成员函数:

BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );

第一个参数指示了菜单的菜单项,ID号或者索引号。 其他的参数跟我们AppendMenu是一样的。

例:

menu.CreatePopupMenu();
GetMenu()->InsertMenu(2,MF_BYPOSITION|MF_POPUP,(UINT)menu.m_hMenu,"SB");
menu.Detach();

 

如果我们想在弹出菜单中添加菜单项,同样可以采用AppendMenu  或者 InsertMenu

例:

menu.AppendMenu(MF_STRING,111,"NO SB!");
menu.AppendMenu(MF_STRING,222,"SB OK?");

GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,333,"你怎么这么SB");
GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN,MF_BYCOMMAND|MF_STRING,444,"SB透了");

 

删除子菜单:

BOOL DeleteMenu( UINT nPosition, UINT nFlags );

例:

GetMenu()->DeleteMenu(1,MF_BYPOSITION);

删除菜单项。例子:

GetMenu()->GetSubMenu(0)->DeleteMenu(2,MF_BYPOSITION);

 

对动态插入的菜单进行命令响应:

我们在文件 FileView 中有个Header Files

Resource.h定义了资源的ID。我们可以自己添加一个资源ID

例:#define IDM_SB      111

1.首先在头文件中,做命令响应函数的原型。

例:

//{{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 void OnSB(); //这里是我们自已定义的命令响应函数的原型
 //}}AFX_MSG

2.接下来,消息映射,对于命令消息,我们通过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()
 ON_COMMAND(IDM_SB,OnSB) //我们自己定义的消息响应。
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

3.最后函数。命令响应函数的实现。

例:

void CMainFrame::OnSB()
{
}

 

接下来,我们做一个电话本的实例:

现在我们要把我们输入的内容显示出来。

当我们CMainFrame的这个窗口和菜单创建成功之后,当我们再次去修改这个菜单的时候,我们需要让这个菜单栏重绘。

void DrawMenuBar( );

我们用上边这个函数来重画。

如果菜单栏Windows已经创建窗口之后,被改变,我们用这个函数来重画改变菜单栏。

例:

m_menu.CreatePopupMenu(); //创建一个空的菜单
//获得菜单 由于菜单栏在框架类中,我们想要获取到框架类的菜单
GetParent()->GetMenu()->AppendMenu(MF_POPUP,(UINT)m_menu.m_hMenu,"PhoneBook");
GetParent()->DrawMenuBar(); //因为DrawMenuBar是在View类中调用的,而菜单栏在CMainFrame中而不再View类中,所以调用DrawMenuBar就不起作用了,所以我们要用父窗口的指针来调用这个函数。

 

使窗体重绘的函数

void Invalidate( BOOL bErase = TRUE );

CString类中的Find函数。

int Find( TCHAR ch ) const;

int Find( LPCTSTR lpszSub ) const;

int Find( TCHAR ch, int nStart ) const;

int Find( LPCTSTR pstr, int nStart ) const;

 

 

例:

void CMenu2View::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(0==++m_nIndex) //判断是否是第一次接收数据。
  {
   m_menu.CreatePopupMenu();
   //获得菜单由于菜单栏在框架类中,我们想要获取到框架类的菜单
   GetParent()->GetMenu()->AppendMenu(MF_POPUP,(UINT)m_menu.m_hMenu,"PhoneBook");
   GetParent()->DrawMenuBar(); //我们用这个函数来重画。如果菜单栏Windows已经创建窗口之后,被改变,

                                 我们用这个函数来重画改变菜单栏。(用到这个函数的原因是,当我们输入

                                 数据的时候,产生一个新的菜单,但是这个新的菜单没有马上显示出来。)


  }
  m_menu.AppendMenu(MF_STRING,IDM_PHONE1+m_nIndex,m_strLine.Left(m_strLine.Find(' ')));
  m_strArray.Add(m_strLine); //CStringArray集合类 Add是添加一个成员 GetAt()通过索引取出一个成员。
  m_strLine.Empty(); //字符串清空函数。
  Invalidate(true); //让整个客户端区域变为无效,当一个窗口区域无效的话,下一个WM_PAINT的消息被

                      发送,客户端就会更新。当参数为true背景会被擦除。
 }
 else
 {
  m_strLine+=nChar;
  dc.TextOut(0,0,m_strLine);
 }
 CView::OnChar(nChar, nRepCnt, nFlags);
}

 

C++的类去掉C就是名字。如CMenu2View,名字就是Menu2View。而CMainFrame是个例外。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值