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
下载
可以在下面的链接下载代码和示例程序:
http://files.cnblogs.com/files/tomview/dynamenu_20160524.rar
WIN 下的超动态菜单(三)代码的更多相关文章
- WIN 下的超动态菜单(一)
WIN 下的超动态菜单(一)介绍 WIN 下的超动态菜单(二)用法 WIN 下的超动态菜单(三)代码 作者:黄山松,发表于博客园:http://www.cnblogs.com/tomview/ ...
- WIN 下的超动态菜单(二)用法
WIN 下的超动态菜单(一)简介 WIN 下的超动态菜单(二)用法 WIN 下的超动态菜单(三)代码 作者:黄山松,发表于博客园:http://www.cnblogs.com/tomview/ ...
- JS打字效果的动态菜单代码分享
这篇文章主要介绍了JS打字效果的动态菜单,推荐给大家,有需要的小伙伴可以参考下. 这是一款基于javascript实现的打字效果的动态菜单特效代码,分享给大家学习学习. 小提示:浏览器中如果不能正常运 ...
- MFC之创建多级动态菜单
一开始以我是这样做的,结果是错误的: 这段代码第一次点击时,会在第6个位置创建MFC菜单,我本以为再次点击,menu->GetSubMenu(5)返回的值就不会为空了,但事实是它返回了NULL, ...
- .net core3.1 abp动态菜单和动态权限(动态菜单实现和动态权限添加) (三)
我们来创建动态菜单吧 首先,先对动态菜单的概念.操作.流程进行约束:1.Host和各个Tenant有自己的自定义菜单2.Host和各个Tenant的权限与自定义菜单相关联2.Tenant有一套默认的菜 ...
- SAAS云平台搭建札记: (三) AntDesign + .Net Core WebAPI权限控制、动态菜单的生成
我们知道,当下最火的前端框架,非蚂蚁金服的AntDesign莫属,这个框架不仅在国内非常有名,在国外GitHub上React前端框架也排名第一.而且这个框架涵盖了React.Vue.Angular等多 ...
- 20款jquery下拉导航菜单特效代码分享
20款jquery下拉导航菜单特效代码分享 jquery仿京东商城左侧分类导航下拉菜单代码 jQuery企业网站下拉导航菜单代码 jQuery css3黑色的多级导航菜单下拉列表代码 jquery响应 ...
- js下拉框二级关联菜单效果代码具体实现
这篇文章介绍了js下拉框二级关联菜单效果代码具体实现,有需要的朋友可以参考一下 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transit ...
- Delphi编程中动态菜单要点归纳
一.创建菜单并添加项目 在设计程序时,有时需要动态创建菜单, 通常使用以下的语句: PopupMenu1 := TPopupMenu.Create(Self); Item := TMenuIte ...
随机推荐
- 对于Fragment的一些理解
前言 Fragment想必大家不陌生吧,在日常开发中,对于Fragment的使用也很频繁,现在主流的APP中,基本的架构也都是一个主页,然后每个Tab项用Fragment做布局,不同选项做切换,使用起 ...
- C++ this指针的用法
this指针的含义及其用法: 1. this指针是一个隐含于每一个成员函数中的特殊指针.它指向正在被该成员函数操作的那个对象.2. 当对一个对象调用成员函数时,编译程序先将对象的地址赋给this指针, ...
- 【转】asp.net(c#)加密解密算法之sha1、md5、des、aes实现源码详解
原文地址:http://docode.top/Article/Detail/10003 目录: 1..Net(C#)平台下Des加密解密源代码 2..Net(C#)平台下Aes加密解密源代码 3..N ...
- 增加删除字段修改字段名,修改表结构,非常用SQL语句技巧总结
1.为数据表添加一个新字段 Alter TABLE [dbo].[CustomerBackupConfig] Add [Stamp] [timestamp] NULL GO 2.为数据表添加两个新字段 ...
- [WCF编程]1.WCF入门示例
一.WCF是什么? Windows Communication Foundation(WCF)是由微软开发的一系列支持数据通信的应用程序框架,整合了原有的windows通讯的 .net Remotin ...
- iOS开发之CocoaPods的安装与使用
前言部分 iOS开发时,项目中会引用许多第三方库,CocoaPods(https://github.com/CocoaPods/CocoaPods) 可以用来方便的统一管理这些第三方库. 一.安装 由 ...
- asp.net 301重定向代码
/// <summary> ///重定向代码 /// </summary> /// <param name="sender"></para ...
- 转载:《TypeScript 中文入门教程》 5、命名空间和模块
版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变 ...
- 推荐一个内容滚动jquery插件
myslider是一个内容滚动jquery插件,版本0.1.2的每次滚动内容是一行内容,可以是文字,可以是一个链接,还可以是图片. 官方网址:http://keleyi.com/jq/myslider ...
- jQueryMobile示例页面代码
这是一个jQueryMobile示例页面 示例效果:http://hovertree.com/texiao/jquerymobile/ 可以在手机或者触屏浏览器查看效果. 以下是HTML代码: < ...