WIN 下的超动态菜单(三)代码

WIN 下的超动态菜单(一)简介

WIN 下的超动态菜单(二)用法

WIN 下的超动态菜单(三)代码

作者:黄山松,发表于博客园:http://www.cnblogs.com/tomview/

超动态菜单的含义

         auto_dynamenu 是一个封装了 WINDOWS 菜单功能的 C++ 类库,用于动态生成 WINDOWS 菜单。所谓的动态有两个含义:

(1)菜单是动态创建和生成的;

(2)菜单本身的内容也是动态的,是可以根据程序的状态动态确定的

        因此这个封装类同把菜单预定义存储在 XML, INI, 资源内,运行时调用显示是有很大差别的,这个类方便动态显示同程序状态有关的,动态变化的菜单。

        譬如我的 高闯(高清智能交通违章检测抓拍系统)程序中,可以自动或手动录像,在录像没有开始的时候,菜单是如下的样子:

        image

        当开始了录像之后,菜单不但可以显示开始录像的功能,还可以显示当前录像的文件名,长度,大小等信息,并增加停止录像的菜单,具体显示为如下样子:

        image

        当同时启动了自动的监控录像的时候,菜单还可以显示如下:

        image

        当录像结束之后,还可以增加一个显示播放上一个录像的菜单:

        image

封装类的接口定义考虑

        封装类的接口定义是从易用,集中的角度考虑的:

(1)封装在类里面并在头文件中直接嵌入代码,是为了方便使用,包含头文件就可以了

(2)接口只设计了一个,避免 CreateMenu, InsertMenu, CheckMenu, 等有很多接口函数的封装方式,简化应用

(3)菜单定义和菜单处理的代码可以放在一起,而不是分散在代码中的各个不同地方,方便代码维护

代码的局限

(1)首要的局限就是这是一个 C++ 的 WINDOWS 代码封装类,现在谁还在 WINDOWS 下用 C++ 编程呢?

(2)自动确定菜单位置的代码部分根据控件类名确定菜单显示位置,新版的控件类名可能会有变化,需要更新代码

        当前代码中处理的控件类有:BUTTON,ThunderCommandButton,ThunderRT6CommandButton,AfxOleControl42sd,Afx:4000000:8,Afx:400000:8,AfxWnd42sd,ToolbarWindow32,TrayNotifyWnd,Shell_TrayWnd,NotifyIconOverflowWindow,SysTreeView32,SysListView32,SysTabControl32。

auto_dynamenu 代码

#ifndef __HSS_AUTO_DYNAMIC_MENU_HSS__
#define __HSS_AUTO_DYNAMIC_MENU_HSS__

/**************************************************************************************************\
动态创建菜单,并获取选择的菜单项加以处理

  作者:黄山松,http://www.cnblogs.com/tomview/

  用法见示例程序,及作者在 博客园 的系列文章

  "WIN 下的超动态菜单"
\**************************************************************************************************/


class auto_dynamenu
{
public:

