Creating Simple Task Tray Apps for Windows 95/NT 4.0 with Delphi
Posted: (EET/GMT+2)
Windows 95 and NT 4.0 include an interesting feature named taskbar notification area, or task tray for short. This area, usually located in the right side of the task bar, can contain small icons. These icons could hide simple applications only starting a larger one, or they could be full blown applications with menus and dialog boxes. In this How To we create a small task tray application with Delphi 2.0 which displays a menu of almost all of the shortcuts on the Windows desktop.
Before proceeding, please see note about recuired interface units.
Technically, a task tray application is much like an ordinary application. It has a message loop, and it handles it's functions by responding to certain messages sent by Windows itself. For example, our "main" procedure is like this:
Procedure RunTrayApplication;
Var Msg : TMsg;
Begin
CreateWindow;
AddTrayIcon;
While GetMessage(Msg,0,0,0) do Begin
TranslateMessage(Msg);
DispatchMessage(Msg);
End;
DeleteTrayIcon;
End;
Let's start with the window creation. Actually, this window is not the
window which can be seen on the task tray when the application is
ready. Instead, this window is only a message processor, or something
like a "parent". The Windows shell handles the creation of messages
(for example, a mouse click) and the send the message to our window.
Next step is to add (or register) our icon with the shell. This is
done with the Shell_NotifyIcon API function. This message is actually
a three-purpose function. This time we only need to use the add
feature:
Now that we've added our icon, we need to decide how we are going to
handle the messages. Deciding the callback message number (see above)
is simple:
When the user clicks the left mouse button on our icon, we create menu
of the shortcut icons on the desktop:
Enumerating the items is done using shell interfaces*.
First, we get an instance of the IShellFolder interface for the
desktop using the SHGetDesktopForlder API call. Using this interface,
we can get an instance of still another interface, IEnumIDList. This
interface is used to do the actual item enumeration. We simply call a
function repeatedly until an error value is returned (ie. all items
have been enumerated). When we get an item, we add it to our menu
using the custom AddToMenu function. (Note that this enumeration is
not complere. It skips for example the "My Computer" and "Recycle Bin"
icons. This is by design.)
After the items have been enumerated and the menu is created, we are
ready to run the menu. Note that we save the found desktop items to a
global list. Also, every menu item has its own item ID. This ensures
that we can get an index to our data array using simple substraction.
Thus the call
Now the only interesting procedure we haven't covered yet is how to
actually run the selected desktop icon. Looking at the previous code,
it might seem that running an "icon" would be difficult. Fortunately,
this is very simple:
Having got this far, you should now have an idea how to create a
simple task tray application using Delphi - without creating a 200k
EXE file. Note that the actual application implements some more
features, like an about box, but that's nothing spectacular.
You can donwload the whole application below. Please see note below.
Also, be sure to read the acomppanying read-me file in the archive.
As you can see, all we need to do is create a window, register an
icon on the task tray, sit in a message loop, and terminate
gracefully. Of course, additional coding is required, but it really
isn't something to worry about.
Procedure CreateWindow;
Var
WC : TWndClass;
W : hWnd;
Begin
With WC do Begin
Style := 0;
lpfnWndProc := @WndProc;
cbClsExtra := 0;
cbWndExtra := 0;
hIcon := 0;
hCursor := 0;
hbrBackground := 0;
lpszMenuName := nil;
lpszClassName := 'MyTrayIconClass';
hInstance := System.hInstance;
end;
RegisterClass(WC);
W := Windows.CreateWindow('MyTrayIconClass','MyVeryOwnTrayIconWindow',
ws_OverlappedWindow,0,0,0,0,0,0,hInstance,nil);
ShowWindow(W,sw_Hide);
UpdateWindow(W);
MainWindow := W;
End;
The window is created using normal Windows functions. Notice that the
window style is "ws_OverlappedWindow", but it's size is zero and that
it is hidden so that it won't show up. Creating an overlapped window
with no parent assures that our app will be shown on the Close Proram
list when Ctrl+Alt+Del is pressed (in Windows 95).
Procedure AddTrayIcon;
Var IconData : TNotifyIconData;
Begin
With IconData do Begin
cbSize := SizeOf(IconData);
Wnd := MainWindow;
uID := 0;
uFlags := nif_Icon Or nif_Message Or nif_Tip;
uCallBackMessage := wm_MyCallBack;
hIcon := LoadIcon(hInstance,'MYICON');
StrCopy(szTip,PChar(TrayIconTip));
End;
Shell_NotifyIcon(nim_Add,@IconData);
End;
The most important thing is the TNotifyIconData data structure. It is
a parameter passing record, and for us, we need to set the window
handle (which defines the window handling the messages), the callback
message number (we define this - I'll return to this later), an icon,
and of course, a tool tip for our icon. After the data has been set,
we are ready to add the icon to the task bar. To achieve this, nim_Add
code must be used.
Const
wm_MyCallback = wm_User+1000;
cm_Exit = 100; { we worry about... }
cm_About = 101; { ...these later }
The actual window procedure is quite normal. Several Windows messages
(like wm_NCCreate) must be processed. However, for us, the far most
important messages are the wm_MyCallback and wm_Command:
Function WndProc(Window : hWnd; Msg,WParam,LParam : Integer)
: Integer; StdCall;
Begin
Result := 0;
Case Msg of
wm_NCCreate : Result := 1;
wm_Destroy : PostQuitMessage(0);
wm_Command : Begin { a command was chosen from the popup menu }
If (WParam = cm_Exit) Then
PostMessage(Window,wm_Destroy,0,0)
Else If (WParam = cm_About) Then
MessageBox(0,'Shell Test Copyright © '+
'Jani Järvinen 1996.',
'About Shell Test',mb_OK)
Else OpenDesktopIcon(WParam-cm_About);
End;
wm_MyCallback : Begin { our icon was clicked }
If (LParam = wm_LButtonDown) Then
ShowIconPopupMenu
Else If (LParam = wm_RButtonDown) Then
ShowAboutPopupMenu;
End;
Else Result := DefWindowProc(Window,Msg,WParam,LParam);
End;
End;
As you can see, the shell notifies us when the user clicks our icon.
Notice that we won't get a normal wm_LButtonDown message. Instead, we
get our own wm_MyCallback message, and the actual message is in the
LParam parameter.
Type
TIconData = Array[1..100] of String;
Var
IconData : TIconData;
Procedure ShowIconPopupMenu;
Var
ShellFolder : IShellFolder;
EnumIDList : IEnumIDList;
Result : hResult;
Dummy : ULong;
ItemIDList : TItemIDList;
Pntr : PItemIDList;
StrRet : TStrRet;
PopupMenu : hMenu;
ItemID : Integer;
Pos : TPoint;
Procedure AddToMenu(Item : String);
Var S : String;
Begin
IconData[ItemID-cm_About] := Item;
S := ExtractFileName(Item);
If (System.Pos('.',S) <> 0) Then SetLength(S,System.Pos('.',S)-1);
AppendMenu(PopupMenu,mf_Enabled Or mf_String,ItemID,PChar(S));
Inc(ItemID);
End;
begin
PopupMenu := CreatePopupMenu;
ItemID := cm_About+1;
SHGetDesktopFolder(ShellFolder);
ShellFolder.EnumObjects(MainWindow,SHCONTF_NONFOLDERS,EnumIDList);
Pntr := @ItemIDList;
Result := EnumIDList.Next(1,Pntr,Dummy);
While (Result = NoError) do Begin
ShellFolder.GetDisplayNameOf(Pntr,SHGDN_FORPARSING,@StrRet);
With StrRet do AddToMenu(String(CStr));
Result := EnumIDList.Next(1,Pntr,Dummy);
End;
EnumIDList.Release;
ShellFolder.Release;
GetCursorPos(Pos);
AppendMenu(PopupMenu,mf_Separator,0,'');
AppendMenu(PopupMenu,mf_Enabled Or mf_String,cm_Exit,'E&xit');
SetForegroundWindow(MainWindow);
TrackPopupMenu(PopupMenu,tpm_LeftAlign Or tpm_LeftButton,
Pos.X,Pos.Y,0,MainWindow,nil);
DestroyMenu(PopupMenu);
end;
This somewhat complicated procedure can be divided into two different
parts: enumerating the items on the desktop and displaying the actual
menu.
OpenDesktopIcon(WParam-cm_About)
above. Of
course, WParam holds the item ID of the menu item user clicked.
Procedure OpenDesktopIcon(Number : Integer);
Var
S : String;
I : Integer;
begin
S := IconData[Number];
I := ShellExecute(0,nil,PChar(S),nil,nil,sw_ShowNormal);
If (I < 32) Then Begin
S := 'Could not open selected item "'+S+'". '+
'Result was: '+IntToStr(I)+'.';
MessageBox(0,PChar(S),'Shell Test',mb_OK);
End;
end;
Above, the Win32 API function ShellExecute does all the dirty work
like resolving the possible shortcuts. The important thing to note in
the call is the second parameter. It is the operation we wish to do
for the file. The parameter could be "open" or "print" or - as we have
it - nil. It would sound reasonable if the "open" parameter worked,
but it doesn't. If anybody knows why it must be nil, please mail me.
Reasons aside, simply pass in nil, and everything is OK.
Note: The Delphi interface files don't include all the
functions and objects needed in this How To. However, a file named
"shellobj.zip" is released as public domain, and it is used by this
application. This file is included in the archive below. You can also
download it from the
original
site.
Code for this How To
Questions? Comments? Give feedback.
Or just fill in a form.