菜单是应用程序中常用的用于交互操作的界面工具之一,它能够将一个应用程序的功能有效地按类组织,并以列表的方式显示出来,方便用户操作。
常见的菜单可分为三类:主菜单、弹出菜单和快捷菜单。
PopUp
属性为True
,主菜单只有显示弹出菜单的能力,没有执行的能力(没有ID,不能添加事件处理函数)对于菜单的显示都遵循下列一些规则:
Alt
构成一个组合键,当按住Alt
键不放,再敲击该字母时,对应的菜单项就会被选中。IDR_MAINFRAME
菜单是MFC AppWizard的单文档应用程序自动创建的一个默认的主菜单。ID (菜单命令的ID):
ID_顶层菜单名_下一级菜单名(_再下一级菜单名…)
Caption (菜单命令的名称):
Separator (分隔线):
True
,菜单命令将变成一个分隔线,起到视觉分割的作用。Popup (弹出式):
True
,菜单命令将成为弹出式菜单。Inactive (不活动):
True
,菜单命令的初始状态为非活动状态。Checked (检查标记):
True
,菜单命令的初始状态将被标记为已选中(打勾)。Grayed (变灰):
True
,菜单命令将以灰色显示。Help (帮助形式):
True
,菜单将显示在菜单栏的帮助部分。Break (菜单命令的分隔):
None
,Column
,Bar
。Prompt (菜单命令功能的提示):
True
,鼠标指针悬停在菜单命令上时,在底部状态栏会显示有关该命令功能的提示信息。菜单命令也是一种消息,在Windows中,消息分为三类:标准消息、命令消息和通告消息。
WM_COMMAND
之外,所有以WM_
开头的消息。从CWnd
派生的类都可以接收到这类消息。WM_COMMAND
形式呈现。在程序中,通过资源的标识(ID)来区分来自资源的命令消息。从CCmdTarget
派生的类,都可以接收到这类消息。WM_COMMAND
形式呈现的。从CCmdTarget
派生的类,都可以接收到这类消息。CMenu
类MFC中的CMenu
类封装了Windows的菜单功能,提供了对菜单和菜单项的多种操作。
与CMenu
类相关的主要函数如下:
CMenu* CWnd::GetMenu() const;
功能:得到主菜单的指针
CMenu* CMenu::GetSubMenu(int nPos)const;
nPos
:指定菜单项的位置,第一个菜单项为0
,第二个菜单项为1
,以此类推。
功能:得到第nPos+1个菜单项的弹出菜单的指针。
获取了菜单指针后,可以调用AppendMenu
或InsertMenu
函数在程序运行时添加菜单项。
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()
来更新菜单。
BOOL CMenu::DeleteMenu(UINT nPosition, UINT nFlags);
nPosition
:标识要删除的菜单项。
符号 | 含义 | nPosition 值 |
---|---|---|
MF_BYCOMMAND |
菜单项以ID号来标识 | 菜单项资源ID |
MF_BYPOSITION |
菜单项以位置来标识 | 菜单项位置 |
注意:当删除菜单项后,应调用CWnd::DrawMenuBar()
来更新菜单。
UINT CMenu::GetMenuItemCount() const;
获取菜单项数目失败时,函数返回值为-1。
UINT Cmenu ::GetMenuItemID(int nPos) const;
GetMenuItemID()
方法根据菜单项的位置返回菜单ID,如果该菜单项对应一个弹出菜单,则返回值为-1,如果该菜单项是一个分隔条,则返回值为0。
nPos
:标识菜单项的位置,第一个菜单项为0。
void CCmdUI::SetCheck(int nCheck1=1);
设定菜单项是否被选中,nCheck1
等于1为选中,0为未选中。
void CCmdUI::Enable(BOOL bOn=TRUE);
设定菜单项是否可选,bOn
等于1为可选,0为不可选(呈灰色)
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时,表示用户单击在菜单外面时,菜单会立即消失。
注意
CCmdUI
类更新菜单项的显示。COMMAND
UPDATE_COMMAND_UI
UPDATE_COMMAND_UI
是更新命令用户接口消息,专门用于处理菜单项和工具条按钮的更新。成员函数 | 功能 |
---|---|
Enable() |
设置菜单项是否有效 |
SetCheck() |
增加或清除"√"标记 |
CMainFrame
类中添加一个成员变量int type;OnLine()
函数中将其设置为1,即type=1;OnRectangle()
函数中将其设置为2,即type=2;ON_UPDATA_COMMAND_UI
消息映射void CMainFrame::OnUpdateLine(CCmdUI *pCmdUI)
{
pCmdUI->SetCheck(0);
if (type == 1)
{
pCmdUI->SetCheck(1);
pCmdUI->Enable(FALSE);
}
}
在MFC中,工具栏的功能由类CToolBar
实现。工具栏资源和工具栏类CToolBar
是工具栏的两个要素。创建工具栏的方法有两种:
ResourceView
视图中自带的工具栏进行创建。IDR_MAINFRAME_256
,可以在此基础上设计自己的工具栏,删除或添加一些按钮。需要为工具栏按钮提供ID号,一般取某个菜单项的ID。添加按钮
删除按钮
移动按钮
在工具栏中插入空格
项目 | 含义 |
---|---|
ID | 工具栏按钮的标识符,可以从ID框的下拉列表中选区标识符名称 |
Width | 工具栏按钮的像素宽度 |
Height | 工具栏按钮的像素高度 |
Prompt | 工具栏按钮提示信息。若为“建立新文档\n新建”,则表示当鼠标指向该按钮时,在状态栏中显示“建立新文档”,而在弹出的提示信息中出现“新建” |
使用工具栏编辑器来编辑工具栏资源。双击ResourceView
视图中的Toolbar
工具栏资源,即可打开工具栏编辑器。
状态栏实际上是一个窗口,一般分为几个窗格,每个窗格显示不同的信息。
状态栏可以分为两部分,其中左边最长的那部分称为提示行,当我们把鼠标移动到某个菜单项或工具按钮上时,该部分将显示相应的提示信息。
用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
函数更新任何窗格BOOL SetPaneText(int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE );
在MFC的CMFCStatusBar
类中,有两个成员函数可以改变状态栏风格,它们是:
void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth );
void SetPaneStyle( int nIndex, UINT nStyle );
参数nIndex
表示要设置的状态栏窗格的索引,nID
用来为状态栏窗格指定新的ID,cxWidth
表示窗格的像素宽度,nStyle
表示窗格的风格类型,用来指定窗格的外观,例如SBPS_POPOUT
表示窗格是凸起来的,见表。
ICON
与位图相似,用于存放系统中用到的所有的图标。
当选择Image->NewItemType时,将跳出对图标属性的设置。
掌握通用对话框以及菜单、工具栏和状态栏的使用。
制作一个含有菜单、工具栏和状态栏的单文档应用程序,要求如下:
首先利用向导构建多文档程序框架,并将项目取名为Ex4
。
COLORREF m_color;
CFont* m_font;
用于储存颜色和字体。
3. 在初始化代码里对颜色和字体进行初始化:
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
的绘图函数: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"));
}
void CEx4View::On32771()
{
// 修改颜色
CColorDialog dlg;
if (dlg.DoModal() == IDOK)
{
m_color = dlg.GetColor();
RedrawWindow();
}
}
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();
}
}
打开资源视图的Toolbar,删除无用的按钮并添加字体和颜色按钮,修改其ID以及Prompt。
IDS_POSITION_X
以及IDS_POSITION_Y
。indicators
数组:static UINT indicators[] =
{
ID_SEPARATOR, // 状态行指示器
IDS_POSITION_X,
IDS_POSITION_Y
};
CMainFrame
鼠标移动消息: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
鼠标移动事件处理程序:#include "MainFrm.h"
void CEx4View::OnMouseMove(UINT nFlags, CPoint point)
{
((CMainFrame*)AfxGetMainWnd())->OnMouseMove(nFlags, point);
CView::OnMouseMove(nFlags, point);
}
与颜色对话框CColorDialog
类似,MFC也提供了CFontDialog
,但使用时有所不同。首先,虽然它也提供了GetFont()
接口,但直接使用会编译报错。因此需要借用一个LOGFONT
临时变量来接收用户传递的字体。另外,成员变量m_font
要声明为指针类型,字体信息需要存储在堆区。如果直接使用CFont
,首次修改字体没问题,但第二次修改的时候程序将抛出异常。我曾经尝试使用try-catch
进行捕获,但没找到异常所在。
需要写在WM_CREATE
消息响应函数中,不能重写函数Create
或CreateEx
,否则将编译不通过。
由于状态栏是CMainFrame
中的成员变量,因此不能直接写在视图类里,否则将抛出异常。我也尝试过使用try-catch
进行捕获,但没找到异常所在。解决方法是写在CMainFrame
的消息响应函数中,再在视图类中调用。
本次实验主要是对MFC中的菜单、工具栏和状态栏的使用进行了学习和实践。在实验过程中,我学会了如何创建菜单资源、编辑菜单内容,以及如何在程序中响应菜单命令。 菜单是用户与应用程序进行交互的重要界面元素之一。在设计菜单时,合理的命名、添加快捷键、设置助记符等能够提高用户体验。理解菜单的属性和菜单项的响应机制对于有效地处理用户操作是至关重要的。而工具栏为用户提供了快速访问常用功能的途径。在设计工具栏时,要考虑按钮的布局和功能的对应关系,使界面简洁明了。通过工具栏编辑器,可以方便地进行按钮的添加、删除和调整。状态栏是用于显示应用程序状态和提供一些辅助信息的重要元素。在实验中,通过设置状态栏的指示器,实现了鼠标坐标的实时显示。这对于某些需要即时反馈的应用场景很有帮助。
颜色对话框和字体对话框是常用的通用对话框,通过它们可以让用户选择颜色和字体。在实验中,利用这两个对话框实现了修改文本颜色和字体的功能。理解对话框的返回值和相关消息处理是关键。
在实验中遇到了一些异常情况,如字体的异常处理和状态栏信息更新的问题。理解异常的产生原因,并通过调试工具进行排查,是解决问题的有效方法。
总体而言,通过这次实验,我对MFC的界面设计有了更深入的理解,并能够熟练地使用这些界面元素来构建功能完善的应用程序,更深入地了解了MFC框架下的界面设计和消息处理机制,为后续开发提供了一定的基础。
代码地址:https://github.com/zsc118/MFC-exercises