在运行时创建图标并在系统托盘中显示它们






4.66/5 (16投票s)
在运行时创建动态图标并在系统托盘中显示它们
引言
我使用一个工具通过 RS232 监测来自不同温度计的两个测量值。在监测期间,当我使用计算机上的其他应用程序时,我无法在对话框窗口中看到测量值,因为该工具被应用程序窗口覆盖了。因此,我需要一个独立于桌面状态的显示。我决定将值写入图标并在系统托盘中重复显示这些图标。

在阅读了关于系统托盘中图标的其他文章后,您可能会问这个演示有什么特别之处?这个演示的主要看点在于能够以简单的方式在运行时动态创建图标,并用这些图标永久更新系统托盘。
两个版本
代码有两个版本。我第一次提交文章时附带了 MFC 演示项目并解释了代码。六个月后,我用 C# 版本更新了文章。因为 C# 代码更容易理解,所以我保留了 MFC 代码的解释不变,只添加了 C# 演示项目。在这个新项目中,您会发现几乎所有的函数和变量都与 MFC 代码中的一样,因为原理没有改变。
Using the Code
该工具的工作方式如下:程序启动时,一个静态(默认)图标会出现在系统托盘中。只有当您按下“开始”按钮时,才会有两个显示变化值的图标。您可以通过最小化按钮隐藏对话框。程序将从任务栏消失,只有变化中的图标会显示在系统托盘中。双击图标,对话框会弹出到桌面(如果对话框可见,则从桌面隐藏)。
在演示项目中,我使用一个计数器来模拟测量值的动态变化。这是通过一个定时器实现的。在每次定时器事件 TIMER1
中,值会改变,图标会被绘制然后推送到系统托盘。
//CDynIconDlg::OnTimer(UINT_PTR nIDEvent)
switch(nIDEvent){
case TIMER1:
ChangeValues();
CreateIcons();
PushIcons();
break;
default: break;
}
如何创建图标?
在这个演示中,我展示了如何从 IDE 的资源视图加载和显示一个静态图标,以及如何在运行时绘制一个图标。我使用一个静态图标作为默认图标,该图标已在资源视图中添加和设计。我只使用了 16x16 像素的图像类型。32x32 像素的图像类型可以删除,或者您可以使用相同的图标作为 IDR_MAINFRAME
图标。资源名称是 IDI_ICON1
。在将图标推送到系统托盘的 NOTIFYCONDATA
结构中需要此名称(见下文)。要在运行时加载默认图标,我使用了以下代码:
//CDynIconDlg::SetDefaultIconInTray()
//create a handle for the default icon
HICON hIcon;
//load the icon from the resources
HINSTANCE hInst =
AfxFindResourceHandle(MAKEINTRESOURCE(IDI_ICON1),RT_GROUP_ICON);
hIcon = (HICON)LoadImage(hInst,MAKEINTRESOURCE(IDI_ICON1),
IMAGE_ICON,16,16,LR_DEFAULTCOLOR);
//push the icon into the system tray
.
.
.
在运行时创建图标
因为使用了 GDI+ 图形对象,所以我实现了 GDI+。如何实现它已在文章《Starting with GDI+.》中进行了说明。
这发生在 CreateIcon1()
和 CreateIcon2()
中。
首先,我们必须准备要写入图标的值。对于使用的 DrawString()
命令,我们需要一个 widechar
数组。我们可以直接将值写入 widechar
数组 strValue
...
//CDynIconDlg::CreateIcon1()
//we need a widechar array for the value
wchar_t strValue[4];
// because the wchar_t requires two bytes
int size = sizeof(strValue)/sizeof(wchar_t);
//the prefix L means, that the string should be a widechar
swprintf( strValue, size, L"%3d", m_nValue1 );
... 或者我们将值写入一个 char
数组并将其转换为 widechar
数组。
//CDynIconDlg::CreateIcon1()
char txt[4];
wchar_t strValue[4];
// because the wchar_t requires two bytes
int size = sizeof(strValue)/sizeof(wchar_t);
//write the value1 in the string
sprintf_s(txt, sizeof(txt),"%3d", m_nValue1);
//change to widechar
MultiByteToWideChar(CP_ACP, 0, txt, -1, strValue, size);
下一步,我们提供一个 Pen
、一个 SolidBrush
、一个 Font
和一些格式化内容。
//CDynIconDlg::CreateIcon1()
//you can change the colors depending on the value
//here it should be white only
//white pen to draw some lines in the icon
Pen whitePen(Color(255, 255, 255));
//white brush for the text
SolidBrush whiteBrush(Color(255,255,255));
//create a small font to write the values in the bitmap
Font font(L"Tahoma",8);
//origin for the string
PointF origin(-1, 1);
//alignment for the string
StringFormat format;
format.SetAlignment(StringAlignmentNear);
现在开始绘画。
//CDynIconDlg::CreateIcon1()
//create a new bitmap with 16x16 pixel
Bitmap bitmap(16,16);
//use the bitmap to draw
Graphics *graph = Graphics::FromImage(&bitmap);
//draw two horizontal lines (just for the show)
graph->DrawLine(&whitePen, 0, 15, 15, 15);
graph->DrawLine(&whitePen, 0, 0, 15, 0);
//draw the string including the value
//here we need the widechar array
graph->DrawString(strValue , -1, &font, origin, &format, &whiteBrush);
最后,我们将位图转换为图标并将其保存到我们的图标句柄 m_hIcon1
。这个句柄是我们类的成员,因为我们稍后在 PushIcon1()
中需要它来将其推送到系统托盘。
//CDynIconDlg::CreateIcon1()
bitmap.GetHICON(&m_hIcon1);
将图标推送到系统托盘
首先,我们需要一个 NOTIFYCONDATA
结构,其中填充了一些数据,包括名称 ICON_VALUE1
、用于双击图标的用户定义消息 WM_TRAY
以及创建图标的句柄 m_hIcon1
。
//CDynIconDlg::PushIcon1()
// set NOTIFYCONDATA structure
NOTIFYICONDATA tnid;
tnid.cbSize = sizeof(NOTIFYICONDATA);
tnid.hWnd = m_hWnd;
tnid.uID = ICON_VALUE1; //Resourcename
tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
tnid.uCallbackMessage = WM_TRAY;//user message
tnid.hIcon = m_hIcon1; //handle of the created icon
请注意资源名称。有三个不同的名称。静态默认图标的名称是 IDI_ICON1
,而另外两个图标具有自定义名称 ICON_VALUE1
和 ICON_VALUE2
(参见 DynIconDlg.h)。
可以使用工具提示。每次值更改后,字符串 m_strTooltip
都会更新。在此演示中,我为两个图标使用相同的工具提示,并将字符串用作成员变量。
//CDynIconDlg::PushIcon1()
//copy the string to the NOTIFYCONDATA structure
lstrcpyn(tnid.szTip, m_strTooltip, sizeof(tnid.szTip));
现在我们需要检查图标是否是第一次出现。如果是,我们就使用带有 NIM_ADD
参数的 Shell_NotifyIcon
,否则我们只使用 NIM_MODIFY
参数来更新图标。
//CDynIconDlg::PushIcon1()
if(m_bFirstIcon1){
//for the first time we have to use the Shell_NotifyIcon with NIM_ADD
//to add the icon to the tray
Shell_NotifyIcon(NIM_ADD, &tnid);
m_bFirstIcon1 = FALSE;
}
else{
//the icon already exists
//Shell_NotifyIcon with NIM_MODIFY
Shell_NotifyIcon(NIM_MODIFY, &tnid);
}
现在图标已在系统托盘中,我们进行清理。
//CDynIconDlg::PushIcon1()
//frees the memory
DestroyIcon(m_hIcon1);
对第二个图标在 CreateIcon2()
和 PushIcon2()
中也遵循相同的过程。
注释
代码中的其他函数都已注释。因此,我的解释很简短。我知道代码的编写效率不高。但我认为这有助于加深对所用函数的理解。
历史
- 2007 年 11 月 29 日 - 创建文章
- 2008 年 6 月 10 日 - 更新文章,添加 C# 代码