📄 06.5.3 添加菜单项及其命令晌应函数.txt
字号:
6.5.3 添加菜单项及其命令晌应函数
接下来,需要实现在输入人名、空格、电话号码,并当按下回车键后,把输入的人名作为菜单项的文本添加到PhoneBook子菜单下这一功能。
因为我们把当前输入的内容全部保存到m s位Line这个变量中,并且人名和电话号码之间是以空格分隔的,所以,首先需要从m strLine变量中分离出人名字符串。
CString类提供了一个 Find成员函数,这个函数在字符串中可以查找一个字符,或者一个字符串,返回匹配结果的第一个字符在该字符串中的位置索引。
例如利用Find函数在字符串 "Hello"中查找字符:"l",将得到"Hello"这个字符串中第一个")"字符出现的位置索引: 2。这里需要提醒读者的是:在C/C++语言中,字符串的索引是从O开始计数的。因此,这里我们可以在m s位Line中查找空格字符,得到它的位置索引,然后利用前面我们已经介绍的CString类的另一个成员函数: Left 把人名字符串截取出来,并将该字符串作为菜单项名称添加到PhoneBook子菜单下。具体的实现代码如例6-37所示,其中加灰显示的代码是新添的。
例6-37
void CMenu2View : : OnChar(UINT nChar , UINT nRepCnt , U工NT nFlags)
{ // TODO : Add your message handler code here and/ or call default CClientDC dc(th工s ) ; if(OxOd nChar)
{
if (0 ++m_n工ndex )
m_Menu. CreatePopupMenu () ;
GetParent()->GetMenu()->AppendMenu(MF_ POPUP , (UINT)m_Menu .m_hMenu, "PhoneBook");
GetParent()->DrawMenuBar();
~menu.AppendMenu(MF_STRING,lll,m_strLine.Left(m_strLine.Find(' ')));
m_strLine.Empty() ;
Invalidate() ;
}
else
m_strLine += nChar ;
dC.TextOut(O , O, m_strLine) ;
CView: :OnChar(nChar, nRepCnt , nFlags);
注:这里暂时先给新添加的这个菜单项临时取了一个ID: 111。
读者可以测试一下 Menu2程序,当程序运行后,输入几行:"人名电话号码"这样的文字,并同车,然后检查PhoneBook子菜单下的内容。将会发现输入的人名都作为菜单项
" ‘ I 207
添加到这个子菜单下了。
下面就要实现当单击这些菜单项时,应在程序窗口中显示对应的字符串,即:人名电话号码。那么在程序中就应该将所有输入的字符串都保存起来。我们可以定义一个字符串数组来保存用户输入的所有字符串,但是因为不知道用户会输入多少个字符串,所以数组的大小无法确定。当然对于这种动态增加的数组,可以通过链表来实现,但是这种方法很麻烦,也比较复杂。 MFC为我们提供了一些非常有用的集合类,这些集合类类似于数组的功能,但它们可以很方便地动态增加和删除元素。这里我们可以利用一个名为 CStringArray的集合类,这个集合类支持 CString对象的数组。为了增加一个字符串元素,可以利用该集合类的 Add成员函数,该函数的声明具有如下的形式 :
int Add( LPCTSTR newElement );
该函数的参数是一个指向常量字符串的指针 CLPCTSTR类型)。而通过 MSDN,我们可以在类 CString的成员函数中,发现这个类重载了 LPCTSTR操作符。这样的话,当我们向 CStringArray对象中添加元素时,可以直接给 Add函数的参数传递一个 CString类型的变量,编译器会自动完成转换的。当需要返回一个集合元素时,可以利用集合类的 GetAt成员函数,这个函数具有如下声明形式:
CString GetAt( int n工 ndex ) const;
国际: MFC提供了几个存储不同类型元素的集合类,它们的成员函数都很
类似,所以,掌握了其中的一个,其他的也就都明白了。
下面,我们就先为 CMenu2View类定义一个公有的 Cpublic类型) CStringArray类型的成员变量: m s位Array,用来保存所有输入的字符串。至于为什么将 m s位Array声明为公有的,在后面会讲述。然后在 OnChar函数中,在按下回车键后,并在 m strLi ne变量清空之前,把当前输入的一行文字增加到这个集合类变量中,即在 CMenu2View类的 OnChar函数中添加下述加灰显示的代码。
iJtl6-38
void CMenu2View: :OnChar(UINT nChar, UINT nRepCnt , UINT nFlags)
. .. .. .
m_menu .AppendMenu(MF_STRING, 111 ,
m_strLine.Left(m_strLine.Find(' ')));
m_strArray.Add(m_strLine);
m_strLine.Empty() ;
Invalidate() ;
.
.....
程序的要求是当单击动态添加的人名菜单项时,程序要在窗口中显示对应的字符串:人名电话号码。这就需要首先对动态添加的菜单项进行命令捕获。这里,笔者为读者介绍一种比较有技巧的实现方法。首先,在 Menu2工程中,在资源编辑器中打开程序的菜单,
然后在【帮助】子菜单后面添加一个新的子菜单,名称可以任意,例如 "abc",接着再为它添加几个菜单项,例如 4个,本例添加菜单项的名称及其 E如表 6.3所示。
表 6.3新添菜单项的名称及 10
菜单项名称 菜单项 ID
IDM PHONEI
2 10M PHONE2
3 IDM PHONE3
4 IDM PHONE4
品《,
保存对资源所做的修改,然后打开 Menu2程序的 Resource.h文件,在其中可以看到如
下几行代码:
#define工 DM
#define IDM PHONE2
、2
叫,
aq&
#define工 DM PHONE3
、
J
#define IDM PHONE4
寸,
然后利用 Class Wizard为 CMenu2View类分别添加以上这四个菜单项的命令响应函数,其中在消息列表中都选择 COMMAND消息。之后, CMenu2View类对这四个菜单项就有了四个命令响应函数。
接下来,我们在菜单资源编辑器中删除刚才新添加的子菜单 Cabc),但是将会发现它们的命令响应函数在源文件中都被保留下来了。
再回到上述例 6-38所示 CMenu2View的 OnChar函数,找到动态添加菜单项的代码,即调用 AppendMenu函数的那行代码,把其中的菜单 ID参数改成 IDM PHONEl,即修改成下面这行代码:
m_menu.AppendMenu(MF_STRING,IDM_PHONE1, m_strLine.Left(m_strLine.Find(' ')));
这行代码将添加 ID为 10M PHONE1的菜单项,但是,程序不能每次添加新菜单项时都使用这个菜单 E值,第二次添加时应该是 IDM_PHONE2......,直到第四次添加时应该是由M PNONE4 C因为这里是一个示例,所以本例假设最多只添加 4个菜单项)。但是如何编写程序代码让这里的 ID号由 IDM_PHONEl自动增加到 IDM_PHONE4呢?前面我们已经定义了一个变量: ID_nIndex,当每次按下回车键增加菜单项时,它的值就增1,这样,就可以利用这个变量加上 IDM_PHONEl的值来确定当前的菜单 E。当第一次按下回车键后, ID_nIndex的值是 0,加上 IDM_PHONEl后得到 IDM_PHONE1,即第一个菜单项的 ID;当第二次按下回草键后, ID nIndex的值增加为1,再加上 IDM_PHONEl C它的值为 32771)后,变成 32772,即 IOM_PHONE2,也就是第二个菜单项的 ID;……因此,利用这种方法,随着文字的输入,回车键的按下, ID_nIndex的数值不断增加,得到的菜单项 ID号也在不断变化。利用这种方式添加命令响应函数的过程很快,而且也很方便。这里,还有一些小问题需要提醒读者注意,如例 6-39所示是 CMenu2View类的头文件中消息映射函数的声明代码。
"I 209
第6章禀单
iJlJ 6-39
// Generated message map functions protected :
//({AFX_MSG(CMenu2View)
afx_msg void OnChar(U工NT nChar , UINT nRepCnt , UINT nFlags) ;
afx_msg void OnPhonel();
afx_ffisg void OnPhone2();
afx_ffisg void OnPhone3();
afx~sg void OnPhone4();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
从中可以发现VC++把我们新添的这儿个菜单命令的响应函数都放在AFX_MSG这两个注释宏之间了。其实,这样的代码也没有错误。但是为了区分这是自己手工添加的,可以把它们拿出来放在两个AFX MSG注释宏之后。
如例6-40所示是CMenu2View类源文件中消息映射表的定义代码。
例6-40
BEG工N_MESSAGE_MAP(CMenu2View, C飞lìew)
// ({AFX_MSG_MAP(CMenu2View)
ON_WM_ CHAR ( )
ON_COMMAND(工DM_PHONEl , OnPhonel)
ON_COMMAND(工DM_PHONE2 , OnPhone2)
ON_COMMAND(工DM_PHONE3 , OnPhone3)
ON_COMMAND(工DM_PHONE4 , OnPhone4)
// }}AFX_MSG_MAP
// Standard prìntìng commands
ON_COMMAND(工D_FILE_PR工NT, CView : :OnFilePrint)
ON_ COMMAND(ID_FILE_ PRINT_DIRECT , CView : :OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView : :OnFilePr工ntPreview)
END_MESSAGE_MAP()
可以发现VC++把刚才添加的几个命令响应映射宏都放在了两个 AFX一MSG MAP注释宏之间了。如果消息响应函数映射宏放在了这两个AFX-MSG-MAP注释宏之间的话,在ClassWizard对话框中,在Message Maps选项卡下的Memberfunctions列表中就会列出这些函数,如图6.46所示。如果消息响应函数的声明放在了这两个AFX-MSG-MAP注释宏外面的话,在 ClassWizaI吐对话框中就不会列出这些函数。与上面所说的在头文件中命令响应函数声明代码放置的位置不同,这里的映射宏代码一定要拿到两个 AFX MSG MAP注释宏之外。这是因为当 ClassWizard发现菜单项已经被删除了的时候,它就会把己为该菜单项添加的消息映射宏也删除了,这样,消息映射的三个环节中就断了一环,程序就会出错,造成不必要的麻烦。因此,这里一定要把 ClassWizard添加的几个消息映射宏拿出来,放到两个AFX_MSG_MAP注释宏之外,也就是说,Menu2程序中CMenu2View类的消息映射定义代码应该如例6-41所示。
详
凯灿
例 6-41
BEGIN_MESSAGE_MAP(CMenu2View, CView)
jj{{AFX_MSG_ MAP(CMenu2View)
ON_WM_CHAR ( )
j /} } AFX_MSG_MAP
ON_COMMAND(工 DM_PHONE1, OnPhonel)
ON_COMMAND(工 DM_PHONE2, OnPhone2)
ON_COMMAND(IDM_PHONE3 , OnPhone3)
ON_COMMAND(工 DM_PHONE4, OnPhone4)
11 Standard printing commands
ON_COMMAND(ID_FILE_PRINT , Cview : :OnFilePrint)
ON_COMMAND(工 D_FILE_PRINT_DIRECT, CView ::OnFilePrint)
ON_ COMMAND(ID_ FILE_PRINT_PREVIEW, CView : : OnFilePrintPreview)
END_MESSAGE_MAP()
图 6.46 ClassWizard对话框
经过以上几步, Menu2程序的 CMenu2View类就有了四个动态菜单项的命令响应函数。下面我们可以为它们分别添加具体的代码,以显示相应的人名和电话号码。例如,第一个动态菜单项的命令响应函数的定义可以是如例 6-42所示的这样。
例 6-42
void CMenu2View::O口 Pho口 el ( )
11 TODO : Add your command handler code here
CClientDC dc(this) ;
dc.TextOut(O , O,m_strArray.GetAt(O)) ;
在该函数中,首先定义设备描述表对象: dc,然后得到字符串数组中第一个字符串(其位置索引为 0),并在窗口的 ( 0,0)位置处显示出来。其他几个命令响应函数的处理基本相同,只是获取字符串数组中字符串元素时使用的位置索引不同而己,第二个命令响应函数使用的索引是1,第三个命令响应函数使用的索引是 2,依次类推。所以对其他几个菜单命令响应函数而言,可以直接复制第一个菜单命令响应函数中的代码,然后修改索引值
........‘ I 211
第6
就可以了。最后,读者可以运行程序井测试,将会发现 Menu2程序能够响应那些动态添加的菜单项的命令了。
菜单命令消息
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -