1.2 弹出菜单应用实例

在程序中使用弹出式菜单能够方便用户操作。本节通过几个典型实例介绍各种弹出式菜单的设计。

实例004 在控件上单击右键弹出带菜单

本实例可以方便操作、提高效率

实例位置:光盘\mingrisoft\01\004

实例说明

在许多应用软件中,当用户单击鼠标右键时,会弹出一个快捷菜单,用户可以通过快捷菜单方便地进行各种操作。本实例实现了弹出式菜单的功能,效果如图1.4所示。

技术要点

实现弹出式菜单非常简单,只需要处理WM_CONTEXTMENU消息就可以了。在其消息处理函数(默认为OnContextMenu)中调用菜单的TrackPopupMenu方法即可在指定位置弹出菜单。

实现过程

(1)新建一个基于对话框的应用程序。

(2)在对话框中添加List Control控件,设置控件属性,如图1.5所示。

图1. 4 在控件上单击右键弹出菜单

图1.5 列表控件属性

(3)处理对话框的WM_CONTEXTMENU消息,代码如下:

        void CPopManuDlg::OnContextMenu(CWnd* pWnd, CPoint point)
        {
        CMenu m_popmenu;                           //定义菜单对象
        m_popmenu.LoadMenu(IDR_POPMENU);          //加载菜单资源
        CMenu*m_submenu=m_popmenu.GetSubMenu(0);  //获得子菜单
        m_submenu->TrackPopupMenu(TPM_LEFTBUTTON|TPM_LEFTALIGN,point.x,point.y,this); //显示弹出菜单
        m_popmenu.DestroyMenu();
        }

举一反三

根据本实例,读者可以:

设计系统托盘菜单。

实例005 个性化的弹出菜单

这是一个可以提高基础技能的实例

实例位置:光盘\mingrisoft\01\005

图1.6 个性化的弹出菜单

实例说明

网上许多软件的弹出式菜单非常漂亮。本实例设计了一个漂亮的弹出式菜单,效果如图1.6所示。

技术要点

设计弹出式菜单与设计普通的菜单一样,需要从CMenu类派生一个子类,然后改写MeasureItem方法设置菜单项大小;改写DrawItem方法根据当前状态绘制菜单。设计思路可以参考实例002带图标的程序菜单。

实现过程

(1)新建一个基于对话框的应用程序。

(2)CMenu派生一个子类CIconMenu,定义一个菜单项结构CMenuItemInfo,代码如下:

        /*************************************
        CMenuItemInfo结构用于记录菜单项信息
        *************************************/
        struct CmenuItemInfo
        {
        CString m_ItemText;                   //菜单项文本
        int m_IconIndex;                      //菜单项索引
        int m_ItemID;                         //菜单标记 -2顶层菜单,-1弹出式菜单,0分隔条,其他普通菜单
        };

(3)在CIconMenu类中定义如下成员变量:

        CMenuItemInfo m_ItemLists[MAX_MENUCOUNT];   //菜单项信息
        int m_index;                                //临时索引
        int m_iconindex;                            //图像索引
            BOOL m_isdrawtitle;                          //是否重绘标题
            CFont m_titlefont;                           //标题字体

(4)向CIconMenu类中添加DrawItemText方法,绘制菜单项文本,代码如下:

        /*************************************************************
        功能描述: 绘制菜单项文本
        参数说明: m_pdc标识画布对象,str标识菜单文本,m_rect标识菜单区域
        *************************************************************/
        void CIconMenu::DrawItemText(CDC* m_pdc,LPSTR str,CRect m_rect)
        {
            m_rect.DeflateRect(40,0,0,0);                                       //调整文本绘制区域
            m_pdc->DrawText(str,m_rect,DT_SINGLELINE|DT_VCENTER|DT_LEFT);       //绘制菜单项文本
        }

(5)向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->SelectStockObject(BLACK_PEN);                                 //设置黑色画笔
        m_rect.DeflateRect(25,1,0,2);                                        //调整菜单项区域
        m_pdc->Rectangle(m_rect);                                            //绘制矩形
        CBitmap m_bitmap;
        m_bitmap.LoadBitmap(IDB_LEFTBITMAP);                                 //加载位图资源
        BITMAP m_size;
        m_bitmap.GetBitmap(&m_size);                                         //获得位图数据
        CDC m_memdc;
        m_memdc.CreateCompatibleDC(m_pdc);                                   //创建内存兼容设备上下文
        CGdiObject* m_oldobject;
        m_oldobject=m_memdc.SelectObject(&m_bitmap);                         //选入位图
        m_pdc->StretchBlt(m_rect.left+1,m_rect.top+1,m_rect.Width()-2,m_rect.Height()-2,&m_memdc,0,0,
                  m_size.bmWidth,  m_size.bmHeight,SRCCOPY);                 //绘制位图
        m_bitmap.DeleteObject();
            }
            else
            {
        m_pdc->FillSolidRect(m_rect,RGB(0x000000F9,0x000000F8,0x000000F7)); //填充菜单项背景
            }
        }

(6)向CIconMenu类中添加DrawSeparater方法,绘制分隔条,代码如下:

        void CIconMenu::DrawSeparater(CDC* m_pdc,CRect m_rect)
        {
            if (m_pdc != NULL)
            {
        m_rect.DeflateRect(25,0,0,0);                                     //设置绘制区域
        m_pdc->Draw3dRect(m_rect,RGB(255,0,0),RGB(0,0,255));              //绘制滚动条
            }
        }

