孙鑫VC++讲座笔记Lesson 6 菜单编程

时间:2022-02-01 21:16:20

MFC自动生成了一个菜单,如果要添加自己的菜单,VC++提供的资源编辑器提供给了我们一种所见即所得的编辑方式,在资源编辑器中添加自己的菜单,在程序运行时就能看到

一、 MFC对菜单命令的响应路由

在“帮助”菜单后面添加一个菜单Test,它的ID号是灰的,为什么?

取消popup属性,就可以让test菜单响应命令消息。

1、在CMainFrame中增加命令响应函数。

       MessageBox弹出一个消息框,因为CMainFrame是从CWnd派生来的,因此继承了CWndMessageBox()函数,是CWnd封装的一个成员函数。后面几个参数有缺省值,所以只要对第一个参数赋值就可以了。

void CMainFrame::OnShow()

{

       // TODO: Add your command handler code here

       MessageBox("MainFrame Clicked!");

}

问题:是不是test菜单项只能由CMainFrmae这个类来捕获呢?

CMenuAppCMenuDocCMenuview中分别增加对IDM_TEST的命令响应函数。现在一个菜单项有了4个命令响应函数,应该由哪一个函数最先对菜单命令进行响应呢?是不是当点击菜单时,4个响应函数都会做出响应呢?

void CMenuApp::OnTest()

{

       // TODO: Add your command handler code here

       AfxMessageBox("CMenuApp Clicked!");// CMenuApp不是从Cwnd 继承而来,没有MessageBox函数。

}

void CMenuDoc::OnTest()

{

       // TODO: Add your command handler code here

       MessageBox("CMenuDoc Clicked!");//

}

void CMenuview::OnTest()

{

       // TODO: Add your command handler code here

       MessageBox("CMenuview Clicked!");//

}

运行,发现最先是view 类对菜单进行了响应,

去掉view类中的OnTest()函数(类视图中,右键delete),再运行,发现此时Doc进行响应。

删除Doc类中的OnTest()函数,再运行,发现此时CMainFrame进行响应

删除CMainFrame类中的OnTest()函数,再运行,发现此时CMenuApp进行响应.

 

刚才我们看到,对于一个菜单项,在这4个类中同时增加命令响应函数的时候,它们之间命令消息传递的顺序,是先view doc CMainFrmae CMenuApp

 

 

刚才我们一再说这个消息是一个命令消息,消息分为三类,

n       标准消息

      WM_COMMAND之外,所有以WM_开头的消息。

      CWnd派生的类,都可以接收到这类消息。

n       命令消息

      来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。

       CCmdTarget派生的类,都可以接收到这类消息。

n       通告消息

      由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。

       CCmdTarget派生的类,都可以接收到这类消息。

 

 

命令消息的路由

 

 

 

 

 

二、 如何创建标记菜单、缺省菜单、图形标记菜单、禁用菜单、设置菜单、取消菜单

如果要在 文件->新建 菜单项上加一个标记,可以在OnCreate()函数中去完成,在窗口创建完成之后,去创建一个标记菜单,

首先需要对菜单的结构有一个了解。

慨念:通常所说的子菜单到底是“文件”还是“文件”菜单下面的诸如“新建”、“打开”?

菜单和房屋楼层的对应关系

整个一幢楼对应菜单栏,每一层楼对应菜单栏上的每一个子菜单,房间对应每一个菜单项。

“文件”这一个部分就是一个子菜单。“编辑”这一部分又是一个子菜单

如果要对菜单进行编程,就好象在找一个房间一样,首先需要找到整幢楼,接下来找到楼层,最后找到房间。

如果要对“新建”菜单项加上一个标记,需要首先找到菜单栏,然后找到子菜单,最后找到“新建”菜单项

对于房间或楼层,可以按照索引来访问,还可以通过菜单项的标识进行访问。

1、  获取菜单栏对象的一个指针

CWnd::GetMenu

Retrieves a pointer to the menu for this window.

CMenu* GetMenu( ) const;

Return Value

Identifies the menu. The value is NULL if CWnd has no menu. The return value is undefined if CWnd is a child window.

