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 ...
随机推荐
- Cache-Aside Pattern(缓存模式)
Load data on demand into a cache from a data store. This pattern can improve performance and also he ...
- do{...}while(0)的妙用
在学习第一门编程语言时,就已经介绍了顺序分支.条件分支.循环分支.比如循环分支有for.while.do-while语句.在随后的学校及工作中,如果手工循环一般使用for.while,很少使用do-w ...
- [Asp.net 5] Logging-日志系统的基本架构(下)
接上节内容,我们继续讲解日志的其他部分. ILoggerProvider以及扩展类 我们在上节的架构图上并没有看到有直接实现该接口的实现类.那么如果将Logger类直接使用会有什么结果呢? var f ...
- Android网络编程1
最近在自学Android开发,从这篇开始作为我学习android开发的笔记,来记录学习过程中遇到的问题点和其解决的方法: Ui界面代码 <?xml version="1.0" ...
- [WCF编程]9.性能与限流
一.性能概述 WCF服务的性能取决于很多因素.出了CPU.RAM和网络性能等常见的因素外,实例上下文模式.并发模式.数据契约的设计或使用的绑定等与WCF有关的因素都起着重要的作用. 实例上下文模式用来 ...
- input框中的name和id的区别
1. 可以说几乎每个做过Web开发的人都问过,到底元素的ID和Name有什么区别阿?为什么有了ID还要有Name呢?! 而同样我们也可以得到最classical的答案:ID就像是一个人的身份证号码,而 ...
- 从零开始学Python第一周:Python基础(上)
Python语法基础(上) 一,Python的变量 (1)创建变量 变量的含义:存储信息的地方 创建变量并赋值 x = 1 print x x = 123 #再次赋值 print x (2)使用变量 ...
- Struts2基于注解的Action配置
使用注解来配置Action的最大好处就是可以实现零配置,但是事务都是有利有弊的,使用方便,维护起来就没那么方便了. 要使用注解方式,我们必须添加一个额外包:struts2-convention-plu ...
- c#中奖算法的实现
算法名称 Alias Method public class AliasMethod { /* The probability and alias tables. */ private int[] _ ...
- 设计模式-观察者模式(Observer Model)
文 / vincentzh 原文连接:http://www.cnblogs.com/vincentzh/p/6031844.html LZ刚刚开始心热的开启了博客之路,想着记点流水账,可帝都的天都冷成 ...