WIN 下的超动态菜单(三)代码
作者:黄山松,发表于博客园:http://www.cnblogs.com/tomview/
超动态菜单的含义
auto_dynamenu 是一个封装了 WINDOWS 菜单功能的 C++ 类库,用于动态生成 WINDOWS 菜单。所谓的动态有两个含义:
(1)菜单是动态创建和生成的;
(2)菜单本身的内容也是动态的,是可以根据程序的状态动态确定的
因此这个封装类同把菜单预定义存储在 XML, INI, 资源内,运行时调用显示是有很大差别的,这个类方便动态显示同程序状态有关的,动态变化的菜单。
譬如我的 高闯(高清智能交通违章检测抓拍系统)程序中,可以自动或手动录像,在录像没有开始的时候,菜单是如下的样子:
当开始了录像之后,菜单不但可以显示开始录像的功能,还可以显示当前录像的文件名,长度,大小等信息,并增加停止录像的菜单,具体显示为如下样子:
当同时启动了自动的监控录像的时候,菜单还可以显示如下:
当录像结束之后,还可以增加一个显示播放上一个录像的菜单:
封装类的接口定义考虑
封装类的接口定义是从易用,集中的角度考虑的:
(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
下载
可以在下面的链接下载代码和示例程序:
http://files.cnblogs.com/files/tomview/dynamenu_20160524.rar