    /**************************************************************************************************\
    * static int           :    返回值,表明选择了哪个菜单项
    * dynamenu             :    
    * HWND hWnd            :    当前窗口句柄
    * LPPOINT pPoint       :    显示菜单的位置,通常为0即可,自动确定显示的菜单位置
    * char* pszMenu         :    表明动态菜单内容的菜单字符串
    * int nDefaultMode     :    自动更新菜单选择标记的模式,0 无,1 等于模式,2 位模式
    * int nDefaultValue    :    缺省值,根据这个值,按照 nDefaultMode 来显示菜单项的选择标记
    \**************************************************************************************************/
    static int dynamenu(HWND hWnd, LPPOINT pPoint, char* pszMenu, int nDefaultMode, int nDefaultValue)
    {
        int length = strlen(pszMenu);
        for (int i = 0 ; i < length ; i ++)
        {
            if (pszMenu[i] == '\n')
                pszMenu[i] = '\0';
        }

        MENUITEMINFO mii;
        memset(&mii, 0, sizeof(mii));
        mii.cbSize = sizeof(mii);
        mii.fMask = MIIM_TYPE|MIIM_ID|MIIM_STATE|MIIM_DATA;
        mii.fType = MFT_STRING;
        HMENU hMenu = ::CreatePopupMenu();
        HMENU hParent = hMenu;

        char szSubMenu[256];
        MENUITEMINFO miis;
        memset(&miis, 0, sizeof(miis));
        miis.cbSize = sizeof(miis);
        miis.fMask = MIIM_SUBMENU | MIIM_TYPE;
        miis.dwTypeData = szSubMenu;

        char* psz = pszMenu;
        length = strlen(psz);

        int index = 0;
        int cmd = 0;
        BOOL bChecked = FALSE;
        BOOL bGrayed = FALSE;
        BOOL bSeperator = FALSE;
        BOOL bRadio = FALSE;
        BOOL bColumn = FALSE;

        while(length)
        {
            hParent = hMenu;
            bChecked = FALSE;
            bGrayed = FALSE;
            bSeperator = FALSE;
            bRadio = FALSE;
            bColumn = FALSE;

            char* p;

            //2015年3月4日 增加,允许在前面有某些控制字符/////////////////////////////////////////////////////
            char ctrl = 0;
            if (psz[0] == '^' || psz[0] == '#' || psz[0] == '*')
            {
                ctrl = psz[0];
                psz ++;
                length --;
            }
            /////////////////////////////////////////////////////////////////////////////////////////

            while(p = strchr(psz, '|'))
            {
                *p = '\0';

                miis.dwTypeData = szSubMenu;
                miis.cch = sizeof(szSubMenu);
                int sindex = 0;
                for (BOOL fSu = GetMenuItemInfo(hParent, sindex, TRUE, &miis) ; fSu ; )
                {
                    if (miis.dwTypeData && _stricmp(psz, miis.dwTypeData) == 0)
                    {
                        if (miis.hSubMenu)
                            hParent = miis.hSubMenu;
                        break;
                    }
                    sindex ++;
                    miis.dwTypeData = szSubMenu;
                    miis.cch = sizeof(szSubMenu);
                    fSu = GetMenuItemInfo(hParent, sindex, TRUE, &miis);
                }
                
                if (!fSu)
                {
                    //没找到,添加
                    HMENU hSubMenu = ::CreatePopupMenu();
                    ::AppendMenu(hParent, MF_POPUP, (UINT)hSubMenu, psz);
                    hParent = hSubMenu;
                }

                length -= (p - psz + 1);
                psz = p + 1;
            }

            while(length)
            {
                //2015年3月4日 新增加允许最前面有三种前置控制字符///////////////////////////////////////
                if (ctrl == '^')
                {
                    bChecked = TRUE;
                    ctrl = 0;
                }
                else if (ctrl == '*')
                {
                    bRadio = TRUE;
                    bChecked = TRUE;
                    ctrl = 0;
                }
                else if (ctrl == '#')
                {
                    bGrayed = TRUE;
                    ctrl = 0;
                }
                ////////////////////////////////////////////////////////////////////////////////////

                if (psz[0] == '-')        //Break
                {
                    //mii.fType = MFT_MENUBARBREAK | MFT_STRING;
                    bColumn = TRUE;
                    psz += 1;
                    length -= 1;
                    continue;
                }
                else if (psz[0] == '`')    //2009年10月10日 忽略这个
                {
                    psz += 1;
                    length -= 1;
                    continue;
                }
                else if (psz[0] == '~')        //Seperator
                {
                    bSeperator = TRUE;
                    psz += 1;
                    length -= 1;
                    if (length == 0)
                    {
                        //仅仅一个seperator
                        mii.fType = MFT_SEPARATOR;
                        ::InsertMenuItem(hParent, index++, true, &mii);
                        psz += length + 1;
                        length = strlen(psz);
                        break;
                    }
                    continue;
                }
                else if (psz[0] == '^')        //Check
                {
                    bChecked = TRUE;
                    psz += 1;
                    length -= 1;
                    continue;
                }
                else if (psz[0] == '#')        //Grayed
                {
                    bGrayed = TRUE;
                    psz += 1;
                    length -= 1;
                    continue;
                }
                else if (psz[0] == '*')
                {
                    bRadio = TRUE;
                    bChecked = TRUE;
                    psz += 1;
                    length -= 1;
                    continue;
                }

                p = strchr(psz, '=');
                if (p)
                {
                    if (p[1] == '0' && (p[2] == 'X' || p[2] == 'x'))
                    {
                        sscanf(p+1, "%X", &cmd);
                    }
                    else if (p[1] == '\"')    //2009年3月10日 强迫是字符串,不管是不是能够被转化为数字
                    {
                        cmd = (DWORD)(p+2);
                    }
                    else if (sscanf(p+1, "%d", &cmd) == 0)
                    {
                        cmd = (DWORD)(p+1);
                    }
                    *p = '\0';
                }
                else
                {
                    cmd = (DWORD)psz;
                }

                if (bSeperator)
                {
                    mii.fType = MFT_SEPARATOR;
                    ::InsertMenuItem(hParent, index++, true, &mii);
                    //mii.fType = MFT_STRING;
                }

                mii.wID = index + 1;
                mii.dwTypeData = psz;
                mii.dwItemData = (DWORD)cmd;
                if (bRadio)
                    mii.fType = MFT_STRING|MFT_RADIOCHECK;
                else
                    mii.fType = MFT_STRING;

                mii.fState = bGrayed ? MFS_DISABLED : MFS_ENABLED;

                if (bChecked)
                {
                    mii.fState |= MF_CHECKED;
                }
                else
                {
                    switch(nDefaultMode)
                    {
                    case 0:                //no
                        break;
                    case 2:                //&
                        if (nDefaultValue & cmd)
                            mii.fState |= MF_CHECKED;
                        break;
                    case 1:                //=
                    default:            
                        if (nDefaultValue == cmd)
                            mii.fState |= MF_CHECKED;
                        break;
                    }
                }

                if (bColumn)
                {
                    mii.fType |= MFT_MENUBARBREAK;
                }

                ::InsertMenuItem(hParent, index++, true, &mii);

                psz += length + 1;

                length = strlen(psz);

                break;
            }
        }

        POINT pt;
        if (pPoint)
        {
            pt = *pPoint;
        }
        else
        {
            GetCursorPos(&pt);
            HWND hChild = WindowFromPoint(pt);
            if (hChild)// && hChild != hWnd)        ???
            {
                char szClass[256];
                int n = GetClassName(hChild, szClass, sizeof(szClass));
                if (n)
                {
                    RECT rect;
                    if (_stricmp(szClass, "BUTTON") == 0
                        || _stricmp(szClass, "ThunderCommandButton") == 0
                        || _stricmp(szClass, "ThunderRT6CommandButton") == 0
                        )
                    {
                        if (::GetWindowRect(hChild, &rect))
                        {
                            pt.x = rect.left;
                            pt.y = rect.bottom;
                        }
                    }
                    else if (_stricmp(szClass, "AfxOleControl42sd") == 0
                        || _stricmp(szClass, "Afx:4000000:8") == 0
                        || _stricmp(szClass, "Afx:400000:8") == 0
                        || _stricmp(szClass, "AfxWnd42sd") == 0
                        )
                    {
                        //是个OCX控件
                        //看看大小,如果比较小,像个按钮,则在下面显示
                        if (GetWindowRect(hChild, &rect))
                        {
                            int w = rect.right - rect.left;
                            int h = rect.bottom - rect.top;
                            if (w > h && ((w < 200 && h < 100) || w > h * 2))
                            {
                                pt.x = rect.left;
                                pt.y = rect.bottom;
                            }
                        }
                    }
                    else if (_stricmp(szClass, "ToolbarWindow32") == 0)
                    {
                        //2014年6月5日 在win7中,可能在:溢出通知区域 ToolbarWindow32
                        //父窗口类: NotifyIconOverflowWindow
                        //再父窗口类:#32769 (Desktop)

                        HWND hp = GetParent(GetParent(hChild));

                        if (hp)    //win7的情况,在溢出通知栏,没有父窗口(spy++显示父窗口为桌面)
                        {
                            GetClassName(hp, szClass, sizeof(szClass));

                            if (_stricmp(szClass, "TrayNotifyWnd") == 0                //WinXp
                                || _stricmp(szClass, "Shell_TrayWnd") == 0            //Win2000
                                )
                            {
                                //在通知区域显示的图片上面显示的
                                RECT rcp;
                                GetWindowRect(hp, &rcp);
                                pt.y = rcp.top - 1;
                            }
                            else
                            {
                                hp = 0;
                            }
                        }

                        if (hp == 0)
                        {
                            //判断是不是在win7的溢出通知区域
                            hp = GetParent(hChild);
                            GetClassName(hp, szClass, sizeof(szClass));
                            if (_stricmp(szClass, "NotifyIconOverflowWindow") == 0)
                            {
                                //win7的溢出窗口类,直接在当前位置显示菜单即可
                            }
                            else
                            {
                                POINT ptc = pt;
                                ::ScreenToClient(hChild, &ptc);
                                //但是在win7里面,为什么向图标的那个ToolbarWindow32发送tb_hittest会导致程序出错呢?
                                int index = SendMessage(hChild, TB_HITTEST, 0, (LPARAM)&ptc);
                                if (index >= 0)
                                {
                                    if (SendMessage(hChild, TB_GETITEMRECT, index, (LPARAM)&rect))
                                    {
                                        pt.x = rect.left;
                                        pt.y = rect.bottom + 1;
                                        ::ClientToScreen(hChild, &pt);
                                    }
                                }
                            }
                        }
                    }
                    else if (_stricmp(szClass, "SysTreeView32") == 0)
                    {
                        POINT ptc = pt;
                        ::ScreenToClient(hChild, &ptc);
                        TVHITTESTINFO ht;
                        ht.pt = ptc;
                        ht.hItem = 0;
                        ht.flags = 0;
                        HTREEITEM hItem = TreeView_HitTest(hChild, &ht);
                        if (hItem)
                        {
                            RECT rc;
                            if (TreeView_GetItemRect(hChild, hItem, &rc, TRUE))
                            {
                                //TreeView_GetItem
                                pt.x = rc.left;
                                //pt.x = ptc.x;
                                if (pt.x > 20)
                                    pt.x -= 20;
                                pt.y = rc.bottom + 1;
                                ::ClientToScreen(hChild, &pt);
                            }
                        }
                    }
                    else if (_stricmp(szClass, "SysListView32") == 0)
                    {
                        POINT ptc = pt;
                        ::ScreenToClient(hChild, &ptc);
                        LVHITTESTINFO ht;
                        ht.pt = ptc;
                        ht.iItem = 0;
                        ht.iSubItem = 0;
                        ht.flags = 0;
                        
                        int iItem = ListView_SubItemHitTest(hChild, &ht);
                        if (iItem != -1)
                        {
                            RECT rc;
                            if (ListView_GetSubItemRect(hChild, ht.iItem, ht.iSubItem, LVIR_BOUNDS, &rc))
                            {
                                pt.x = rc.left;
                                pt.y = rc.bottom + 1;
                                ::ClientToScreen(hChild, &pt);
                            }
                        }
                    }
                    else if (_stricmp(szClass, "SysTabControl32") == 0)
                    {
                        POINT ptc = pt;
                        ::ScreenToClient(hChild, &ptc);
                        TCHITTESTINFO ti;
                        ti.flags = 0;
                        ti.pt = ptc;

                        int iItem = TabCtrl_HitTest(hChild, &ti);
                        if (iItem != -1)
                        {
                            RECT rc;
                            if (TabCtrl_GetItemRect(hChild, iItem, &rc))
                            {
                                pt.x = rc.left;
                                pt.y = rc.bottom + 1;
                                ::ClientToScreen(hChild, &pt);
                            }
                        }
                    }
                }
            }
        }

        index = ::TrackPopupMenuEx(hMenu, TPM_RETURNCMD|TPM_NONOTIFY, pt.x, pt.y, hWnd, NULL);

        if (index)
        {
            mii.fMask = MIIM_DATA | MIIM_ID;
            if (GetMenuItemInfo(hMenu, index, FALSE, &mii))
            {
                index = mii.dwItemData;
                if (nDefaultMode == 2)
                {
                    //index 可能返回0,表明所有的标记为都被取消选中了
                    if (nDefaultValue & index)
                    {
                        index = nDefaultValue & (~index);
                        //if (index == 0)
                        //    index = INT_MAX;
                    }
                    else
                    {
                        index = nDefaultValue | index;
                    }
                }
                else if (nDefaultMode == 1)
                {
                    if (index == 0)
                        index = nDefaultValue;
                }
                else if (index == 0)        //2007-09-10 选择了0 的话,返回INT_MAX,而返回0表示取消或者出错
                {
                    index = INT_MAX;
                }
            }
            else
            {
                index = 0;
            }
        }
        else if (nDefaultMode == 1)
        {
            index = nDefaultValue;
        }
        else if (nDefaultMode == 2)
        {
            index = nDefaultValue;
        }

        ::DestroyMenu(hMenu);

        return index;
    };
};


#endif

下载

可以在下面的链接下载代码和示例程序:

https://files.cnblogs.com/files/tomview/dynamenu_20160524.rar

posted @ 2016-05-26 10:54  shansong.huang  阅读(688)  评论(0编辑  收藏  举报