Introduction
Recently I was browsing CodeProject, looking for a simple example of how to
manage system tray icons. Ooops sorry, I shouldn't call them tray icons as MSDN
documentation so carefully points out:
"The taskbar notification area is sometimes erroneously called the
tray."
In spite of this, I will insist on calling a spade a shovel and refer to the
"taskbar notification area" as the "tray" and "taskbar notification area icons"
as "tray icons". Anyway back to the point, why another article about tray
icons?
- I've never written a Code Project article before and this seemed pretty
easy.
- This isn't another wrapper, but simple straight forward example of
Shell_NotifyIcon
with a minimum amount of code.
- I wanted an example written in pure Win32 code.
- I wanted to address some of the common problems and questions I've seen
posted in other articles on the subject.
The basics
Adding, modifying, hiding and deleting tray icons is accomplished in two
steps:
- Initialize a
NOTIFYICONDATA
structure
- Call
Shell_NotifyIcon
Initialize a NOTIFYICONDATA structure
NOTIFYICONDATA niData;
ZeroMemory(&niData,sizeof(NOTIFYICONDATA));
ULONGLONG ullVersion =
GetDllVersion(_T("Shell32.dll"));
if(ullVersion >= MAKEDLLVERULL(6,0,0,0))
niData.cbSize = sizeof(NOTIFYICONDATA);
else if(ullVersion >= MAKEDLLVERULL(5,0,0,0))
niData.cbSize = NOTIFYICONDATA_V2_SIZE;
else niData.cbSize = NOTIFYICONDATA_V1_SIZE;
niData.uID = MY_TRAY_ICON_ID;
niData.uFlags = NIF_ICON|NIF_MESSAGE|NIF_TIP;
niData.hIcon =
(HICON)LoadImage( hInstance,
MAKEINTRESOURCE(IDI_MY_ICON),
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_DEFAULTCOLOR);
niData.hWnd = hWnd;
niData.uCallbackMessage = MY_TRAY_ICON_MESSAGE;
Call Shell_NotifyIcon
Shell_NotifyIcon(NIM_ADD,&niData);
Stealth dialog
I've seen more than a few posts asking how to begin a dialog app minimized to
the system tray, hence the name Stealth Dialog. This can be accomplished simply
by first creating a modeless dialog:
HWND hWnd = CreateDialog( hInstance,
MAKEINTRESOURCE(MY_DIALOG),
NULL,
(DLGPROC)MyDlgProc );
Then use Shell_NotifyIcon
as shown above to add your icon to the
tray. Do not call ShowWindow
.
Menus and messages
Messages from the tray will go to the window specified by the
hWnd
member of the NOTIFYICONDATA
struct
and the message ID is specified by the uCallbackMessage
member (see
above). The specific message is in the LPARAM
.
INT_PTR CALLBACK MyDlgProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case MY_TRAY_ICON_MESSAGE:
switch(lParam)
{
case WM_LBUTTONDBLCLK:
ShowWindow(hWnd, SW_RESTORE);
break;
case WM_RBUTTONDOWN:
case WM_CONTEXTMENU:
ShowContextMenu(hWnd);
}
break;
case...
If you implement a context menu, messages are received through
WM_COMMAND
and the menu item ID is contained in the low-order word
of the WPARAM
.
case WM_COMMAND:
switch (LOWORD(wParam))
{
case MY_MENU_MSG1:
...
break;
case MY_MENU_MSG2:
...
Important: If you implement a context menu, it's vital that you set
your window to the foreground before calling TrackPopupMenu
to
ensure the menu closes properly.
void ShowContextMenu(HWND hWnd)
{
...
HMENU hMenu;
...
SetForegroundWindow(hWnd);
TrackPopupMenu(hMenu, ...
Cleaning up
Sometime before your app closes you should remove your tray icon by calling
Shell_NotifyIcon
with the NIM_DELETE
flag.
case WM_DESTROY:
Shell_NotifyIcon(NIM_DELETE,&niData);
Notes:
The MSDN documentation says about the cbSize
member of the
NOTIFYICONDATA
structure:
"You can keep your application compatible with all Shell32.dll versions
while still using the current header files by setting the size of the
NOTIFYICONDATA
structure appropriately. Before
initializing the structure, use the DllGetVersion
function
to determine which Shell32.dll version is installed on the system. If it is
version 5.0 or greater, initialize the cbSize
member
with:
nid.cbSize = sizeof(NOTIFYICONDATA);
Setting cbSize
to this value enables all the version
5.0 and 6.0 enhancements. For earlier versions, the size of the pre-6.0
structure is given by the NOTIFYICONDATA_V2_SIZE
constant
and the pre-5.0 structure is given by the
NOTIFYICONDATA_V1_SIZE
constant. Initialize the
cbSize
member with:
nid.cbSize = NOTIFYICONDATA_V2_SIZE;
Using this value for cbSize
enables your application
to use NOTIFYICONDATA
with earlier Shell32.dll
versions, although without the version 6.0 enhancements:"
Now maybe it's my neighbors Turkish tobacco or maybe I'm just not catching on
here, but there seems to be an overlapping conflict between "5.0 or greater" and
"pre-6.0".
Anyway, if anybody can shed any light on this or anything else they care to
shed light on, please shed.