文章目录
1.预备知识
1.菜单
菜单是应用程序中常用的用于交互操作的界面工具之一,它能够将一个应用程序的功能有效地按类组织,并以列表的方式显示出来,方便用户操作。
常见的菜单可分为三类:主菜单、弹出菜单和快捷菜单。
- 主菜单 指出现在应用程序主窗口或最上层窗口的菜单。通常对应有一个弹出菜单作为它的子菜单。主菜单的
PopUp
属性为True
,主菜单只有显示弹出菜单的能力,没有执行的能力(没有ID,不能添加事件处理函数) - 弹出菜单 指选择主菜单或一个菜单项时弹出的子菜单。
- 快捷菜单 当右击某个界面对象时,通常会弹出快捷菜单,它出现在鼠标箭头的位置,快速展示当前对象可用的命令功能。
对于菜单的显示都遵循下列一些规则:
- 若点击某菜单项会弹出一对话框,那么在该菜单项文本后有"..."。
- 若某项菜单有子菜单,那么在该菜单项文本后有箭头标志。
- 菜单项需要助记符,用括号将带下划线的字母括起来。助记符与
Alt
构成一个组合键,当按住Alt
键不放,再敲击该字母时,对应的菜单项就会被选中。 - 若某项菜单需要快捷键的支持,则一般将其列在相应菜单项文本之后。任何时候按下快捷键,相应的菜单命令都会被执行。
1.创建菜单
在系统自动生成的菜单资源中添加一个主菜单命令
IDR_MAINFRAME
菜单是MFC AppWizard的单文档应用程序自动创建的一个默认的主菜单。- 双击该菜单资源名称,可以打开菜单编辑器。
- 在菜单编辑器中,为程序添加自己的菜单命令。
菜单属性
-
ID (菜单命令的ID):
- 格式:
ID_顶层菜单名_下一级菜单名(_再下一级菜单名...)
- 格式:
-
Caption (菜单命令的名称):
- 菜单命令的名称或标签。
- 可以在相应字母前使用"&"设置快捷键。
- 可以使用"\tCtrl+C"添加加速键,以便在不打开菜单的情况下直接执行菜单命令。
-
Separator (分隔线):
- 如果为
True
,菜单命令将变成一个分隔线,起到视觉分割的作用。
- 如果为
-
Popup (弹出式):
- 如果为
True
,菜单命令将成为弹出式菜单。 - 单击此菜单可能会显示另一层命令。
- 在VS2010中,顶层菜单默认为弹出式菜单。
- 如果为
-
Inactive (不活动):
- 如果为
True
,菜单命令的初始状态为非活动状态。
- 如果为
-
Checked (检查标记):
- 如果为
True
,菜单命令的初始状态将被标记为已选中(打勾)。
- 如果为
-
Grayed (变灰):
- 如果为
True
,菜单命令将以灰色显示。 - 表示菜单的初始状态为不可用。
- 如果为
-
Help (帮助形式):
- 如果为
True
,菜单将显示在菜单栏的帮助部分。
- 如果为
-
Break (菜单命令的分隔):
- 三个选项:
None
,Column
,Bar
。 - 当一个菜单的菜单命令太多时,可以将其分成两列来显示。
- 三个选项:
-
Prompt (菜单命令功能的提示):
- 如果为
True
,鼠标指针悬停在菜单命令上时,在底部状态栏会显示有关该命令功能的提示信息。
- 如果为
2.编辑菜单过程中所涉及的操作
- 插入菜单项 选中空白区域,输入菜单项标题,并设置属性。
- 调整菜单项位置 选中某菜单项将其拖至适当位置。
- 删除菜单项 用鼠标单击菜单项,然后单击Cut按钮或按Del键删除。
3.菜单设计步骤
- 使用菜单编辑器编辑菜单资源;
- 右键点击菜单项→添加事件处理函数。
4.菜单的响应和消息路由
菜单命令也是一种消息,在Windows中,消息分为三类:标准消息、命令消息和通告消息。
- 标准消息是除
WM_COMMAND
之外,所有以WM_
开头的消息。从CWnd
派生的类都可以接收到这类消息。 - 命令消息是来自菜单、加速键或工具栏按钮的消息。这类消息都以
WM_COMMAND
形式呈现。在程序中,通过资源的标识(ID)来区分来自资源的命令消息。从CCmdTarget
派生的类,都可以接收到这类消息。 - 通告消息是由控件产生的消息,例如按钮的单击、列表框的选择等都会产生这类消息,目的是为了向其父窗口(通常是对话框)通知事件的发生。这类消息也是以
WM_COMMAND
形式呈现的。从CCmdTarget
派生的类,都可以接收到这类消息。
5.CMenu
类
MFC中的CMenu
类封装了Windows的菜单功能,提供了对菜单和菜单项的多种操作。
与CMenu
类相关的主要函数如下:
获取菜单指针
cpp
CMenu* CWnd::GetMenu() const;
功能:得到主菜单的指针
cpp
CMenu* CMenu::GetSubMenu(int nPos)const;
nPos
:指定菜单项的位置,第一个菜单项为0
,第二个菜单项为1
,以此类推。
功能:得到第nPos+1个菜单项的弹出菜单的指针。
添加菜单项
获取了菜单指针后,可以调用AppendMenu
或InsertMenu
函数在程序运行时添加菜单项。
cpp
BOOL CMenu::AppendMenu(UINT nFlags, UINT nIDNewItem=0, LPCTSTR lpszNewItem=NULL);
BOOL CMenu::InsertMenu(UINT nPosition, UINT nFlags, UINT nIDNewItem=0, LPCTSTR lpszNewItem=NULL);
nFlags
常用的两种风格为:MF_POPUP
(添加主菜单项)和MF_STRING
(添加弹出菜单项)。
注意:当菜单项增加后,应调用CWnd::DrawMenuBar()
来更新菜单。
删除菜单项
cpp
BOOL CMenu::DeleteMenu(UINT nPosition, UINT nFlags);
nPosition
:标识要删除的菜单项。
DedeteMenu的nFlags标志及其对其他值的影响
符号 | 含义 | nPosition 值 |
---|---|---|
MF_BYCOMMAND |
菜单项以ID号来标识 | 菜单项资源ID |
MF_BYPOSITION |
菜单项以位置来标识 | 菜单项位置 |
注意:当删除菜单项后,应调用CWnd::DrawMenuBar()
来更新菜单。
获取菜单项数目
cpp
UINT CMenu::GetMenuItemCount() const;
获取菜单项数目失败时,函数返回值为-1。
获取菜单ID号
cpp
UINT Cmenu ::GetMenuItemID(int nPos) const;
GetMenuItemID()
方法根据菜单项的位置返回菜单ID,如果该菜单项对应一个弹出菜单,则返回值为-1,如果该菜单项是一个分隔条,则返回值为0。
nPos
:标识菜单项的位置,第一个菜单项为0。
对菜单项属性的修改
cpp
void CCmdUI::SetCheck(int nCheck1=1);
设定菜单项是否被选中,nCheck1
等于1为选中,0为未选中。
cpp
void CCmdUI::Enable(BOOL bOn=TRUE);
设定菜单项是否可选,bOn
等于1为可选,0为不可选(呈灰色)
显示快捷菜单
cpp
BOOL CMenu::TrackPopupMenu(UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect=NULL);
nFlags
:表示菜单在屏幕显示的位置以及鼠标按钮标志.
x
:菜单的水平坐标;
y
:菜单的垂直坐标;
pWnd
:标识显示快捷菜单的窗口,此窗口将收到此快捷菜单全部的WM_COMMAND
消息;
lpRect
:一个RECT
结构或CRect
对象指针,表示一个矩形区域,用户单击这个区域时,快捷菜单不消失。当lpRect
为NULL时,表示用户单击在菜单外面时,菜单会立即消失。
注意
- 一般情况下,菜单项都不止一种状态。例如,在没有选择任何内容时,Edit菜单下的Copy、Cut等菜单项是无效的(灰色显示)。有时,我们还会看到,在菜单项旁边可能还会有检查标记,表示它是选中的还是不选中的。
- MFC通过消息映射机制和
CCmdUI
类更新菜单项的显示。 - 为菜单项添加消息响应函数时,如果选择一个菜单ID,在Messages列表框中就会出现以下两项:
COMMAND
UPDATE_COMMAND_UI
其中UPDATE_COMMAND_UI
是更新命令用户接口消息,专门用于处理菜单项和工具条按钮的更新。
成员函数 | 功能 |
---|---|
Enable() |
设置菜单项是否有效 |
SetCheck() |
增加或清除"√"标记 |
- 为程序添加更新用户界面的消息处理函数,使得用户在选择某一菜单项后,为此菜单项添加复选标记,同时设置为无效状态,表示不可再选此项。
- 记录用户当前选择的菜单项
- 在
CMainFrame
类中添加一个成员变量int type; - 在类的初始化函数中赋初值为0,即type=0;
- 在
OnLine()
函数中将其设置为1,即type=1; - 在
OnRectangle()
函数中将其设置为2,即type=2;
- 在
- 建立菜单的
ON_UPDATA_COMMAND_UI
消息映射 - 为菜单的消息处理函数添加代码
- 记录用户当前选择的菜单项
cpp
void CMainFrame::OnUpdateLine(CCmdUI *pCmdUI)
{
pCmdUI->SetCheck(0);
if (type == 1)
{
pCmdUI->SetCheck(1);
pCmdUI->Enable(FALSE);
}
}
2.工具栏
在MFC中,工具栏的功能由类CToolBar
实现。工具栏资源和工具栏类CToolBar
是工具栏的两个要素。创建工具栏的方法有两种:
- 使用
ResourceView
视图中自带的工具栏进行创建。 - 添加自定义工具栏
概述
- 系统默认生成的工具栏资源为
IDR_MAINFRAME_256
,可以在此基础上设计自己的工具栏,删除或添加一些按钮。需要为工具栏按钮提供ID号,一般取某个菜单项的ID。 - 编程时,菜单、快捷键、工具栏资源往往配合使用。具有相同ID号的菜单项、工具栏按钮、快捷键被用户操作后,会产生相同的命令消息,只需要进行一次消息映射。
基本操作
-
添加按钮
- 双击工具条上的空白按钮或单击空白按钮后,绘制新的按钮。
-
删除按钮
- 将按钮拖出工具栏外,即可删除该按钮。
-
移动按钮
- 鼠标选中按钮,按住鼠标不动,移动到指定位置上,松开鼠标。
- 需要注意的是,按Delete键并不能删除该按钮,只是将其中的图形以背景色填充。
-
在工具栏中插入空格
工具栏按钮属性
项目 | 含义 |
---|---|
ID | 工具栏按钮的标识符,可以从ID框的下拉列表中选区标识符名称 |
Width | 工具栏按钮的像素宽度 |
Height | 工具栏按钮的像素高度 |
Prompt | 工具栏按钮提示信息。若为"建立新文档\n新建",则表示当鼠标指向该按钮时,在状态栏中显示"建立新文档",而在弹出的提示信息中出现"新建" |
编辑工具栏
使用工具栏编辑器来编辑工具栏资源。双击ResourceView
视图中的Toolbar
工具栏资源,即可打开工具栏编辑器。
3.状态栏
状态栏实际上是一个窗口,一般分为几个窗格,每个窗格显示不同的信息。
状态栏可以分为两部分,其中左边最长的那部分称为提示行,当我们把鼠标移动到某个菜单项或工具按钮上时,该部分将显示相应的提示信息。
用AppWizard创建的SDI或MDI应用程序框架中,有一个静态的indicator数组,它是在MainFrm.cpp文件中定义的,被MFC用作状态栏的定义。
常用操作
增加和减少窗格
状态栏中的窗格可以分为信息行窗格和指示器窗格两类。若在状态栏中增加一个信息行窗格,则只需在indicators
数组中的适当位置中增加一个ID_SEPARATOR
标识;若在状态栏中增加一个用户指示器窗格,则在indicators
数组中的适当位置增加一个在字符串表中定义过的资源ID,其字符串的长度表示用户指示器窗格的大小。若状态栏减少一个窗格,其操作与增加相类似,只需减少indicators
数组元素。
在状态栏上显示文本
有三种办法可以在状态栏窗格显示文本信息:
- 调用
CWnd::SetWindowText
更新信息行窗格(或窗格0)中的文本。由于状态栏也是一种窗口,故在使用时可直接调用。若状态栏变量为m_wndStatusBar
,则m_wndStatusBar. SetWindowText()
语句将在信息行窗格(或窗格0)内显示"消息"字样。 - 手动处理状态栏的
ON_UPDATE_COMMAND_UI
更新消息,并在处理函数中调用CCmdUI::SetText
函数。 - 调用
CStatusBar::SetPaneText
函数更新任何窗格
(包括信息行窗格)中的文本。此函数原型描述如下:
cpp
BOOL SetPaneText(int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE );
改变状态栏的风格
在MFC的CMFCStatusBar
类中,有两个成员函数可以改变状态栏风格,它们是:
cpp
void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth );
void SetPaneStyle( int nIndex, UINT nStyle );
参数nIndex
表示要设置的状态栏窗格的索引,nID
用来为状态栏窗格指定新的ID,cxWidth
表示窗格的像素宽度,nStyle
表示窗格的风格类型,用来指定窗格的外观,例如SBPS_POPOUT
表示窗格是凸起来的,见表。
状态栏窗恪的风格类型 | 风格类型 | 含义 | | ------------ | ---------------------------------------- | | `SBPS_NOBORDERS` | 在窗格周围没有3-D边框。 | | `SBPS_POPOUT` | 反转边界以使文字"凸出来"。 | | `SBPS_DISABLED` | 不绘制文本。 | | `SBPS_STRETCH` | 扩大窗格以填充不用的空白空间。没有状态条只能有一个窗格具有这种风格。 | | `SBPS_NORMAL` | 没有扩展,边界或"凸出来"。 |
ICON
与位图相似,用于存放系统中用到的所有的图标。
当选择Image->NewItemType时,将跳出对图标属性的设置。
2.实验目的
掌握通用对话框以及菜单、工具栏和状态栏的使用。
3.实验内容
制作一个含有菜单、工具栏和状态栏的单文档应用程序,要求如下:
- 其中主菜单栏中添加一个菜单项:名称是"我的菜单",有颜色,字体两个菜单选项,用户选择之后可以弹出颜色和字体对话框,对文档中输出的'Hello World"进行颜色和字体的改变。
- 工具栏中添加按钮,且工具栏中按钮的功能与对应菜单项功能一致。
- 状态栏中添加两个信息栏用来显示鼠标的x,y坐标位置,当鼠标移动时状态中的指针坐标位置会发生变化。
4.代码实现
首先利用向导构建多文档程序框架,并将项目取名为Ex4
。
1.菜单栏
-
打开资源视图的Menu,修改菜单栏资源如下:
-
利用类向导为
CEx4View
添加两个变量:
cpp
COLORREF m_color;
CFont* m_font;
用于储存颜色和字体。
- 在初始化代码里对颜色和字体进行初始化:
cpp
int CEx4View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
m_color = RGB(0, 0, 0);
(m_font=new CFont)->CreateFontW(0, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, nullptr);
/*CPoint p;
GetCursorPos(&p);
((CMainFrame*)AfxGetMainWnd())->OnMouseMove(0, p);*/
/*CPoint p;
GetCursorPos(&p);
((CMainFrame*)AfxGetMainWnd())->initPointPrint(p);*/
return 0;
}
- 重写
CEx4View
的绘图函数:
cpp
void CEx4View::OnDraw(CDC* pDC)
{
CEx4Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
pDC->SelectObject(m_font);
pDC->SetTextColor(m_color);
pDC->TextOutW(100, 100, TEXT("Hello World"));
}
- 对菜单栏颜色按钮添加事件处理程序:
cpp
void CEx4View::On32771()
{
// 修改颜色
CColorDialog dlg;
if (dlg.DoModal() == IDOK)
{
m_color = dlg.GetColor();
RedrawWindow();
}
}
- 对菜单栏字体按钮添加事件处理程序:
cpp
void CEx4View::On32772()
{
// 修改字体
CFontDialog dlg;
if (dlg.DoModal() == IDOK)
{
static LOGFONT t;
dlg.GetCurrentFont(&t);
delete m_font;
(m_font=new CFont)->CreateFontIndirectW(&t);
RedrawWindow();
}
}
2.工具栏
打开资源视图的Toolbar,删除无用的按钮并添加字体和颜色按钮,修改其ID以及Prompt。
3.状态栏
- 打开资源视图的String Table,添加
IDS_POSITION_X
以及IDS_POSITION_Y
。 - 修改
indicators
数组:
cpp
static UINT indicators[] =
{
ID_SEPARATOR, // 状态行指示器
IDS_POSITION_X,
IDS_POSITION_Y
};
- 添加
CMainFrame
鼠标移动消息:
cpp
void CMainFrame::OnMouseMove(UINT nFlags, CPoint point)
{
static CString s;
s.Format(TEXT("%d"), point.x);
m_wndStatusBar.SetPaneText(1, s);
s.Format(TEXT("%d"), point.y);
m_wndStatusBar.SetPaneText(2, s);
CFrameWnd::OnMouseMove(nFlags, point);
}
- 在视图类里调用
CMainFrame
鼠标移动事件处理程序:
cpp
#include "MainFrm.h"
void CEx4View::OnMouseMove(UINT nFlags, CPoint point)
{
((CMainFrame*)AfxGetMainWnd())->OnMouseMove(nFlags, point);
CView::OnMouseMove(nFlags, point);
}
5.运行结果
6.总结
1.实验中遇到的困难
如何获取用户选择的字体
与颜色对话框CColorDialog
类似,MFC也提供了CFontDialog
,但使用时有所不同。首先,虽然它也提供了GetFont()
接口,但直接使用会编译报错。因此需要借用一个LOGFONT
临时变量来接收用户传递的字体。另外,成员变量m_font
要声明为指针类型,字体信息需要存储在堆区。如果直接使用CFont
,首次修改字体没问题,但第二次修改的时候程序将抛出异常。我曾经尝试使用try-catch
进行捕获,但没找到异常所在。
如何初始化字体和颜色
需要写在WM_CREATE
消息响应函数中,不能重写函数Create
或CreateEx
,否则将编译不通过。
如何更新状态栏信息
由于状态栏是CMainFrame
中的成员变量,因此不能直接写在视图类里,否则将抛出异常。我也尝试过使用try-catch
进行捕获,但没找到异常所在。解决方法是写在CMainFrame
的消息响应函数中,再在视图类中调用。
2. 心得体会
本次实验主要是对MFC中的菜单、工具栏和状态栏的使用进行了学习和实践。在实验过程中,我学会了如何创建菜单资源、编辑菜单内容,以及如何在程序中响应菜单命令。 菜单是用户与应用程序进行交互的重要界面元素之一。在设计菜单时,合理的命名、添加快捷键、设置助记符等能够提高用户体验。理解菜单的属性和菜单项的响应机制对于有效地处理用户操作是至关重要的。而工具栏为用户提供了快速访问常用功能的途径。在设计工具栏时,要考虑按钮的布局和功能的对应关系,使界面简洁明了。通过工具栏编辑器,可以方便地进行按钮的添加、删除和调整。状态栏是用于显示应用程序状态和提供一些辅助信息的重要元素。在实验中,通过设置状态栏的指示器,实现了鼠标坐标的实时显示。这对于某些需要即时反馈的应用场景很有帮助。
颜色对话框和字体对话框是常用的通用对话框,通过它们可以让用户选择颜色和字体。在实验中,利用这两个对话框实现了修改文本颜色和字体的功能。理解对话框的返回值和相关消息处理是关键。
在实验中遇到了一些异常情况,如字体的异常处理和状态栏信息更新的问题。理解异常的产生原因,并通过调试工具进行排查,是解决问题的有效方法。
总体而言,通过这次实验,我对MFC的界面设计有了更深入的理解,并能够熟练地使用这些界面元素来构建功能完善的应用程序,更深入地了解了MFC框架下的界面设计和消息处理机制,为后续开发提供了一定的基础。