分享
 
 
 

VC++6.0中实现将应用程序的图标加入到Windows的系统托盘中

王朝厨房·作者佚名  2007-01-04
窄屏简体版  字體: |||超大  

西安二炮工程学院 俞俊军 张 毅

摘要

本文对如何将应用程序的图标加入到Windows的系统托盘中做了较为详细的介绍,

然后给出了一个C++类以方便的实现该功能,并在VC++6.0中给出了一个应用程序

实例来体现其具体实现过程。同时该应用程序实例还讲解了如何在托盘中实现动

画图标以及在程序中关闭计算机的技术。

关键词:系统托盘 动画图标

Windows98桌面的系统托盘位于任务栏的右侧,即Windows98桌面的右下方。它常

用来显示一些系统的状态。如:系统时间,音量控制以及其它的一些图标(依个

人机器安装的软件而不定),如下图为笔者的Windows98系统托盘。(图略)

常常能见到一些优秀的软件在运行后会将其应用程序图标加入到系统托盘中,如

金山词霸。如果能将自己编写的应用程序的图标也加入到系统托盘中,将会使你

的程序显得很有专业水准。

其实这并不困难,与系统托盘通信的函数只有一个:

Shell_NotifyIcon (UINT message, NOTIFYICONDATA &m_nid);

首先看一下该函数的两个参数。

第一个参数message可以取以下值:

NIM_ADD 向托盘中加入一个图标;

NIM_MODIFY 修改托盘中的图标

NIM_DELETE 从托盘中删除一个图标

第二个参数m_nid是NOTIFYICONDATA结构的一个引用。该结构的原型如下:

typedef struct _NOTIFYICONDATA

{

DWORD cbSize;// 结构的大小,必须在程序中给出

HWND hWnd;

//是你程序中将要接收托盘消息的窗口句柄

UINT uID;

// 应用程序中定义的托盘图标ID,此参数用作标识

UINT uFlags;

//设置属性,低三位有意义,0--7,如下:

//第一位//#define NIF_MESSAGE 0x1

// uCallbackMessage参数有效

//第二位//#define NIF_ICON 0x2 // hIcon参数有效

//第三位//#define NIF_TIP 0x4 // szTip参数有效

UINT uCallbackMessage;

// 自定义的消息ID值,一定不要与以有的消息ID相重。

HICON hIcon;

//显示在系统托盘上的Icon的句柄,可以为系统的 IDI_WINLOGO等

CHAR szTip[64]; // 用于图标显示的提示字符串

} NOTIFYICONDATA;

为了接收到来自托盘的通知消息你可以将uCallbackMessage设定为你所定义的消息

ID值,同时设定NIF_MESSAGE标志。这样当用户在你的托盘图标上移动或按下鼠标

时,Windows将发出消息:该消息的 messageID是你在uCallbackMessage中定义的

值;wParam是你定义的uID值;而lParam是鼠标事件(如WM_LBUTTONDOWN),这样你

的应用程序就能响应该事件了。

因此,为了将自己的应用程序加入到系统托盘中,首先得建立一处理托盘通知消息

的窗口对象,然后将窗口对象与你自己的托盘通知消息联系起来并建立相应的托盘

通知消息映射机制,以便你的窗口对象能处理相应的事件。

可以看到结构体NOTIFYICONDATA中,其成员变量hWnd,uID,uFlags均用于在窗口对

象与你自己的托盘通知消息之间建立联系,而成员变量uCallbackMessage则必须是

对应于你的窗口对象的托盘通知消息ID值。

于是要完成的工作有:

(1)建立一处理托盘通知消息的窗口对象;

(2)建立一结构体NOTIFYICONDATA变量,并给变量的相应域赋值以在托盘通知消

息与窗口对象之间建立联系;

(3)建立相应的托盘通知消息映射机制;

(4)调用Shell_NotifyIcon函数以在系统托盘中加入、修改或删除图标;

(5)当然别忘了在你的窗口对象中编写相应的事件响应函数。

因此,可以编写一C++类来实现以上功能以简化编程同时提高代码的可重用性。以

下为该类代码:

