Ask the Delphi Pro 10-Minute Solutions
 |
Creating a System Tray Application
By Brendan Delumpa
Isn't it funny how coding certain things in Windows that appear as if they should be easy to implement, ultimately become exercises in "hair pulling"? It's not so much that the task of coding is that difficult; the information you need to accomplish it is either hidden behind layers of related, hyperlinked material (for example, Windows help) or does not exist in the first place (certain "omissions" in the Delphi technical documentation. Have I been here before?). Well, such is the case with creating System Tray applications. Implementation is actually a snap, but getting to the point where you know enough to implement it is not so straightforward. And when you see the code for creating a System Tray application, like me, you'll probably smirk. But don't worry, it's not something that'll make you say, "Duh!" Let's take a look...
There are two distinct things that you need to take care of to successfully create a System Tray application. The first thing is to "hide" your application from Windows. While System Tray applications look and act the same as any Windows program, they can't be switched to using Alt-Tab, nor do they have a button on the task bar. So we'll take care of that first.
By convention, any window that has a style of WS_EX_TOOLWINDOW is not available to the task bar, nor are users able to switch to it. At first, you might think that you would set this style using the CreateParams procedure. Unfortunately, that procedure will only work on a form. Now let's make a distinction here. The main form of the application is not the application's window. An application object has its own window--you can't see it, but it's there just the same. This is the window to which you apply the WS_EX_TOOLWINDOW style. So where do you put the code? In the project source, of course. So, open up an new project, then select View|Project Source from the main menu in the IDE. Once there, copy and paste the following code into the editor window.
program Project1;
uses
Forms,
Unit1 in 'Unit1.pas' {Form1},
Windows; //This is required to test the window style
{$R *.RES}
//Declare a var to retrieve current window information
var
ExtendedStyle : Integer;
begin
Application.Initialize;
//Get the Extended Styles of the Application, by passing its
//handle to GetWindowLong
ExtendedStyle := GetWindowLong(Application.Handle, GWL_EXSTYLE);
//Now, set the Extended Style by doing a bit masking operation.
//OR in the WS_EX_TOOLWINDOW bit, and AND out the WS_EXAPPWINDOW bit
//This effectively converts the application from an App Windows to a
//Tool Window.
SetWindowLong(Application.Handle, GWL_EXSTYLE, ExtendedStyle OR WS_EX_TOOLWINDOW
AND NOT WS_EX_APPWINDOW);
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Now, to actually create the effect of a System Tray application (put our application into the System Tray itself) is a bit more involved than hiding the application, but by no means is it a difficult proposition. So what will you need? First of all you'll need the main form of the application. Once there, drop a TPopupMenu component on the form. The popup menu will act as the main interface for performing various actions you'd like to take with your System Tray application. Take a look at the code below:
This sets up the application to be a system tray application and is the main form for the application. It has a popup menu that will be used to display the main form, or close the application. And using the ShellAPI unit, we can then use a couple of calls to display the application's icon on the system tray, and make it respond to a right mouse click.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ShellAPI, ExtCtrls, Menus;
type
TForm1 = class(TForm)
PopupMenu1: TPopupMenu;
ShowMainForm1: TMenuItem;
N1: TMenuItem;
ExitApplication1: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure ShowMainForm1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ExitApplication1Click(Sender: TObject);
private
procedure WndProc(var Msg : TMessage); override;
public
IconNotifyData : TNotifyIconData;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
//Set the border icons to have only a system menu. This will
//leave just the close button.
BorderIcons := [biSystemMenu];
//Now set up the IconNotifyData structure so that it receives
//the window messages sent to the application and displays
//the application's tips
with IconNotifyData do begin
hIcon := Application.Icon.Handle;
uCallbackMessage := WM_USER + 1;
cbSize := sizeof(IconNotifyData);
Wnd := Handle;
uID := 100;
uFlags := NIF_MESSAGE + NIF_ICON + NIF_TIP;
end;
//Copy the Application's Title into the tip for the icon
StrPCopy(IconNotifyData.szTip, Application.Title);
//Add the Icon to the system tray and use the
//the structure and its values
Shell_NotifyIcon(NIM_ADD, @IconNotifyData);
end;
procedure TForm1.WndProc(var Msg : TMessage);
var
p : TPoint;
begin
case Msg.Msg of
WM_USER + 1:
case Msg.lParam of
WM_RBUTTONDOWN: begin
GetCursorPos(p);
PopupMenu1.Popup(p.x, p.y);
end;
end;
end;
inherited;
end;
{This is one of the popup menu item's OnClick handler}
procedure TForm1.ShowMainForm1Click(Sender: TObject);
begin
Form1.Show;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caNone;
Form1.Hide;
end;
procedure TForm1.ExitApplication1Click(Sender: TObject);
begin
Shell_NotifyIcon(NIM_DELETE, @IconNotifyData);
Application.ProcessMessages;
Application.Terminate;
end;
end.
As you can see, there's really not much to this. Just study the code to see what's going on. But the important thing you should concentrate on is the Create method of the form and what is done to the IconNotifyData structure. This is a record structure declared in the ShellAPI unit that stores information for a tray icon. Notice the flags that were used: NIF_MESSAGE + NIF_ICON + NIF_TIP. These flags tell the icon to process application messages, use the application's icon and its tip, respectively. Once we've set up all that, then it's a simple matter of creating the window interaction stuff, like we'd normally do at design time. The tray icon doesn't come into play until run time.
The other thing to look at is the override of the WndProc procedure. WndProc is short for Window Procedure. It intercepts all the messages sent to the window, and acts as the central message dispatcher. In that procedure, you can trap specific Windows messages by overriding the inherited procedure. In our case, we trap two things: the Msg field of the message sent to the application. If it was our custom message (WM_USER + 1) defined for the IconNotifyData variable, then we want to handle a right-click. All other messages sent to the application are handled in their normal fashion.
I realize that this 10-Minute Solution was pretty quick and dirty, so I encourage you to play around with the code. Just keep in mind that you have to do two things, if you're going to create system tray application:
- You need to first create the "hiding" mechanism for the application.
- Then, you need to create the interface so that you can interact with the application when its main form isn't being displayed.
Notice that I didn't really do too much with the form. I wanted to keep the presentation as simple as possible. However, there is nothing to stop you from making a more complex menu with more complicated actions. Remember: a System Tray application is pretty much like any other Windows program.
|
|
|