Introduction
I use a tool to observe two measured values from different thermometers via RS232. When I work with other applications on my computer during the observation time, I can't see the measured values in the dialog window, because the tool is overlaid with the window of the application. So I needed a display which is independent from the state of the desktop. I decided to write the values in icons and show these icons in the system tray repetitively.
After reading other articles about icons in the system tray, you might ask what is special in this demo? The main point of interest in this demo is the dynamic creation of the icons at runtime in a simple way and updating of the system tray permanently with these icons.
Two Versions
There are two versions of the code. The first time, I submitted the article with the MFC demo project and explained the code. Six months later, I updated the article with a C# version. Because the C# code is much easier to understand, I leave the explanation for the MFC code untouched and just added the C# demo project. In this new project, you will find nearly all the methods and variables as in the MFC code because the principle has not changed.
Using the Code
The tool works like this: When the program starts, a static (default) icon appears in the system tray. Just when you push the 'Start' button, there will be two icons which show the changing values. You can hide the dialog with the minimize button. The program will disappear from the task bar, and only the changing icons will be shown in the system tray. With a double click on the icons, the dialog will popup to the desktop (or will be hidden from the desktop if the dialog was visible).
In the demo project, I use a counter to simulate the dynamics of the measured values. It is realized by a timer. At each timer event TIMER1
, the values are changed, the icons are drawn and then pushed into the system tray:
switch(nIDEvent){
case TIMER1:
ChangeValues();
CreateIcons();
PushIcons();
break;
default: break;
}
How to Create Icons?
In this demo, I show how to load and display a static icon from the resource view of the IDE and how to draw an icon at runtime. I use a static icon for the default icon, which was added and designed in the resource view. I use only the 16x16 pixel image type. The 32x32 pixel image type could be deleted or you use the same icon for the IDR_MAINFRAME
icon. The resource name is IDI_ICON1
. This name is necessary for the NOTIFYCONDATA
structures which will push the icon into the system tray (see below). To load the default icon at runtime, I use the following code:
HICON hIcon;
HINSTANCE hInst =
AfxFindResourceHandle(MAKEINTRESOURCE(IDI_ICON1),RT_GROUP_ICON);
hIcon = (HICON)LoadImage(hInst,MAKEINTRESOURCE(IDI_ICON1),
IMAGE_ICON,16,16,LR_DEFAULTCOLOR);
.
.
.
Creating Icons at Runtime
Because a GDI+ graphics object is used, I implemented GDI+. How to do it is written in the article, Starting with GDI+.
This happens in CreateIcon1()
and CreateIcon2()
.
At first we have to prepare the value which should be written into the icon. For the used command DrawString()
, we need a widechar
array. Either we write the value directly to the widechar
array strValue
...
wchar_t strValue[4];
int size = sizeof(strValue)/sizeof(wchar_t);
swprintf( strValue, size, L"%3d", m_nValue1 );
... or we write the value in a char
array and convert it to a widechar
array.
char txt[4];
wchar_t strValue[4];
int size = sizeof(strValue)/sizeof(wchar_t);
sprintf_s(txt, sizeof(txt),"%3d", m_nValue1);
MultiByteToWideChar(CP_ACP, 0, txt, -1, strValue, size);
In the next step, we provide a Pen
, a SolidBrush
, a Font
and some formatting stuff:
Pen whitePen(Color(255, 255, 255));
SolidBrush whiteBrush(Color(255,255,255));
Font font(L"Tahoma",8);
PointF origin(-1, 1);
StringFormat format;
format.SetAlignment(StringAlignmentNear);
Now let's start with painting:
Bitmap bitmap(16,16);
Graphics *graph = Graphics::FromImage(&bitmap);
graph->DrawLine(&whitePen, 0, 15, 15, 15);
graph->DrawLine(&whitePen, 0, 0, 15, 0);
graph->DrawString(strValue , -1, &font, origin, &format, &whiteBrush);
And at last, we convert the bitmap into an icon and save it to our icon handle m_hIcon1
. This handle is a member of our class because we need it later in PushIcon1()
to push it into the system tray.
bitmap.GetHICON(&m_hIcon1);
Push the Icon into the System Tray
First we need a NOTIFYCONDATA
structure filled with some data including the name ICON_VALUE1
, a user defined message WM_TRAY
for the double clicks on the icon and the handle m_hIcon1
of the created icon.
NOTIFYICONDATA tnid;
tnid.cbSize = sizeof(NOTIFYICONDATA);
tnid.hWnd = m_hWnd;
tnid.uID = ICON_VALUE1;
tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
tnid.uCallbackMessage = WM_TRAY;
tnid.hIcon = m_hIcon1;
Take care of the resource names. There are three different names. The static default icon has the name IDI_ICON1
and the other two icons have the self defined names ICON_VALUE1
and ICON_VALUE2
(see DynIconDlg.h).
It is possible to use a tooltip. After every change of values, the string m_strTooltip
will be updated. In this demo, I use the same tooltip for both icons and use the string as a member variable.
lstrcpyn(tnid.szTip, m_strTooltip, sizeof(tnid.szTip));
Now we have to check if the icon appears for the first time. Then we have to use the Shell_NotifyIcon
with the NIM_ADD
parameter, otherwise we just want to update the icon with the NIM_MODIFY
parameter.
if(m_bFirstIcon1){
Shell_NotifyIcon(NIM_ADD, &tnid);
m_bFirstIcon1 = FALSE;
}
else{
Shell_NotifyIcon(NIM_MODIFY, &tnid);
}
Now the icon is in the system tray and we clean up:
DestroyIcon(m_hIcon1);
The same procedure is also followed for the second icon in CreateIcon2()
and PushIcon2()
.
Comments
The other methods in the code are commented. So I made my explanations short. I know that the code is not written very efficiently. But I think this enforces the understanding of the used methods.
History
- 29th November, 2007 - Created the article
- 10th June, 2008 - Updated the article with C# code