第六课:菜单(一)
一、有关菜单的一些基本知识:
1. 对于一个单文档的工程来说,菜单是在CxxxApp的Initinstance中产生的(Xxx为你的工程名字):
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMenuDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CMenuView));
AddDocTemplate(pDocTemplate);
其中IDR_MAINFRAME是菜单的ID,我们在资源面板里可以看到,很多资源的ID都是IDR_MAINFRAME,包括菜单、工具栏、加速键、图标和字符串表,所以,一个ID可以标识多个资源。需要注意的是,工具栏是在CMainFrame的OnCreate函数中产生的:
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
2. 当我们点击菜单时,系统发出的都是WM_COMMAND消息,在这个消息的扩展参数wParam中,包含菜单的ID,用户可以通过ID来判断是哪个菜单被点击了。
3. 在单文档工程中,MFC AppWizard为我们生成的四个类里,都可以响应同一个菜单的WM_COMMAND消息。如果在这四个类里都加了同一个菜单的响应函数,四个响应函数将只有一个被执行,也就是说,一个菜单消息只能响应被一次。它们之间有一个优先顺序,依次是:ViewàDocumentàMainFrameàApp。具体响应函数应该放在哪个类里,就要看在这个响应函数里,完成的是什么功能,放在哪个类里方便。比如,如果点击一个菜单,想隐藏工具栏,它的响应函数就应放在CMainFrame里。
4. 菜单是一格一格弹出来的,每弹出一格发出一个ON_UPDATE_COMMAND_UI消息,我们可以响应这个消息,设置菜单弹出的状态,比如打勾、打点、变灰等。注意:ON_UPDATE_COMMAND_UI只能由拥有菜单的窗口发出,切记!切记!!切切记!!!
二、菜单的操作:
我们想实现的功能是,有四个菜单:点、直线、矩形和椭圆。用户点击相应的菜单,以后用鼠标就会画出相就的图形。
1. 建立菜单:在资源面板里先建立一个弹出菜单-“画图”,再在这个弹出菜单下建四个子菜单,“点”、“直线”、“矩形”和“椭圆”,然后修改它们的ID。当我们建立一个子菜单时,系统会分配给它一个类似ID_MENUITEM32780的ID。这个ID非常难记,最好把它改为一个容易记的ID,如IDM_DRAW_DOT,ClassWizard会根据这个ID来为菜单响应函数生成名字。另外,弹出菜单没有ID。
2. 在CView中加入成员变量:
CPoint m_ptOrigin; //用于记录鼠标按下的点
int m_nType; //用于记录当前所画的图形
在CView 的构造函数中对它们进行初始化:
m_nType=-1;
m_ptOrigin=0;
3. 在CView中加入菜单“点”、“直线”、“矩形”和“椭圆”的WM_COMMAND消息的响应函数。在其中设置m_nType为不同的值。
点的响应函数中: m_nType=0;
直线的响应函数中: m_nType=1;
矩形的响应函数中: m_nType=2;
椭圆的响应函数中: m_nType=3;
4. 在CView中加入WM_LBUTTONDONW和WM_LBUTTONUP的消息响应函数OnLButtonDown和OnLButtonUp 。
在OnLButtonDown中保存鼠标按下的点:m_ptOrigin=point;
在OnLButtonUp中根据m_nType的值画相应的图形:
CClientDC dc(this);
switch(m_nType)
{
case 0:
dc.SetPixel(point.x,point.y,RGB(255,0,0));
break;
case 1:
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
break;
case 2:
dc.Rectangle(m_ptOrigin.x,m_ptOrigin.y,point.x,point.y);
break;
case 3:
dc.Ellipse(m_ptOrigin.x,m_ptOrigin.y,point.x,point.y);
break;
default:
break;
}
5. 此时我们已经基本完成了要求的功能,但还无法判断当前所画的是什么图形,为了使界面更加友好,可以WM_UPDATE_COMMAND_UI消息,在当前所画的图形对应的菜单项上打勾。用ClassWizard为菜单“点”、“直线”、“矩形”和“椭圆”的ON_UPDATE_COMMAND_UI的响应函数,其形式如下:
void CMenuView::OnUpdateDrawDot(CCmdUI* pCmdUI)
可以看到,在调用ON_UPDATE_COMMAND_UI的响应函数时,系统为用户传来了CCmdUI的指针。CCmdUI 是一个只被使用在ON_UPDATE_COMMAND_UI消息的响应函数中类,它可以控制正在被弹出的菜单项的状态。这个类中有一个成员变量m_nIndex表示菜单项在整个弹出菜单中的序号,另一个成员变量m_nID表示菜单的ID。我们来介绍一下它的成员函数:
Enable() : 设置菜单项是否有效;
SetCheck() : 设置菜单项是否打勾;
SetRadio () : 设置菜单项是否打点;
SetText() : 设置菜单项的文本。
我们就可以利用这些函数来控制菜单项的状态,比如设置当前所画的图形在对应的菜单项上打勾。
因为四个菜单的ON_UPDATE_COMMAND_UI响应函数的代码类似,我们只写出“点”的ON_UPDATE_COMMAND_UI的响应函数代码:
a. 直接判断:
if(m_nType==0)
pCmdUI->SetCheck(true);
else
pCmdUI->SetCheck(false);
b. 利用m_nIndex:
pCmdUI->SetCheck(m_nType==pCmdUI->m_nIndex);
c. 利用m_nID:
pCmdUI->SetCheck(m_nType+IDM_DRAW_DOT==pCmdUI->m_nID);
此种方法有一个条件,菜单“点”、“直线”、“矩形”和“椭圆”的ID必须是连续的。如果不连续,可以打开resource.h,将它们的ID修改为连续。
6. 快捷菜单:
在CView中加入WM_RBUTTONUP的响应函数OnRButtonUp,在其中加入代码:
ClientToScreen(&point);
GetParent()->GetMenu()->GetSubMenu(4)->TrackPopupMenu(
TPM_LEFTALIGN,point.x,point.y,GetParent());
说明:
a. OnRButtonUp中传入的坐标是相对于窗口的,而TrackPopupMenu函数需要的是相对屏幕的坐标,所以要用ClientToScreen转换一下。
b. 如果写成
GetMenu()->GetSubMenu(4)->TrackPopupMenu(
TPM_LEFTALIGN,point.x,point.y,GetParent());
运行时会报红框,因为这句代码会调用CView的成员函数GetMenu(),因为CView根本就没有菜单。
c. TrackPopupMenu函数的第四个参数指定拥有菜单的窗口,这个窗口将得到快捷菜单的所有WM_COMMAND消息。填成GetParent(),CMainFrame拥有快捷菜单,此时程序没有任何问题。如果填成this,快捷菜单将不能及时的打勾。因为ON_UPDATE_COMMAND_UI只能由拥有菜单的窗口发出。
d. 我们让CMainFrame拥有快捷菜单,它得到所有WM_COMMAND消息,在CView 中加入的WM_COMMAND消息的响应函数仍然会被运行。因为对于WM_COMMAND消息存在一个转发的过程。
e. 对于快捷菜单,有一个专门的消息以响应――WM_CONTEXTMENU。如果响应它来产生快捷菜单,就不用进行ClientToScreen的坐标转换了。
f. 产生快捷菜单,也可以点击ProjectàAdd To ProjectàComponents And ControlsàVisual C++ Components , 选择Pop-up Menu。
7.给菜单项增加图标:
a. 在CMainFrame中增加两个成员变量,用于装入两个位图,一个是选择菜单时的图标,一个是未被选择时的。
CBitmap bmp1;
CBitmap bmp2;
b. 在资源面板里增加两个位图,其ID为IDB_BITMAP1,IDB_BITMAP2。
c. 在CMainFrame的OnCreate函数中加入代码:
bmp1.LoadBitmap(IDB_BITMAP1);
bmp2.LoadBitmap(IDB_BITMAP2);
GetMenu()->GetSubMenu(4)->SetMenuItemBitmaps(
0,MF_BYPOSITION,&bmp1,&bmp2);
d. 此时位图可能在菜单中不能完全显示,用GetSystemMetrics()可以得到菜单图标的大小:
CString str;
str.Format("%d,%d",
GetSystemMetrics(SM_CXMENUCHECK),
GetSystemMetrics(SM_CYMENUCHECK));
MessageBox(str);
评论