2、  获取子菜单

CMenu::GetSubMenu

Retrieves the CMenu object of  a pop-up menu.

 

CMenu* GetSubMenu(

   int nPos

) const;

Parameters

nPos

Specifies the position of the pop-up menu contained in the menu. Position values start at 0 for the first menu item. The pop-up menu's identifier cannot be used in this function.(只能用索引,不能用标识)

 

注意:GetMenu() GetSubMenu()返回的都是CMenu对象的指针,但不一样,当调用CWnd:: GetMenu()时,返回的是指向整个菜单栏的指针,GetSubMenu()返回的是一个指向子菜单的指针,

当获取到了这个子菜单,相当于找到了楼层,当然接下来就要对房间进行操作,

3、  标记菜单的实现

CMenu::CheckMenuItem

Adds check marks to or removes check marks from menu items in the pop-up menu.

 

UINT CheckMenuItem(

   UINT nIDCheckItem,

   UINT nCheck // MF_BYCOMMAND   MF_BYPOSITION   

                          //MF_CHECKED   MF_UNCHECKED   组合

);

 

 

 

代码:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

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

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

     return 0;

}

4、  缺省菜单的实现(粗体方式显示的菜单项)

CMenu::SetDefaultItem

Sets the default menu item for the specified menu.

 

BOOL SetDefaultItem(

   UINT uItem,//菜单项的标识或位置,由第二个参数来决定

   BOOL fByPos = FALSE

);

Parameters

uItem

Identifier or position of the new default menu item or - 1 for no default item. The meaning of this parameter depends on the value of fByPos.

fByPos

Value specifying the meaning of uItem. If this parameter is FALSE, uItem is a menu item identifier. Otherwise, it is a menu item position.

 

代码:

GetMenu()->GetSubMenu(0)->SetDefaultItem(1,TRUE);//第一个参数为索引

GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN);//第一个参数为命令ID

GetMenu()->GetSubMenu(0)->SetDefaultItem(5,TRUE);// //第一个参数为索引

 

把“打印”设置为一个缺省菜单项,

GetMenu()->GetSubMenu(0)->SetDefaultItem(4,TRUE);

打印没有变为粗体显示,为什么?它的索引为4吗?可以有两个缺省菜单吗?

分析:索引计算错误码,因为分隔栏也要参加计算索引,在计算索引时不要把分隔栏忽略掉。一个子菜单中只能有一个缺省菜单,要不然怎么叫做缺省菜单呢!

 

 

 

5、  图形标记菜单的实现

CMenu::SetMenuItemBitmaps

Associates the specified bitmaps with a menu item.

 

BOOL SetMenuItemBitmaps(

   UINT nPosition,

   UINT nFlags,

   const CBitmap* pBmpUnchecked,

   const CBitmap* pBmpChecked

);

Parameters

nPosition

Specifies the menu item to be changed. The nFlags parameter can be used to interpret nPosition in the following ways:

MF_BYCOMMAND

Specifies that the parameter gives the command ID of the existing menu item. This is the default if neither MF_BYCOMMAND nor MF_BYPOSITION is set.

MF_BYPOSITION

Specifies that the parameter gives the position of the existing menu item. The first item is at position 0.

nFlags

Specifies how nPosition is interpreted.

pBmpUnchecked

Specifies the bitmap to use for menu items that are not checked.

pBmpChecked

Specifies the bitmap to use for menu items that are checked

用资源编辑器做一幅位图,构造一个CBitmap对象,用Load方法加载位图,(要显示一幅位图,如果这个对象定义为一个局部变量,当OnCreate()执行完成之后,这个对象的生命周期到了,发生析构,于是就看不见图标记。所以要把CBitmap对象定义为CMainFrame的成员变量。)

private:

CBitmap m_bitmap;

OnCreate()

{

m_bitmap.LoadBitmap(IDB_BITMAP1);

GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0,MF_BYPOSITION,&m_bitmap,&m_bitmap);

}

运行:没有看见图形标记,

分析:位图大于标记图形的大小,对于一个图形标记菜单,它的位图尺寸是有限制的(13*13)可以通过一个API函数去获取图形标记的位图大小。

 

 

