Introduction
When I started working on my current project, I noticed that lots of programs I was working on, required notification icons (yeah, tray icons) because of some reasons. Actually, writing a couple of calls to Shell_NotifyIcon()
is not a big deal, but this project required animated tray icon. And I decided that I have something to do with all these tray icons. The result of my work is here. Meet � CakTrayIcon
and CakTrayIconAnimator
classes!
Background
If you want to create a similar class, you'll need to read about the famous Shell_NotifyIcon()
function and take a look at the IconPro MSDN sample (see "Icons in Win32" article by John Hornick from Sep 29, 1995).
Shell_NotifyIcon()
will give you an ability to control notification icon and the sample will show you how to extract planes from the icon resource.
If you do not know yet, the icons in Windows can have multiple so-called planes. The plane is actually an image. Usually icons have 16x16x16, 32x32x256 and 48x48x256 planes, so Windows Explorer can display the icons correctly in different video and list modes. But the icons can have saying, 10 16x16x16 planes or 25 32x32x16M planes � whatever you want. I decided to use such kind of the icons to make animated notification icons.
Don't worry � almost all good icon editors can make such icons. Not the editor of Visual Studio, though.
Using the Code
Using CakTrayIcon
is easy. Just declare the object of this class.
void CMainFrm::SomeImportantFunc()
{
CakTrayIcon ti(this, 1,
LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME)),
_T("Some tip"));
return;
}
Your icon should be visible all the time? No problem.
First, edit your header file.
class CMainFrm : public CFrameWnd
{
private:
CakTrayIcon m_TrayIcon;
};
Then, initialize the icon in OnCreate
handler.
int CMainFrm::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
m_TrayIcon.Create(this, 1);
m_TrayIcon.SetIconAndTip(LoadIcon(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDR_MAINFRAME)),
_T("This is a tray icon"));
return 0;
}
That's all. The icon will be destroyed when the window will be closed.
Handling Events
No problem to handle the events from your icon too.
Declare the message handler in the header.
class CMainFrm : public CFrameWnd
{
private:
afx_msg LRESULT OnTrayIconNotify(WPARAM, LPARAM);
};
And add it to the message map.
BEGIN_MESSAGE_MAP(CMainFrm, CFrameWnd)
ON_REGISTERED_MESSAGE(CakTrayIcon::WM_TRAYICONNOTIFY, OnTrayIconNotify)
END_MESSAGE_MAP()
LRESULT CMainFrm::OnTrayIconNotify(WPARAM, LPARAM)
{
AfxMessageBox("Yabba-dabba-doo!");
return 0;
}
Please, refer to NOTIFYICONDATA.uCallbackMessage
for details about the message you'll receive. Note that you'll receive the id of the icon in wParam
parameter. Also note that you should use ON_REGISTERED_MESSAGE
macro along with CakTrayIcon::WM_TRAYICONNOTIFY
parameter to add the notification message into the map.
More Stuff
Implementing "just another tray icon class" is boring. So I added a couple of helper functions.
You can change an icon, a tip or both using the functions below.
m_TrayIcon.SetIcon(LoadIcon(0, IDI_ERROR));
m_TrayIcon.SetTip(_T("Hi there!"));
m_TrayIcon.SetIconAndTip(LoadIcon(0, IDI_ERROR), _T("Hi!"));
And, of course, you can show an information balloon.
m_TrayIcon.PopupBalloon(_T("Some information"), _T("Balloon title"),
NIIF_INFO, 10000);
Note that balloon tip can be used only if you targeted IE version 5.0 or later (see _WIN32_IE
macro). In other cases you will be able to call this method, but nothing will happen.
Wake Up! The Most Interesting Part!
Before using the following code, you'll need at least one multi-plane icon. Please, take a look at hourglass.ico in folder res of the sample project for example.
Assume you want to start lenghtly operation. You'd like to animate something in the system notification area to make the process more interesting for user. Use CakTrayIconAnimator
for this reason.
And again, this is simple as 1-2-3.
void SuperDuperFunction()
{
CakTrayIconAnimator tia(&m_TrayIcon, IDI_HOURGLASS, _T(
"Hey! Don't get sleep!"));
tia.Animate(500);
AfxMessageBox(
_T("Click OK when you tired to look at the hourglass spinning around..."),
MB_OK);
}
Note that you have to have a valid object of CakTrayIcon
class and an integer code or string name of the multi-plane icon resource. CakTrayIconAnimator
will save the current tray icon and tip and restore them after animation completed.
That's it. You don't have to write tons of code to show and animate your icons. Animation is implemented using thread, so you don't have to write any support code for it.
Points of Interest
When I was working on CakTrayIconAnimator
class, I found that extract a plane from the icon is a pretty tricky thing. Thanks God, I found IconPro sample by John Hornick in MSDN and added several functions from there to CakIconLoader
class. I made this class public, so you can use it if you need to extract icon planes too.
void DoSomething()
{
CakIconLoader ldr(IDI_HOURGLASS);
UINT count = ldr.GetIconsCount();
HICON hIcon = ldr.GetIcon(0);
}
Don't call DestroyIcon()
after GetIcon()
� this icon handle will be freed by next GetIcon()
call or when object of CakIconLoader
will be destroyed.
Interesting fact � when working on CakIconLoader
, I found that I should use CreateIconFromResourceEx()
with zeroed flags instead of CreateIconFromResource()
function. CreateIconFromResource()
creates shared icon and this mean that you'll have resource leak when call it constantly. This difference cost me lots of nerves and time.
Known Issues
Burn in your mind that all Windows except XP, 2003 and Me can display tray icons only in 16 colors (despite what MSDN says). So, if you want your icons to look good, make them 16x16x16 if you target any Windows except XP, 2003 and Me, and make hi-color version for XP, 2003 and Me.
History