(7)向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;
        }

(8)改写CMenu类的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;
                  }
                }
        }
        }

(9)改写CMenu类的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);                                      //绘制菜单项文本
                      DrawMenuTitle(m_dc,m_rect,"明日科技有限公司");                        //绘制菜单左侧标题
                      break;
                  }
                }
        }
        }

举一反三

根据本实例,读者可以:

开发具有背景颜色的菜单。

实例006 任务栏托盘弹出菜单

本实例是一个提高效率、人性化的程序

实例位置:光盘\mingrisoft\01\006

实例说明

在安装完瑞星、金山词霸等软件时,在系统的任务栏中会显示一个托盘图标。用户用鼠标右键单击托盘图标,就会弹出一个快捷菜单。本实例将实现一个任务栏托盘菜单,效果如图1.7所示。

图1.7 任务栏托盘弹出菜单

技术要点

要设计任务栏托盘菜单,需要使用Shell_NotifyIcon函数。该函数语法如下:

        WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
            DWORD dwMessage,
            PNOTIFYICONDATA pnid
        );

参数说明:

● dwMessage:表示发送的消息值,其可选值如下。

■ NIM_ADD:表示添加图标到任务栏。

■ NIM_DELETE:表示从任务栏区域删除一个图标。

■ NIM_MODIFY:表示修改任务栏区域的一个图标。

● pnid:是NOTIFYICONDATA结构指针。

NOTIFYICONDATA结构定义如下:

        typedef struct _NOTIFYICONDATA {
            DWORD cbSize;
            HWND hWnd;
            UINT uID;
            UINT uFlags;
            UINT uCallbackMessage;
            HICON hIcon;
            char szTip[64];
        } NOTIFYICONDATA, *PNOTIFYICONDATA;

参数说明:

● cbSize确定NOTIFYICONDATA结构的大小。

● hWnd表示接收任务栏菜单消息的窗口句柄。

● uID表示任务栏图标标识符。

● uFlags确定NOTIFYICONDATA结构中哪些成员是合法的。

● uCallbackMessage表示应用程序定义的消息标识符,系统将要发送该消息到hWnd表示的窗口。

● hIcon表示添加、修改或删除的图标句柄。

● szTip是工具提示文本。

实现过程

(1)新建一个基于对话框的应用程序。

(2)从CMenu类派生一个子类CIconMenu,改写DrawItem方法和MeasureItem方法重新绘制菜单。

(3)在对话框类中定义一个NOTIFYICONDATA结构变量m_traydata,在OnInitDialog方法中初始化m_traydata。

        m_traydata.cbSize=sizeof(NOTIFYICONDATA);                     //设置结构大小
        m_traydata.hIcon=AfxGetApp()->LoadIcon(IDI_TRAYICON);         //加载图标资源
        m_traydata.hWnd=m_hWnd;                                       //设置窗口句柄
        char  *m_str="系统管理";                                      //声明字符串
        //strlen +1表示将空字符复制到目标字符串中
        strncpy(m_traydata.szTip,m_str,strlen(m_str)+1);              //复制字符串
        m_traydata.uCallbackMessage=WM_TRARMESSAGE;                   //设置回传消息
        m_traydata.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP;               //设置合法参数

(4)在对话框的OnSysCommand方法中判断用户是否单击了“最小化”按钮,如果是,隐藏对话框,调用Shell_NotifyIcon函数向任务栏中添加图标,代码如下:

        void CTrayPopMenuDlg::OnSysCommand(UINT nID, LPARAM lParam)
        {
        if ((nID & 0xFFF0) == IDM_ABOUTBOX)
        {
                CAboutDlg dlgAbout;
                dlgAbout.DoModal();
        }
        else if((nID&0xFFF0)==SC_MINIMIZE)                  //如果是最小化消息
        {
                ShowWindow(SW_HIDE);                       //隐藏窗口
                Shell_NotifyIcon(NIM_ADD,&m_traydata);     //添加系统托盘图标
        }
        else
        {
                CDialog::OnSysCommand(nID, lParam);
        }
        }

(5)在对话框的消息映射部分添加映射宏。

ON_MESSAGE(WM_TRARMESSAGE,OnTrayMessage)

(6)向对话框中添加OnTrayMessage方法,代码如下:

        void CTrayPopMenuDlg::OnTrayMessage(WPARAM wParam, LPARAM lParam)
        {
        if(lParam==WM_LBUTTONDOWN)                        //如果在托盘图标上单击鼠标左键
        {
                ShowWindow(SW_RESTORE);                   //恢复显示窗口
        }
        else if(lParam==WM_RBUTTONDOWN)                    //如果在托盘图标上单击鼠标右键
        {
                CPoint m_point;
                ::GetCursorPos(&m_point);                 //获得鼠标当前位置
                CIconMenu*m_submenu=(CIconMenu*)m_menu.GetSubMenu(0);   //获得子菜单
                m_submenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,
                      m_point.x,m_point.y,AfxGetApp()->m_pMainWnd,TPM_LEFTALIGN);   //显示弹出菜单
        }
        }

举一反三

根据本实例,读者可以:

修改和删除系统托盘菜单。