GetSystemMetrics

The GetSystemMetrics function retrieves various system metrics (widths and heights of display elements) and system configuration settings. All dimensions retrieved by GetSystemMetrics are in pixels.

 

int GetSystemMetrics(

  int nIndex //SM_CXMENUCHECK, SM_CYMENUCHECK

);

 

代码:

CString str;

     str.Format("x=%d,y=%d",GetSystemMetrics(SM_CXMENUCHECK),

                   GetSystemMetrics(SM_CYMENUCHECK));

     MessageBox(str);

改进:调整位图大小为13*13,观察右下角指示器。

6、  让菜单项不能使用的实现

CMenu::EnableMenuItem

Enables, disables, or dims a menu item.

 

UINT EnableMenuItem(

   UINT nIDEnableItem,//几个值的组合MF_BYPOSITION | MF_DISABLED | MF_GRAYED

   UINT nEnable

);

Parameters

nIDEnableItem

Specifies the menu item to be enabled, as determined by nEnable. This parameter can specify pop-up menu items as well as standard menu items.

nEnable

Specifies the action to take. It can be a combination of MF_DISABLED, MF_ENABLED, or MF_GRAYED, with MF_BYCOMMAND or MF_BYPOSITION. These values can be combined by using the bitwise OR operator. These values have the following meanings:

MF_BYCOMMAND   Specifies that the parameter gives the command ID of the existing menu item. This is the default.

MF_BYPOSITION   Specifies that the parameter gives the position of the existing menu item. The first item is at position 0.

MF_DISABLED   Disables the menu item so that it cannot be selected but does not dim it.

MF_ENABLED   Enables the menu item so that it can be selected and restores it from its dimmed state.

MF_GRAYED   Disables the menu item so that it cannot be selected and dims it.

Return Value

Previous state (MF_DISABLED, MF_ENABLED, or MF_GRAYED) or –1 if not valid.

代码:(通常将MF_DISABLED MF_GRAYED放在一起使用,在不使用的同时变灰)

GetMenu()->GetSubMenu(0)->EnableMenuItem(1,MF_BYPOSITION | MF_DISABLED | MF_GRAYED);

发现:打开并没有被屏蔽,

MSDN

NOTE: m_bAutoMenuEnable is set to FALSE in the constructor of CMainFrame so no ON_UPDATE_COMMAND_UI or ON_COMMAND handlers are  needed, and CMenu::EnableMenuItem() will work as expected

分析:m_bAutoMenuEnable 要被设置为FALSE,在CMainFrame的构造函数中。在MFC当中,当一个菜单它能够使用或不能使用,MFC给我们提供了一种命令更新的机制,所有菜单项的更新都是由这种机制来完成的,所以EnableMenuItem 就不起作用,如果你想要自己去控制这个菜单项的使用和不使用,就要将这个变量放到CMainFrame的构造函数中,将它设置为FALSE

CMainFrame的构造函数中,将它设置为FALSE,运行正常。

当把bAutoMenuEnable 置为FALSE后,发现,编辑菜单下面的几个菜单都没有变灰了,就是因为bAutoMenuEnable 被为FALSE的原因。因为这个时候MFC就不会用它的命令更新机制去判断哪一个菜单项可以被使用,哪一个不能被使用了。所有的操作都要由我们自己去完成了。MFC的命令更新机制就不起作用了。

7、  如何将整个菜单取消掉

CWnd::SetMenu

Sets the current menu to the specified menu.

 

BOOL SetMenu(

   CMenu* pMenu

);

Parameters

pMenu

Identifies the new menu. If this parameter is NULL, the current menu is removed.

代码:SetMenu(NULL)

8、  再把取消的这个菜单设置上去。

CMenu menu;

       menu.LoadMenu(IDR_MAINFRAME);

       SetMenu(&menu);

       menu.Detach();

注意:文件->打印预览-<关闭,出现非法操作,在这里定义了一个局部对象menu,可以将menu定义为CMainFrame的成员变量,另一种方式,CMenu类的说明:

MSDN