class CTrayIcon : public CCmdTarget {

protected:

DECLARE_DYNAMIC(CTrayIcon)

NOTIFYICONDATA m_nid;

// Shell_NotifyIcon 函数中的结构参数

public:

CTrayIcon(UINT uID);

~CTrayIcon();

// 通过调用该成员函数来接收托盘通知消息

void SetNotificationWnd(CWnd* pNotifyWnd,

UINT uCbMsg);

// SetIcon 函数用来在系统托盘中加入、改变及删除图标。

//要删除图标这样调用:SetIcon(0)

BOOL SetIcon(UINT uID);

BOOL SetIcon(HICON hicon, LPCSTR lpTip);

BOOL SetIcon(LPCTSTR lpResName, LPCSTR lpTip)

{

return SetIcon(lpResName ?

AfxGetApp()->LoadIcon(lpResName):NULL,lpTip);

}

BOOL SetStandardIcon(LPCTSTR lpszIconName,LPCSTR lpTip)

{

return SetIcon(::LoadIcon(NULL,lpszIconName),lpTip);

}

virtual LRESULT OnTrayNotification(WPARAM uID, LPARAM lEvent);

};

CTrayIcon::CTrayIcon(UINT uID)

{

//初始化NOTIFYICONDATA结构变量

memset(&m_nid, 0 , sizeof(m_nid));

m_nid.cbSize = sizeof(m_nid);

m_nid.uID = uID;

AfxLoadString(uID, m_nid.szTip, sizeof

(m_nid.szTip));

}

CTrayIcon::~CTrayIcon()

{

SetIcon(0); // 从系统托盘中删除图标

}

// 设定通知窗口,该窗口必须已被创建

void CTrayIcon::SetNotificationWnd(CWnd* pNotifyWnd, UINT uCbMsg)

{

ASSERT(pNotifyWnd==NULL || ::IsWindow(pNotifyWnd->GetSafeHwnd()));

m_nid.hWnd = pNotifyWnd->GetSafeHwnd();

ASSERT(uCbMsg==0 || uCbMsg>=WM_USER);

m_nid.uCallbackMessage = uCbMsg;

}

BOOL CTrayIcon::SetIcon(UINT uID)

{

HICON hicon=NULL;

if (uID) {

AfxLoadString(uID, m_nid.szTip, sizeof(m_nid.szTip));

hicon = AfxGetApp()->LoadIcon(uID);

}

return SetIcon(hicon, NULL);

}

//////////////////

//

BOOL CTrayIcon::SetIcon(HICON hicon, LPCSTR lpTip)

{

UINT msg;

m_nid.uFlags = 0;

// 设定图标

if (hicon) {

// 判断是要在系统托盘中增加还是要删除图标

msg = m_nid.hIcon ? NIM_MODIFY : NIM_ADD;

m_nid.hIcon = hicon;

m_nid.uFlags |= NIF_ICON;

} else { // 删除图标

if (m_nid.hIcon==NULL)

return TRUE; //已被删除

msg = NIM_DELETE;

}

if (lpTip)

strncpy(m_nid.szTip, lpTip, sizeof(m_nid.szTip));

if (m_nid.szTip[0])

m_nid.uFlags |= NIF_TIP;

if (m_nid.uCallbackMessage && m_nid.hWnd)

m_nid.uFlags |= NIF_MESSAGE;

BOOL bRet = Shell_NotifyIcon(msg, &m_nid);

if (msg==NIM_DELETE || !bRet)

m_nid.hIcon = NULL;

return bRet;

}

// 缺省事件处理程序,该程序处理鼠标右击及双击事件。

LRESULT CTrayIcon::OnTrayNotification(WPARAM wID,

LPARAM lEvent)

{

if (wID!=m_nid.uID ||

(lEvent!=WM_RBUTTONUP && lEvent!=WM_LBUTTONDBLCLK))

return 0;

// 使用与托盘图标拥有同样ID号的菜单作为右键弹出菜单

// 并将菜单上的第一项作为缺省命令使用,

// 缺省命令在WM_LBUTTONDBLCLK事件发生时被击发

//

CMenu menu;

if (!menu.LoadMenu(m_nid.uID))

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; // 表示事件已被处理

}

以下以在VC++6.0中具体实现的程序为例。该程序将拥有以下功能:程序被执行

后,首先显示一对话框表示程序开始执行,然后该对话框消失。接着程序图标

被加入到系统托盘中,可以看到,该图标将是一动画图标。当鼠标在该系统托

盘上右击时,将弹出一菜单。如图所示(略)。其第一项为缺省项命令,单击

将显示应用程序。为简化编程,该应用程序只是显示一应用程序主窗口。而单击

