现在比较流行的软件都会在系统托盘上显示一个图标,左键点击该图标时可以显示/隐藏软件主界面,节省任务栏的空间;右键点击该图标会出现一个菜单,显示一些常用的功能。那么这是怎么实现的呢?其实很简单,只需要一个API函数就搞定了,跟我一起看吧~
首先看一个结构体:
typedef struct _NOTIFYICONDATA {
DWORD cbSize;
HWND hWnd;
UINT uID;
UINT uFlags;
UINT uCallbackMessage;
HICON hIcon;
TCHAR szTip[64];
DWORD dwState; //Version 5.0
DWORD dwStateMask; //Version 5.0
TCHAR szInfo[256]; //Version 5.0
union {
UINT uTimeout; //Version 5.0
UINT uVersion; //Version 5.0
} DUMMYUNIONNAME;
TCHAR szInfoTitle[64]; //Version 5.0
DWORD dwInfoFlags; //Version 5.0
} NOTIFYICONDATA, *PNOTIFYICONDATA;
使用该结构指定托盘图标的相关信息,其中Version 5.0的部分表示的是Shell DLL的版本号,可以用DllGetVersion获得当前系统的DLL版本。出于兼容的考虑,该部分很少使用。
cbSize: NOTIFYICONDATA结构的大小,sizeof(NOTIFYICONDATA)
hWnd:接收托盘通知的窗口句柄
uID:托盘图标的ID号,该ID与hWnd一起唯一标识了一个托盘图标的身份,因此该ID号不能重复,一般使用与该图标相关联的菜单的ID
uFlags:为下列值的组合
NIF_ICON:表示结构中的hIcon有效
NIF_MESSAGE:表示结构中的uCallbackMessage有效
NIF_TIP:表示结构中的szTip有效
NIF_STATE:表示结构中的dwState和dwStateMask有效
NIF_INFO:使用气球型的托盘提示代替传统的方框型托盘提示.结构中的szInfo, uTimeout, szInfoTitle,和dwInfoFlags有效
uCallbackMessage:托盘消息,当托盘区域有鼠标事件(如鼠标移动,单击等)产生时,会向接收窗口发送该消息,进行相应的处理
hIcon:托盘图标句柄
szTip:托盘提示字符
好了,就介绍这么多,下面接收这个API函数Shell_NotifyIcon:
BOOL Shell_NotifyIcon(
DWORD dwMessage,
PNOTIFYICONDATA pnid
);
dwMessage:为下列值之一
NIM_ADD:添加托盘图标
NIM_DELETE:删除图盘图标
NIM_MODIFY:修改托盘图标
NIM_SETFOCUS:Version 5.0
NIM_SETVERSION:Version 5.0
pnid:NOTIFYICONDATA结构的指针
好了,下面言归正传,开始着手编程了~
我们分为以下几步逐步分析:
初始化
添加/修改/移除图标
添加托盘消息响应函数
添加菜单消息处理函数
(1)初始化
首先要声明一个NOTIFYICONDATA成员变量:
NOTIFYICONDATA m_nid;
然后在资源面板中添加一个菜单资源IDR_TRAYMENU和一个图标资源IDI_RED
由于我们要处理鼠标消息,因此先定义一个用户自定义消息:
#define UM_ TRAYNOTIFICATION (WM_USER+100)
然后对该结构的成员进行初始化:
void InitTray()
{
//初始化m_nid
m_nid.cbSize = sizeof(NOTIFYICONDATA);
m_nid.hWnd = this->m_hWnd;
m_nid.uID = IDR_TRAYMENU;
m_nid.uFlags = NIF_ICON|NIF_TIP|NIF_MESSAGE;
m_nid.hIcon = AfxGetApp()->LoadIcon(IDI_RED);
strcpy (m_nid.szTip, "我的托盘听我的");
m_nid.uCallbackMessage = UM_TRAYNOTIFICATION;
}
上面的程序我就不多说了,相信大家都能看懂。
(2)添加/修改/移除图标
这个更简单,直接调用Shell_NotifyIcon函数就可以了:
//向托盘添加图标
void AddTray()
{
Shell_NotifyIcon(NIM_ADD, &m_nid);
}
//移除托盘图标,在程序退出时一定要记得调用,否则图标会残留在托盘上
void RemoveTray()
{
Shell_NotifyIcon(NIM_DELETE, &m_nid);
}
//修改图标,QQ登陆时托盘上的动态效果不用我教了吧
void ModifyTray(UINT uId)//参数为要显示的Icon的ID号
{
m_nid.hIcon = AfxGetApp()->LoadIcon(uId);
Shell_NotifyIcon(NIM_MODIFY, &m_nid);
}
(3)添加托盘消息响应函数
这个其实就是响应自定义消息,忘了?好,那我们一起复习一下吧~
首先定义消息标识符(第1步已经定义过了):
#define UM_ TRAYNOTIFICATION (WM_USER+100)
然后在头文件的DECLARE_MESSAGE_MAP()之前定义消息响应函数:
afx_msg LRESULT OnTrayNotification(WPARAM wId, LPARAM lEvent);
其中两个参数分别为图标ID号和鼠标事件
最后在cpp文件里实现该函数:
LRESULT CXXXDlg::OnTrayNotification(WPARAM wId, LPARAM lEvent)
{
if(wId!=m_nid.uID
|| (lEvent!=WM_LBUTTONUP && lEvent!=WM_RBUTTONUP))
return 0;
//加载菜单
CMenu menu;
if(!menu.LoadMenu(wId))
return 0;
//获取弹出菜单
CMenu *pSubMenu = menu.GetSubMenu(0);
if(!pSubMenu)
return 0;
if(lEvent == WM_RBUTTONUP)
{
//设置默认菜单项
::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);
//获取鼠标位置
CPoint mouse;
GetCursorPos(&mouse);
//设置快捷菜单
::SetForegroundWindow(m_nid.hWnd);
::TrackPopupMenu(pSubMenu->m_hMenu, 0, mouse.x, mouse.y, 0, m_nid.hWnd, NULL);
}
else
{
::SendMessage(m_nid.hWnd, WM_COMMAND, pSubMenu->GetMenuItemID(0), 0);
}
return 1;
}
下面逐句解释上面这段代码:
首先判断发送该消息的图标的ID是否等于m_nid中的ID号,再判断接收到的鼠标消息是否为左键单击消息WM_LBUTTONUP或右键单击消息WM_RBUTTONUP,如果不是则不对该消息进行响应。
然后加载菜单,右键单击时显示该菜单。先用LoadMenu()加载顶层菜单,再用GetSubMenu()获得一级子菜单。
当鼠标事件为右键单击时,显示菜单,否则(即左键单击时)用::SendMessage()向接收窗口发送命令消息,进行相应的处理。::SetMenuDefaultItem()用于设置菜单的默认选择项,以粗体显示。最后用::TrackPopupMenu()函数显示右键菜单。该菜单可能不会像通常那样马上消失,这是因为从弹出菜单接收消息的窗口必须是前景窗口。调用::SetForegroundWindow()函数就可以纠正该错误。
(4)添加菜单消息处理函数
在右键菜单中选择一项后,就会发送命令消息WM_COMMAND,在ClassWizard里添加WM_COMMAND的消息处理函数:
BOOL CZYJReportDlg::OnCommand(WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
switch(LOWORD(wParam))
{
case ID_SHOWHIDE:
ShowWindow(m_bShow?SW_HIDE:SW_SHOW);
m_bShow = !m_bShow;
break;
case ID_EXIT:
RemoveTray();
PostQuitMessage(0);
break;
case ID_ABOUT:
CAboutDlg about;
about.DoModal();
break;
}
return CDialog::OnCommand(wParam, lParam);
}
其中第一个参数wParam的低字节LOWORD(wParam)标识了所选子菜单的ID,根据所选的菜单项执行相应的程序。
OK,以上介绍了写托盘程序的完整过程,是不是很简单呢?当然托盘编程的内容还远不止这些,比如Version 5.0的部分,还可以添加很多漂亮的效果,欢迎大家跟我一起探讨!