The call to Detach detaches the HMENU from the CMenu object, so that when the local CMenu variable passes out of scope, the CMenu object destructor does not attempt to destroy a menu it no longer owns. The menu itself is automatically destroyed when the window is destroyed.

Detach的调用将HMENU句柄从CMenu对象中断开,当一个本地的CMenu对象生命周期到了的时候,析构并不会试图销毁这个菜单,menu本身会自动销毁,当窗口销毁时。

三、 MFC的命令更新机制(114分钟)

1、当用户点击“文件”子菜单,(点击其它子菜单的过程与此类似)即将要显示这个子菜单时,操作系统发出一个WM_INITMENUPOPUP ,这条消息由CFrameWnd:: OnInitMenuPopup(CMenu* pMenu, UINT nIndex, BOOL bSysMenu)捕获,

2、在这个函数中,先构造了一个CCmdUI对象state,第一个菜单项的ID被赋给CCmdUI对象(state)的一个成员变量()

CFrameWnd:: OnInitMenuPopup(CMenu* pMenu, UINT nIndex, BOOL bSysMenu)

{

CCmdUI state;

state.m_pMenu = pMenu;

}

3、然后循环调用state.DoUpdate()函数,直到这个子菜单中的所有菜单项全部被遍历。

state.m_nIndexMax = pMenu->GetMenuItemCount();

       for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;

         state.m_nIndex++)

{

state.m_nID = pMenu->GetMenuItemID(state.m_nIndex);//指向下一个菜单项

……..    

state.m_pSubMenu = NULL;

       state.DoUpdate(this, m_bAutoMenuEnable && state.m_nID < 0xF000);//this是主框架对象的指针,用于让命令路由从主框架类开始,参见DoUpdate()函数。

……..

}

4DoUpdate()函数发送消息CN_UPDATE_COMMAND_UI

BOOL CCmdUI::DoUpdate(CCmdTarget* pTarget, BOOL bDisableIfNoHndler)

{

…….

pTarget->OnCmdMsg(m_nID, CN_UPDATE_COMMAND_UI, this, NULL); //pTarget为主框架对象的指针,即第3步中的this指针,现在的this指针是state对象的指针。

       ……..

       return bResult;

}

5、消息被用户定义的处理函数接受并处理。

void CMainFrame::OnUpdateFileNew(CCmdUI *pCmdUI)//pCmdUI即第4步中的this指针

{

     // TODO: 在此添加命令更新用户界面处理程序代码

     pCmdUI->Enable(FALSE);

}

注意:pCmdUI对象的m_nID成员变量保存了与它相关的菜单项的IDm_nIndex保存了与它相关的菜单项的索引。所以存在这两种写法:

void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)

{

     if(ID_EDIT_CUT==pCmdUI->m_nID)//通过ID号来比较

     pCmdUI->Enable();

}

void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)

{

     if(2==pCmdUI->m_nIndex)//通过索引号来比较,但这种方法可能会让工具栏按钮无法更新,为什么?

因为工具栏按钮的索引不一定与菜单项的索引一致,为什么?

菜单项的索引从第一个菜单项开始,工具栏按钮的索引从工具栏最左边开始,有可能两者的索引不一致。

     pCmdUI->Enable();

}

 

四、 右键弹出菜单

Project->add to project->components and controls->vc++ components ->popupmenu

当询问增加弹出菜单到哪一个类中时,不要选择CMainFrame类,为什么?

在工程中增加了一个菜单资源。

View中增加了一个消息映身和一个OnContext()函数,

BEGIN_MESSAGE_MAP(CMenu2View, CView)

     ON_WM_CONTEXTMENU()

END_MESSAGE_MAP()

OnContext()函数内部调用了TrackPopupMenu()函数,显示一个弹出菜单。

关于TrackPopupMenu()函数

Displays a floating pop-up menu at the specified location and tracks the selection of items on the pop-up menu.

 

BOOL TrackPopupMenu(

   UINT nFlags,

   int x,

   int y,

   CWnd* pWnd,

   LPCRECT lpRect = 0

);