菜单第二项将关闭机器,单击菜单第三项将结束本程序。当并且当用户双击时,

CTrayIcon将执行菜单上的第一项:显示服务程序,这将击活(显示)TrayDemo

(正常情况下,它是隐藏的)。而要终止TrayDemo,你得选择结束本程序。当你

执行File Exit或关掉TrayDemo主窗口时,TrayDemo并没有真正的关掉,它只不过

隐藏起来了而已。TrayDemo 重载了Cmainframe::OnClose函数以执行该项功能。

首先在VC++6.0中生成用应用程序向导生成一单文档工程TrayDemo,然后在工程中

加入以上的CTrayIcon类。

要使用CTrayIcon类,你首先得实例化一个CTrayIcon类对象,TrayDemo在视图中

完成此项工作。以下是对应代码:

class CTrayDemoView : public CView {

protected: CTrayIcon m_trayIcon;

// my tray icon

.

.

.

};

当你实例化一个CTrayIcon类对象之后,你必须分配给其一个ID号。该ID号是此图

标在其生命周期内使用的唯一一个ID号,即使在以后你改变了实际显示的图标。此

ID号是当鼠标事件发生时你获得的ID。它可以不必是图标的资源ID;在TrayDemo

中,其值是IDR_TRAYICON,由CTrayDemoView构造函数所初始化。

CTrayDemoView::CTrayDemoView() :

m_trayIcon(IDR_TRAYICON){

.

.

.

}

要增加图标,可调用SetIcon重载函数之一

m_trayIcon.SetIcon(IDI_MYICON); //参数为资源ID

m_trayIcon.SetIcon("myicon"); //参数为资源名

m_trayIcon.SetIcon(hicon); //参数为HICON句柄

m_trayIcon.SetStandardIcon(IDI_WINLOGO);

//加入系统图标

除了SetIcon(UINT uID)函数需要一个同样拥有uID号的字符串资源作为提示字符串

以外,所有这些函数都有一个可选的指向提示字符串的LPCSTR参数。例如,在

TRAYTEST中有以下行:

// (In TrayDemoView.cpp)

m_trayIcon.SetIcon(IDI_RED);

该语句在增加图标的同时同样设定了提示字符串,因为TrayDemo有一个同样ID的字

符串:如果你想改变图标,只需再次调用其中的一个SetIcon函数,只不过需要不

同的ID或HICON。CTrayIcon类知道响应NIM_MODIFY消息而不是NIM_ADD消息。同样

的函数甚至可以去掉图标:

m_trayIcon.SetIcon(0);//removeicon

CtrayIcon类会将其解释为NIM_DELETE事件。这么多的代码和标志只用一个简单的

重载函数就予以完成,这是C++的伟大之处。

如果要显示动画图标,只需设置一定时器,然后在定时器的响应事件中调用

SetIcon成员函数就可以了。如:

int CTrayDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

m_timerID = this->SetTimer(99,200,NULL);

}

void CTrayDemoView::OnTimer(UINT nIDEvent)

{

uChangeIcon++;

if(uChangeIcon-IDI_RED>2)

uChangeIcon=IDI_RED;

m_trayIcon.SetIcon(uChangeIcon);

CView::OnTimer(nIDEvent);

}

在示例程序中,有3个图标,其ID为IDI_RED,IDI_YELLO,IDI_GREEN,且其ID值是相

连的,因而UINT型变量uChangeIcon用来依次轮换三个图标。这样程序执行以后,你

将会看到红、黄、绿三个交通指示灯依次闪烁。

那么怎样处理托盘通知呢?

要处理托盘通知,需要在你设定图标之前调用CTrayIcon::SetNotificationWnd函

数,当然你必须已经创建了窗口。最适当的地方是在OnCreate函数中,在TrayDemo

中也是这样做的。用ClassWizard在CtrayDemoView类中加入WM_CREATE消息响应函

数OnCreate(),并加入以下代码:

// Private message used for tray notifications

#define WM_MY_TRAY_NOTIFICATION WM_USER+0

int CTrayDemoView::OnCreate(LPCREATESTRUCT lpCreateStruct)

.

.

.

m_trayIcon.SetNotificationWnd(this,WM_MY_TRAY_NOTIFICATION);

m_trayIcon.SetIcon(IDI_RED);

return 0;

}

