第1章窗体与界面设计
1.1 菜单应用实例
在设计程序主界面时,菜单几乎是必不可少的界面元素之一。在MFC中,提供了CMenu类用于操作和管理菜单。本节将通过几个典型实例介绍各种常用菜单的设计。
实例001 在系统菜单中添加菜单项
这是一个可以提高基础技能的实例
实例位置:光盘\mingrisoft\01\001
实例说明
系统菜单是用户右击标题栏时弹出的快捷菜单。默认情况下,系统菜单只包含移动、关于和与标题栏按钮对应的菜单项,如何在系统菜单中添加自己的菜单项呢?本实例实现了该功能,效果如图1.1所示。
图1.1 在系统菜单中添加菜单项
技术要点
为了操作系统菜单,首先需要获取一个系统菜单指针,可以通过GetSystemMenu函数实现,然后利用菜单指针添加一个菜单项,最后在对话框的OnSysCommand方法中处理菜单项的命令。
GetSystemMenu方法用于获取一个系统菜单指针,语法如下:
CMenu* GetSystemMenu( BOOL bRevert ) const;
参数说明:
● bRevert:确定方法执行的动作,如果为FALSE,该方法返回当前正在使用的系统菜单;如果为TRUE,该方法将重新设置系统菜单到默认状态,并且方法返回值不可用。
实现过程
(1)新建一个基于对话框的应用程序。
(2)在对话框类中定义一个菜单指针m_pMenu,用于指向系统菜单。
(3)主要程序代码。
在对话框初始化时(OnInitDialog函数中)获取系统菜单指针,向系统菜单中添加菜单项,代码如下:
m_pMenu=GetSystemMenu(FALSE); //获得正在使用的系统菜单指针 m_pMenu->AppendMenu(MF_STRING,IDI_PECULIARMENU,"系统菜单"); //添加菜单项
响应菜单项的命令消息,在对话框的OnSysCommand方法中添加消息处理代码:
void CPeculiarMenuDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID ) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else if(nID==IDI_PECULIARMENU) //如果是添加的菜单项 { MessageBox("系统菜单","提示",MB_OK|MB_ICONINFORMATION); //弹出消息提示 } else { CDialog::OnSysCommand(nID, lParam); } }
举一反三
根据本实例,读者可以:
禁用系统菜单项。
实例002 带图标的程序菜单
本实例是一个人性化的实例
实例位置:光盘\mingrisoft\01\002
实例说明
在MFC应用程序中,默认情况下,CMenu类并不具有显示图标的功能,但在许多应用程序中,菜单中都带有漂亮的图标。那么如何在MFC应用程序中为菜单添加图标呢?本实例实现了该功能,效果如图1.2所示。
图1.2 带图标的程序菜单
技术要点
要实现带图标的菜单,需要从CMenu类派生一个子类,并在子类中改写DrawItem方法和MeasureItem方法。基本设计思路如下。
首先定义一个记录菜单项信息的结构CMenuItemInfo,该结构包含了菜单项的文本、图像索引、ID等信息。然后从CMenu中派生一个子类,本实例为CIconMenu。在该类中定义一个方法ChangeMenuItem,利用递归的方式修改所有的菜单项信息,使其具有自绘风格(MF_OWNERDRAW)。接着在CIconMenu类中定义绘制菜单项文本、绘制菜单项图标和绘制分隔条的方法。最后改写MeasureItem方法,设置菜单项的大小,改写DrawItem方法,根据菜单项的不同状态,绘制菜单项。
实现过程
(1)新建一个单文档/视图结构的应用程序。
(2)从CMenu类派生一个子类CIconMenu。
(3)定义一个菜单项结构CMenuItemInfo,代码如下:
/************************************* CMenuItemInfo结构用于记录菜单项信息 *************************************/ struct CMenuItemInfo { CString m_ItemText; //菜单项文本 int m_IconIndex; //菜单项索引 int m_ItemID; //菜单标记 -2顶层菜单,-1弹出式菜单,0分隔条,其他普通菜单 };
(4)在CIconMenu类中定义一个CImageList类型的成员变量m_imagelist,用于存储图像;定义一个CMenuItemInfo结构数组m_ItemLists,用于记录每个菜单项信息。
(5)在CIconMenu类的构造函数中创建图像列表,并向图像列表中添加图标,代码如下:
CIconMenu::CIconMenu() { m_index= 0; m_iconindex= 0; //创建图像列表 m_imagelist.Create(16,16,ILC_COLOR24|ILC_MASK,0,0); //添加图标 m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON1)); m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON2)); m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON3)); m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON4)); m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON5)); m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON6)); m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON7)); m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON8)); m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON9)); m_imagelist.Add(AfxGetApp()->LoadIcon(IDI_ICON10)); }
(6)向CIconMenu类中添加ChangeMenuItem方法,修改菜单项信息,代码如下:
BOOL CIconMenu::ChangeMenuItem(CMenu* m_menu,BOOL m_Toped) { if (m_menu != NULL) { int m_itemcount=m_menu->GetMenuItemCount(); //获得菜单项目数 for (int i=0;i<m_itemcount;i++) { m_menu->GetMenuString(i,m_ItemLists[m_index].m_ItemText,MF_BYPOSITION); //获得菜单字符串 int m_itemID=m_menu->GetMenuItemID(i); //获得菜单ID if (m_itemID==-1 && m_Toped) { m_itemID=-2; //顶层菜单 }; m_ItemLists[m_index].m_ItemID=m_itemID; //保存菜单ID if (m_itemID>0) { m_ItemLists[m_index].m_IconIndex=m_iconindex; //保存图标索引 m_iconindex+=1; //设置索引增加 } m_menu->ModifyMenu(i,MF_OWNERDRAW|MF_BYPOSITION |MF_STRING, m_ItemLists[m_index].m_ItemID,(LPSTR)&(m_ItemLists[m_index])); //修改菜单风格 m_index+=1; CMenu*m_subMenu=m_menu->GetSubMenu(i); //获得子菜单 if (m_subMenu) { ChangeMenuItem(m_subMenu); //递归修改菜单项信息 } } } return TRUE ; }
(7)向CIconMenu类中添加DrawComMenu方法,绘制菜单项,代码如下:
void CIconMenu::DrawComMenu(CDC*m_pdc,CRect m_rect,COLORREF m_fromcolor,COLORREF m_tocolor,BOOL m_selected) { if(m_selected) //选中 { m_pdc->Rectangle(m_rect); //绘制矩形区域 m_rect.DeflateRect(1,1); //缩小区域 int r1,g1,b1; //读取渐变起点的颜色值 r1 = GetRValue(m_fromcolor); g1 = GetGValue(m_fromcolor); b1 = GetBValue(m_fromcolor); int r2,g2,b2; //读取渐变终点的颜色值 r2 = GetRValue(m_tocolor); g2 = GetGValue(m_tocolor); b2 = GetBValue(m_tocolor); float r3,g3,b3; //菜单区域水平方向每个点RGB值应该变化的度(范围) r3 = ((float)(r2-r1)) / (float)(m_rect.Height()); g3 = (float)(g2-g1)/(float)(m_rect.Height()); b3 = (float)(b2-b1)/(float)(m_rect.Height()); COLORREF r,g,b; //菜单区域水平方向每个点的颜色值 CPen* m_oldpen ; for(int i=m_rect.top;i<m_rect.bottom;i++) //绘制渐变色效果 { r = r1+(int)r3*(i-m_rect.top); g = g1+(int)g3*(i-m_rect.top); b = b1+ (int)b3*(i-m_rect.top); CPen m_pen(PS_SOLID,1,RGB(r,g,b)); //创建画笔 m_oldpen=m_pdc->SelectObject(&m_pen); //选择画笔 m_pdc->MoveTo(m_rect.left,i); //设置画线起点 m_pdc->LineTo(m_rect.right,i); //画线 } m_pdc->SelectObject(m_oldpen); } else { m_pdc->FillSolidRect(m_rect,RGB(0x000000F9,0x000000F8,0x000000F7)); //填充区域 } }
(8)向CIconMenu类中添加DrawMenuIcon方法,绘制菜单项图标,代码如下:
void CIconMenu::DrawMenuIcon(CDC* m_pdc,CRect m_rect,int m_icon ) { m_imagelist.Draw(m_pdc,m_icon,CPoint(m_rect.left+2,m_rect.top+4),ILD_TRANSPARENT); //绘制图标 }
(9)改写MeasureItem虚拟方法,设置菜单项大小,代码如下:
void CIconMenu::MeasureItem( LPMEASUREITEMSTRUCT lpStruct ) { if (lpStruct->CtlType==ODT_MENU) { lpStruct->itemHeight=ITEMHEIGHT; //设置菜单项高度 lpStruct->itemWidth=ITEMWIDTH; //设置菜单项宽度 CMenuItemInfo* m_iteminfo; m_iteminfo=(CMenuItemInfo*)lpStruct->itemData; //获得菜单项数据 lpStruct->itemWidth = ((CMenuItemInfo*)lpStruct->itemData)->m_ItemText.GetLength()*10;//设置宽度 switch(m_iteminfo->m_ItemID) { case 0: //分隔条 { lpStruct->itemHeight=1; //设置高度为1 break; } } } }
(10)改写DrawItem虚拟方法,绘制菜单项,代码如下:
void CIconMenu::DrawItem( LPDRAWITEMSTRUCT lpStruct ) { if (lpStruct->CtlType==ODT_MENU) { if(lpStruct->itemData==NULL) return; unsigned int m_state=lpStruct->itemState; //获得菜单状态 CDC*m_dc=CDC::FromHandle(lpStruct->hDC); //获得设备上下文 CString str= ((CMenuItemInfo*)(lpStruct->itemData))->m_ItemText; //获得菜单项文本 LPSTR m_str = str.GetBuffer(str.GetLength()); int m_itemID=((CMenuItemInfo*)(lpStruct->itemData))->m_ItemID; //获得菜单项ID int m_itemicon=((CMenuItemInfo*)(lpStruct->itemData))->m_IconIndex; //获得图标索引 CRect m_rect=lpStruct->rcItem; //获得菜单项区域 m_dc->SetBkMode(TRANSPARENT); //设置背景透明 switch(m_itemID) { case-2: //顶层菜单 { DrawTopMenu(m_dc,m_rect,(m_state&ODS_SELECTED)||(m_state&0x0040)); //0x0040 ==ODS_HOTLIGHT DrawItemText(m_dc,m_str,m_rect); //绘制文本 break; } case-1: //弹出式菜单 { DrawItemText(m_dc,m_str,m_rect); //绘制文本 break; } case 0: //分隔条 { DrawSeparater(m_dc,m_rect); //绘制分隔条 break; } default: //普通菜单 { DrawComMenu(m_dc,m_rect,0xfaa0,0xf00ff,m_state&ODS_SELECTED);//绘制菜单背景 DrawItemText(m_dc,m_str,m_rect); //绘制菜单文本 DrawMenuIcon(m_dc,m_rect,m_itemicon); //绘制菜单 break; } } } }
举一反三
根据本实例,读者可以:
实现具有状态栏提示功能的菜单。
实例003 浮动的菜单
这是一个可以提高分析能力的实例
实例位置:光盘\mingrisoft\01\003
实例说明
在许多应用软件中,菜单都具有浮动功能。例如在Word中,用户可以将菜单拖动到任意位置。本实例设计了一个浮动的菜单,效果如图1.3所示。
图1.3 浮动的菜单
技术要点
在文档/视图结构的应用程序中,默认情况下,工具栏具有拖动菜单的功能。在用户单击工具栏按钮时,能够弹出一个快捷菜单,就可以实现一个浮动的“菜单”了,如何确定用户是否单击了工具栏按钮呢,可以通过在框架窗口中添加TBN_DROPDOWN通知消息映射宏来实现。
ON_NOTIFY(TBN_DROPDOWN,AFX_IDW_TOOLBAR,OnToolbarDropDown)
这样,当用户单击工具栏按钮时,就会调用自定义的OnToolbarDropDown方法,在该方法中可以获得用户单击的工具栏按钮ID和按钮的客户区域,根据按钮ID加载相应的子菜单,根据按钮区域设置子菜单弹出的位置。
实现过程
(1)新建一个文档/视图应用程序。
(2)从CToolBar类派生一个子类CFloatMenu。在CFloatMenu中定义一个CMenu指针m_pSubMenu。
(3)向CFloatMenu类中添加AddButtonFromMenu方法,该方法根据菜单添加工具栏按钮,代码如下:
BOOL CFloatMenu::AddButtonFromMenu(UINT IDresource) { CMenu Menu ; if (Menu.LoadMenu(IDresource)) { //获取菜单顶层菜单数 UINT ButtonCount = Menu.GetMenuItemCount(); UINT * array = new UINT[ButtonCount]; CString text; for (int n = 0; n<ButtonCount;n++) { array[n]=ID_BUTTON1+n; //设置工具栏按钮ID } //添加工具栏按钮 SetButtons(array,ButtonCount); for (int i=0;i<ButtonCount;i++) { Menu.GetMenuString(i,text,MF_BYPOSITION); //获得菜单项文本 SetButtonText(i,text); //设置工具栏按钮文本 SetButtonStyle(i,TBSTYLE_DROPDOWN); //设置工具栏按钮风格 } delete array; Menu.DestroyMenu(); return true; } else return FALSE; }
(4)向CFloatMenu类中添加GetIndexFromPoint方法,根据光标点确定工具栏按钮索引,代码如下:
UINT CFloatMenu::GetIndexFromPoint(CPoint pot) { CRect rect; for(int i=0;i<GetToolBarCtrl().GetButtonCount()-1;i++) //根据工具栏按钮数循环 { GetItemRect(i,rect); //获得工具栏按钮区域 if(rect.PtInRect(pot)) //判断光标是否在工具栏按钮上 return i; //返回工具栏按钮索引 } return -1; }
(5)在框架类中添加通知消息映射宏,代码如下:
ON_NOTIFY(TBN_DROPDOWN,AFX_IDW_TOOLBAR,OnToolbarDropDown)
(6)在框架类中添加OnToolbarDropDown方法,在用户单击工具栏按钮时弹出菜单,代码如下:
void CMainFrame::OnToolbarDropDown(NMTOOLBAR *pnmtb, LRESULT *plr) { CWnd*pWnd=&m_wndFloatTool; //获得浮动工具栏窗口指针 UINT nID = IDR_MAINFRAME; CMenu menu; menu.LoadMenu(nID); //加载菜单资源 CMenu* pPop = menu.GetSubMenu(pnmtb->iItem-ID_BUTTON1);//获得子菜单 m_wndFloatTool.m_pSubMenu = pPop; m_wndFloatTool.MenuPopIndex = pnmtb->iItem-ID_BUTTON1;//设置菜单索引 CRect rc; m_wndFloatTool.GetToolBarCtrl().GetItemRect(pnmtb->iItem-ID_BUTTON1,rc);//获得工具栏按钮区域 pWnd->ClientToScreen(&rc); //转换为屏幕坐标区域 pPop->TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_VERTICAL, rc.left,rc.bottom,this,&rc); //显示弹出菜单 m_wndFloatTool.MenuPopIndex = -1; }
举一反三
根据本实例,读者可以:
设计浮动的对话框窗口。