4个参数如果为NULL,在弹出菜单之外点击里,弹出菜单消失,之内点击里,弹出菜单不消失。

手动生成一个右键弹出菜单

1、  在资源编辑中增加一个菜单

注意:popup*项是不会出现的,所以任意起一个名称都是可以的。

2、  View类增加鼠标右键消息处理函数

void CMenu2View::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);

     pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,this);

     CView::OnRButtonDown(nFlags, point);

}

发现菜单的显示位置不太对,为什么?x,y参数是以屏幕坐标(屏幕的左上角)指示的显示的位置,而鼠标右键点击的位置是客户区坐标。是以视图窗口的左上角为原点的一个坐标。坐标体系不一致,所以需要把客户坐标转换为屏幕坐标。

CWnd::ClientToScreen ClientToScreen

Converts the client coordinates of a given point or rectangle on the display to screen coordinates.

 

void ClientToScreen(

   LPPOINT lpPoint

) const;

void ClientToScreen(

   LPRECT lpRect

) const;

加入坐标转换代码:

CClientToScreen(&point);

3、  对弹出菜单中的命令进行响应

4、  利用分别向导分别在CMainFrame类和CMenuView类中增加命令响应函数

void CMainFrame::OnShow()

{

       // TODO: Add your command handler code here

       MessageBox("MainFrame Show!");

}

void CMenu2View::OnShow()

{

       // TODO: Add your command handler code here

       MessageBox("View Show!");

}

 

运行:发现View类进行了响应,删除View类中的响应函数。再运行,发现CMainFrame中的响应函数未进行响应,为什么?

分析:是因为在调用TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,this)函数时,指定的拥有者窗口是thisCMenuView为拥有者。因此只能是view类对弹出菜单中的命令进行响应。如果想要让框架类也能对弹出菜单中的命令进行响应,就需要在TrackPopupMenu()中指定父窗口的指针为框架类的指针。

 

如果此时再在View中增加一个响应函数,看看会不会被调用。结果:会被调用。

 

五、 动态生成菜单

1、动态追加菜单(在OnCreate()函数中进行。)

CMenu::CreatePopupMenu()

 Creates an empty pop-up menu and attaches it to a CMenu object.

这个函数取代在资源编辑器中手动创建菜单资源

CMenu::AppendMenu

AppendMenu

Appends a new item to the end of a menu.

 

BOOL AppendMenu(

   UINT nFlags,//当选择MF_POPUP时,增加一个弹出菜单

   UINT_PTR nIDNewItem = 0,//命令ID,如果第一个参数选择MF_POPUP时,它就被设置为一个菜单句柄。如果为MF_SEPARTOR,则这个参数被忽略。

   LPCTSTR lpszNewItem = NULL //菜单的名称

代码:

CMenu menu;

       menu.CreatePopupMenu();

       GetMenu()->AppendMenu(MF_POPUP,(UINT)menu.m_hMenu,"WinSun");

       Menu.Detach();//将句柄和CMenu对象断开。

注意:第一个参数被设置为MF_POPUP,则第二个参数需要设置为菜单的句柄,因所有与资源相关的类,在它的内部都有一个成员变量,保存了和这个对象相关联的资源的句柄。对于菜单对象,这个成员变量是m_hMenu

2、动态插入菜单

 

代码:

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

3、增加菜单项

menu.AppendMenu(MF_STRING,IDM_HELLO,"Hello");

menu.AppendMenu(MF_STRING,112,"Weixin");

menu.AppendMenu(MF_STRING,113,"Mybole");

在“文件”子菜单中添加一个菜单项,

GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING,114,"Welcome");

在打开和保存之间添加一个菜单项(注意InsertMenu的用法)

GetMenu()->GetSubMenu(0)->InsertMenu(ID_FILE_OPEN,

                     MF_BYCOMMAND | MF_STRING,115,"维新");

4、删除“编辑”子菜单或菜单项

GetMenu()->DeleteMenu(1,MF_BYPOSITION);//如果用菜单栏的指针去调用deletemenu()则删除的是一个子菜单,如果用一个菜单项的指针去调用deletemenu(),则删除的是一个菜单项。