然后进行消息注册(REGISTER),一旦注册以后,你就可以用正常的消息映射方式

处理托盘通知。

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

ON_MESSAGE(WM_MY_TRAY_NOTIFICATION,OnTrayNotification)

// (or ON_REGISTERED_MESSAGE)

END_MESSAGE_MAP()

当然不要忘了在TrayDemoView.h中加入以下语句:

afx_msg LRESULT OnTrayNotification(WPARAM wp, LPARAM lp);

当你的处理程序得到在托盘图标上的鼠标事件的控制以后,WPARAM参数是你在创建

CTrayIcon类时定义的ID;LPARAM是鼠标事件(如,WM_LBUTTONDOWN)。当捕获到

通知后你可以做任何你想做的事情;记得最后要调用

CTrayIcon::OnTrayNotification函数以完成一些缺省的处理。该虚函数完成前面

所提到的一些缸省的UI行为。特别的,它处理WM_LBUTTONDBLCLK和WM-RBUTTONUP事

件。CTrayIcon类寻找与图标拥有同样ID的菜单(如,IDR_TRAYICON)。如果拥有

该ID的菜单存在,CTrayIcon类将在用户右击图标的时候显示此菜单;而当用户双

击时,CTrayIcon将执行菜单上的第一个命令。

LRESULT CTrayDemoView::OnTrayNotification(WPARAM wp, LPARAM lp)

{

return m_trayIcon.OnTrayNotification(wp, lp);

}

只有两件事需要进一步解释。在显示菜单之前,CTrayIcon类使得第一项为缸省项,

因此它看起来是大写的。但怎样使得一个菜单项大写呢?使用函数

GSetMenuDefaultItem。

// Make first menu item the default (bold font)

::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);

这里的0便指定了第一个菜单项,TRUE表示通过位置而不是ID来确定菜单项。

对CTrayIcon::OnTrayNotification,我们关心的第二项是为了显示相关菜单,它干

了些什么?

::SetForegroundWindow(m_nid.hWnd);

::TrackPopupMenu(pSubMenu->m_hMenu, ...);

为了使TrackPopupMenu函数在托盘环境中工作正常,你必须首先在拥有该弹出菜单

的窗口中调用SetForegroundWindow函数。否则,当用户按下Esc键或在菜单以外单

击鼠标时该菜单将不会消失。正如你看到的那样,CTrayIcon类使得托盘图标的编

程很简单。为了使托盘菜单生效,在TrayDemo中所做的只是实现一个通知程序,在

该程序中调用了CTrayIcon::OnTrayNotification,对了别忘了还要提供一个与

CTrayIcon类拥有同样ID的菜单。TrayDemo程序中是在菜单编辑器内加入一ID为

IDR_TRAYICON的如下菜单:

然后,用ClassWizard在视图类中分别为三个菜单命令加入如下的响应函数:

void CTrayDemoView::OnDisplayProgram()

{

CWnd* pWnd;

pWnd=AfxGetApp()->m_pMainWnd;

pWnd->ShowWindow(SW_NORMAL);

pWnd->SetForegroundWindow();

}

void CTrayDemoView::OnCloseProgram()

{

m_bShutdown = TRUE; // really exit

CWnd* pWnd;

pWnd=AfxGetApp()->m_pMainWnd;

pWnd->SendMessage(WM_CLOSE);

}

void CTrayDemoView::OnShutoff()

{

ExitWindowsEx(EWX_SHUTDOWN,0);

}

其中,在OnShutoff函数中,ExitWindowsEx(EWX_SHUTDOWN,0)用来关闭计算机。限

于篇幅,这里不作详细介绍,读者可以查看MSDN来获得更详细的资料。

最后,还要重载Cmainframe::OnClose函数如下:

void CMainFrame::OnClose()

{

CTrayDemoView *pView =

(CTrayDemoView *)GetActiveView();

if (pView->m_bShutdown)

CFrameWnd::OnClose();

else

ShowWindow(SW_HIDE);

}

提醒一点,为使框架程序识别视图类,还要在MainFrm.cpp中加入如下两句:

#include "TrayDemoDoc.h"

#include "TrayDemoView.h"

如果有兴趣,还可以对将本程序继续扩充,使之可以监视系统的状态:当鼠标和键

盘在超过一设定的时间后,仍没有动作,则程序将自动执行关机命令。

以上程序在Windows98,VC++6.0中调试通过。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有