WIN 下的超动态菜单(三)代码
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
下载
可以在下面的链接下载代码和示例程序:
https://files.cnblogs.com/files/tomview/dynamenu_20160524.rar