GetMenu()->GetSubMenu(0)->DeleteMenu(2,MF_BYPOSITION);//删除一个菜单项

5、如何对插入的菜单项进行命令响应,

对于先前的静态生成的菜单,可以利用向导进行命令的捕获,而现在是动态生成的菜单,

方法:在resourch.h中定义一个资源ID 如:#define IDM_HELLO 111

在第3步中改111IDM_HELLO,在头文件中声明函数原型,进行消息映射,实现消息映射函数。

void CMainFrame::OnHello()

{

     MessageBox("Hello!");

}

六、 电话本程序

功能描述:当输入人名、空格、电话号码、回车,在帮助后面动态生成一个菜单。把人名做为动态菜单中的一个菜单项添加进去。再次输入一个人名、空格、电话号码、回车,接着把人名做为菜单项添加。当点击菜单项里又把人名和电话号码显示出来。

分析:实现在键盘上输入时,在屏幕上显示出来。

V iew中捕获WM_CHAR消息,在处理消息时,需要判断一下,在增加动态菜单时,不能每次回车都去增加一个动态菜单,只有当第一次输入时,需要增加一个弹出菜单,以后再输入时,只是往弹出菜单中增加菜单项,为了更好地判断是否是第一次回车,可在view中增加一个成员变量m_nIndxe,构造函数中初始化为-1

整个分为两种情况考虑,第一种情况是按下了回车键,第二种情况为按的是非回车键,

第一种情况又分两种情况:

当用户是第一次按回车键时,需要增一个popup菜单以及一个菜单项

当用户是不是第一次按回车键时,只需要一个菜单项

第二种情况:

              说明用户按的是其它子母键,这时需要把按的子母输出到屏幕上

要实现点击菜单项,在客户区中显示相应的电话号码。需要定义一个动态数组存储输入的人名和电话。每一条人名和电话号码为数组中的一条记录。

 

CStringArray Members

Add        Adds an element to the end of the array; grows the array if necessary.

GetAt      Returns the value at a given index.

伪代码:

       If(回车)

              If (第一次回车)

                     增加一个popup菜单

增加一个菜单项

              Else(不是第一次回车)

增加一个菜单项

              End if

       Else //字母键

              输出字母在屏幕

       endif

 

 

代码:

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)//初始为-1,先自加再比较,为真表示第一次回车,需要添加popup菜单,并添加菜单项,

           {

                  m_menu.CreatePopupMenu();

                  GetParent()->GetMenu()->AppendMenu(MF_POPUP,

                                (UINT)m_menu.m_hMenu,"PhoneBook");

                  GetParent()->DrawMenuBar();//

           }

           m_menu.AppendMenu(MF_STRING,IDM_PHONE1+m_nIndex,m_strLine.Left(m_strLine.Find(' ')));//不是第一次,则只需要添加菜单项,注意菜单项ID的巧妙实现(粗体部分)

           m_strArray.Add(m_strLine);

           m_strLine.Empty();

           Invalidate();

          

    }

    Else//如果按的不是回车键

    {

           m_strLine+=nChar;

           dc.TextOut(0,0,m_strLine);

    }

    CView::OnChar(nChar, nRepCnt, nFlags);

}

注意:当窗口尺寸变化的时候,才出现这个新增的弹出菜单,为什么?

因为之前当我们在OnCreate()中修改了菜单,它立即就会改变反映了我们的修改,当窗口创建完全成功之后,当我们再次去修改菜单时,它是不支立即发生改变的,需要手动地让菜单栏进行重绘,

CWnd::DrawMenuBar

Redraws the menu bar.

void DrawMenuBar( );

Remarks

If a menu bar is changed after Windows has created the window, call this function to draw the changed menu bar.

应通过父窗口指针去调用DrawMenuBar ,因为直接调用DrawMenuBar 是调用了view类的DrawMenuBar ,而View 没有菜单栏。

再次输入时,如果想把先前的内容擦除掉,可以利用窗口的重绘来完成,如何让窗口重绘呢?

问题:

 

