第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;
        }

举一反三

根据本实例,读者可以:

设计浮动的对话框窗口。