调用Invalidate(TRUE) 可以让窗口无效,这样当下一个WM_PAINT发送的时候,窗口的背景将会被擦除掉。 达到擦除先前输入的文本的目的。

 

对动态添加的菜单项进行命令响应:

    技巧:在菜单资源编辑器中添加一个菜单,并利用向导对这个菜单中的命令进行响应,然后,删除这个资源。(Resource.h中添加的这几个资源定义不要被删除),这样,定义的命令响应函数仍然存在。但要消息映射中先前由向导生成的映射从注释宏中移出来。为什么?因为是向导添加的。当向导发现菜单资源已经被删除了,向导就会删除这些消息映射项。

代码:

void CMenu2View::OnPhone1()

{

    // TODO: Add your command handler code here

    CClientDC dc(this);

    dc.TextOut(0,0,m_strArray.GetAt(0));

}

 

void CMenu2View::OnPhone2()

{

    // TODO: Add your command handler code here

    CClientDC dc(this);

    dc.TextOut(0,0,m_strArray.GetAt(1));

}

 

void CMenu2View::OnPhone3()

{

    // TODO: Add your command handler code here

    CClientDC dc(this);

    dc.TextOut(0,0,m_strArray.GetAt(2));

}

 

void CMenu2View::OnPhone4()

{

    // TODO: Add your command handler code here

    CClientDC dc(this);

    dc.TextOut(0,0,m_strArray.GetAt(3));

}

 

七、 如何让菜单的响应由框架类来捕获

正常情况下,可以删除 View类中的命令响应,在框架类中定义新的命令响应。来实现让框架类响应命令,但现在需要在不删除View 类中的命令响应的情况,让框架类来捕获。

分析:

添加菜单的响应由框架类来捕获,也就是说框架类首先对这个命令进行响应,而不是由view类来响应,但是根据之前给大家介绍的菜单命令消息的路由,框架类首先要将命令消息交给它的字窗口View类来进行先响应,那么我们如何才能让框架类它首先捕获对这个菜单命令消息进行的响应,根据之前的powerpoint,我们可以看到,命令消息是到OnCommand这个函数的时候,然后来完成路由的,如何OnCommand是一个虚函数,我们就可在OnCommand函数当中去截获本该交由View类首先进行响应的命令消息。

如何实现:

重载OnCommand()函数

 

 

 

MSDN

 

CWnd::OnCommand 

virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );

 

Return Value

 

An application returns nonzero if it processes this message; otherwise 0.

 

Remarks

 

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.

 

代码:

BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam)

{

       // TODO: Add your specialized code here and/or call the base class

       int MenuCmdId=LOWORD(wParam);//LOWORD取低字节即命令ID

       CMenu2View *pView=(CMenu2View*)GetActiveView();

       if(MenuCmdId>=IDM_PHONE1 && MenuCmdId<IDM_PHONE1+pView->m_strArray.GetSize())

       {

              CClientDC dc(pView);

              dc.TextOut(0,0,pView->m_strArray.GetAt(MenuCmdId-IDM_PHONE1));

              //MessageBox("Test");

              return TRUE;

       }

       return CFrameWnd::OnCommand(wParam, lParam);

}

 

遗失分号的错误分析:

CMainFrame::OnCommand 中使用了CMenu2View对象,需要在CMainFrame.cpp文件中包含Menu2View.h头文件,包含之后会出现一个这样的错误,在CMenu2View.h中出现要求在

public:

    CMenu2Doc* GetDocument();

之前加一个分号的提示。

分析:显然这个问题不是加一个分号就能解决的。而是说CMenu2Doc这个类标识符编译器不认识,为什么在Menu2View.h中已包含了Menu2Doc.h的头文件,还会出现不认识的错误呢?

原因:只有源文件参与编译,头文件不参与编译,当在编译MainFrm.cpp时,它展开Menu2View.h头文件,发现有一个CMenu2Doc* GetDocument();当然它不认识这个CMenu2Doc,所以出错。

解决方法:把Menu2View.cpp 文件中的#include "Menu2Doc.h"剪切到Menu2View.h

                  也可以在MainFrm.cpp中再包含一个Menu2Doc.h