I wrote a program that can identify outdated software in a Windows System and update them by interacting with the user.
It has a Software Updater Program which displays a System Tray Icon and show Balloon Tips about Available / Downloading Updates and Software installed in the System.
The problem is It can't show multiple Balloon Tips when each task is processing by it. Such as, when an update is available for a Software, it should remember user showing a balloon like An update for Software Name is available.
and when user choose to download and minimize it to system tray again, the balloon tip should again show something like Updates are downloading...Click to view the Progress of Downloads.
However I like to know how can I do this by using only one System Tray Icon?
Can I use the NIM_MODIFY
Flag again and again to change the Balloon Tip according to the current state of the Program?
I searched about this and I found some examples, but for Visual Studio and C++.
That's how I tried to show Multiple Tips when the Program is running:
unit MainForm-1;
...
const
NIF_INFO = $10;
NIF_MESSAGE = 1;
NIF_ICON = 2;
NOTIFYICON_VERSION = 3;
NIF_TIP = 4;
NIM_SETVERSION = $00000004;
NIM_SETFOCUS = $00000003;
NIIF_INFO = $00000001;
NIIF_WARNING = $00000002;
NIIF_ERROR = $00000003;
NIN_BALLOONSHOW = WM_USER + 2;
NIN_BALLOONHIDE = WM_USER + 3;
NIN_BALLOONTIMEOUT = WM_USER + 4;
NIN_BALLOONUSERCLICK = WM_USER + 5;
NIN_SELECT = WM_USER + 0;
NINF_KEY = $1;
NIN_KEYSELECT = NIN_SELECT or NINF_KEY;
TRAY_CALLBACK = WM_USER + $7258;
PNewNotifyIconData = ^TNewNotifyIconData;
TDUMMYUNIONNAME = record
case Integer of
0: (uTimeout: UINT);
1: (uVersion: UINT);
end;
TNewNotifyIconData = record
cbSize: DWORD;
Wnd: HWND;
uID: UINT;
uFlags: UINT;
uCallbackMessage: UINT;
hIcon: HICON;
szTip: array [0..127] of Char;
dwState: DWORD; /
dwStateMask: DWORD;
szInfo: array [0..255] of Char;
DUMMYUNIONNAME: TDUMMYUNIONNAME;
szInfoTitle: array [0..63] of Char;
dwInfoFlags: DWORD;
end;
type
MainForm-1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
IconData: TNewNotifyIconData;
procedure SysTrayIconMessageHandler(var Msg: TMessage); message TRAY_CALLBACK;
procedure AddSysTrayIcon;
procedure ShowBalloonTips;
procedure DeleteSysTrayIcon;
public
end;
var
MainForm-1: TForm;
implementation
uses
ShellAPI...,.....,;
procedure MainForm-1.SysTrayIconMessageHandler(var Msg: TMessage);
begin
case Msg.lParam of
WM_MOUSEMOVE:;
WM_LBUTTONDOWN:;
WM_LBUTTONUP:;
WM_LBUTTONDBLCLK:;
WM_RBUTTONDOWN:;
WM_RBUTTONUP:;
WM_RBUTTONDBLCLK:;
NIN_BALLOONSHOW:;
NIN_BALLOONHIDE:;
NIN_BALLOONTIMEOUT:
NIN_BALLOONUSERCLICK:;
end;
end;
procedure MainForm-1.AddSysTrayIcon;
begin
IconData.cbSize := SizeOf(IconData);
IconData.Wnd := AllocateHWnd(SysTrayIconMessageHandler);
IconData.uID := 0;
IconData.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
IconData.uCallbackMessage := TRAY_CALLBACK;
IconData.hIcon := Application.Icon.Handle;
IconData.szTip := 'Software Updater is running';
if not Shell_NotifyIcon(NIM_ADD, @IconData) then
ShowMessage('System Tray Icon cannot be created.');
end;
procedure MainForm-1.DisplayBalloonTips;
var
TipInfo, TipTitle: string;
begin
IconData.cbSize := SizeOf(IconData);
IconData.uFlags := NIF_INFO;
if ssHelperState = UpdatesAvailable then TipInfo := 'Updates are available to the programs installed on your Computer' + ' Click to see details.';
if ssHelperState = UpdatesDownloading then TipInfo := 'Updates are downloading in the background. Click to view the details.';
strPLCopy(IconData.szInfo, TipInfo, SizeOf(IconData.szInfo) - 1);
IconData.DUMMYUNIONNAME.uTimeout := 2500;
if ssHelperState = UpdatesAvailable then TipTitle := 'Updates are Available...';
if ssHelperState = UpdatesDownloading then TipTitle := 'Downloading the Updates...';
strPLCopy(IconData.szInfoTitle, TipTitle, SizeOf(IconData.szInfoTitle) - 1);
IconData.dwInfoFlags := NIIF_INFO;
Shell_NotifyIcon(NIM_MODIFY, @IconData);
{Following code is for testing purpose.}
IconData.DUMMYUNIONNAME.uVersion := NOTIFYICON_VERSION;
if not Shell_NotifyIcon(NIM_SETVERSION, @IconData) then
ShowMessage('Setting the Version is Failed.');
end;
procedure MainForm-1.DeleteSysTrayIcon;
begin
DeallocateHWnd(IconData.Wnd);
if not Shell_NotifyIcon(NIM_DELETE, @IconData) then
ShowMessage('Unable to delete System Tray Icon.');
end;
procedure MainForm-1.FormCreate(Sender: TObject);
begin
AddSysTrayIcon;
ShowBalloonTips;
end;
procedure MainForm-1.FormDestroy(Sender: TObject);
begin
DeleteSysTrayIcon;
end;
...
end.
But, this is failing and I keep getting the same Balloon Tip (First One) again and again when the Program is running.......
I don't know how to use NIN_BALLOONSHOW
and NIN_BALLOONHIDE
Flags correctly. So, Thanks in Advance for Your Important Help.
Why are you declaring everything manually? Delphi 2009 already has declarations for the Shell_NotifyIcon()
API. They are in the ShellAPI
unit. It declares just about everything you are trying to use, except for the uVersion
field (that was added in Delphi 2010). You are not using the guidItem
and hBalloonIcon
fields, so let's not worry about them here. The uTimeout
field exists, and since it is wrapped in a union with uVersion
, the data size does not change, so you can just use uTimeout
when you want to use uVersion
(or you can define your own union and type-cast the field, but that is overkill). You certainly do not need to redeclare the entire API.
You are reusing the same IconData
variable each time you call Shell_NotifyIcon()
, which is fine, but you are not clearing the szTip
and szInfoTitle
fields if your helper state is not UpdatesAvailable
or UpdatesDownloading
, so the tray icon keeps displaying the last tip/balloon you have set. You need to clear those fields when you don't need tips/balloons anymore.
NIN_BALLOONSHOW
and NIN_BALLOONHIDE
are not flags. They are notifications that are sent to your tray icon's registered HWND
. To receive the notifications, you need to fill in the Wnd
and uCallbackMessage
fields and enable the NIF_MESSAGE
flag.
Also, you need to handle the WM_TASKBARCREATED
message. If Explorer gets restarted for any reason (crashes, or is killed by the user), the Taskbar gets re-created, so you have to re-add your tray icon again.
Also, make sure your message handler passes any unhandled window messages to DefWindowProc()
, or you can lock up the system, or at least your app.
And lastly, Delphi 2009 is a Unicode version of Delphi, but there are some sections of your code that are not handling Unicode correctly. Specifically, when populating szTip
and szInfoTitle
using StrPLCopy()
, you need to use Length()
instead of SizeOf()
. The copy is expressed in number of characters, not number of bytes.
With that said, try something more like this:
unit MainForm1;
interface
uses
..., ShellAPI;
type
eHelperState = (Idle, UpdatesAvailable, UpdatesDownloading);
MainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
TaskbarCreatedMsg: UINT;
IconData: NOTIFYICONDATA;
IconAdded: Boolean;
ssHelperState: eHelperState;
procedure SysTrayIconMessageHandler(var Message: TMessage);
procedure AddSysTrayIcon;
procedure ShowBalloonTips;
procedure DeleteSysTrayIcon;
procedures SetHelperState(NewState: eHelperState);
...
end;
var
MainForm: TForm;
implementation
const
TRAY_CALLBACK = WM_USER + $7258;
{$IF RTLVersion < 21}
NOTIFYICON_VERSION_4 = 4;
{$IFEND}
procedure MainForm.FormCreate(Sender: TObject);
begin
TaskbarCreatedMsg := RegisterWindowMessage('TaskbarCreated');
IconData.cbSize := SizeOf(IconData);
IconData.Wnd := AllocateHWnd(SysTrayIconMessageHandler);
IconData.uID := 1;
AddSysTrayIcon;
end;
procedure MainForm.FormDestroy(Sender: TObject);
begin
DeleteSysTrayIcon;
DeallocateHWnd(IconData.Wnd);
end;
procedure MainForm.AddSysTrayIcon;
begin
IconData.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
IconData.uCallbackMessage := TRAY_CALLBACK;
IconData.hIcon := Application.Icon.Handle;
StrLCopy(IconData.szTip, 'Software Updater is running', Length(IconData.szTip));
IconAdded := Shell_NotifyIcon(NIM_ADD, @IconData);
if not IconAdded then
begin
ShowMessage('Unable to add System Tray Icon.');
Exit;
end;
if CheckWin32Version(5, 0) then
begin
IconData.{$IF RTLVersion >= 21}uVersion{$ELSE}uTimeout{$IFEND} := NOTIFYICON_VERSION_4;
if not Shell_NotifyIcon(NIM_SETVERSION, @IconData) then
ShowMessage('Unable to set version for System Tray Icon.');
end;
end;
procedure MainForm.DisplayBalloonTips;
var
Tip, InfoText, InfoTitle: string;
begin
if not IconAdded then Exit;
case ssHelperState of
UpdatesAvailable: begin
Tip := 'Updates are Available. Click to see details.';
InfoText := 'Updates are available to the programs installed on your Computer. Click to see details.';
InfoTitle := 'Updates are Available';
end;
UpdatesDownloading: begin
Tip := 'Downloading Updates. Click to see details.';
InfoText := 'Updates are downloading in the background. Click to see details.';
InfoTitle := 'Downloading Updates';
end;
else
Tip := 'Software Updater is running';
end;
IconData.uFlags := NIF_TIP or NIF_INFO;
StrPLCopy(IconData.szTip, Tip, Length(IconData.szTip));
StrPLCopy(IconData.szInfo, InfoText, Length(IconData.szInfo));
StrPLCopy(IconData.szInfoTitle, InfoTitle, Length(IconData.szInfoTitle));
IconData.uTimeout := 2500;
IconData.dwInfoFlags := NIIF_INFO;
if not Shell_NotifyIcon(NIM_MODIFY, @IconData) then
ShowMessage('Unable to update System Tray Icon.')
end;
procedure MainForm.DeleteSysTrayIcon;
begin
if IconAdded then
begin
IconAdded := False;
if not Shell_NotifyIcon(NIM_DELETE, @IconData) then
ShowMessage('Unable to delete System Tray Icon.');
end;
end;
procedures MainForm.SetHelperState(NewState: eHelperState);
begin
if ssHelperState <> NewState then
begin
ssHelperState := NewState;
DisplayBalloonTips;
end;
end;
procedure MainForm.SysTrayIconMessageHandler(var Message: TMessage);
begin
if Message.Msg = TRAY_CALLBACK then
begin
case LOWORD(Message.LParam) of
WM_MOUSEMOVE: begin
//...
end;
WM_LBUTTONDBLCLK,
NIN_BALLOONUSERCLICK: begin
// display status window...
end;
WM_CONTEXTMENU,
NIN_KEYSELECT,
NIN_SELECT: begin
// display popup menu at coordinates specified by Msg.WParam...
end;
NIN_BALLOONSHOW:;
NIN_BALLOONHIDE:;
NIN_BALLOONTIMEOUT:;
end;
end
else if (Message.Msg = TaskbarCreatedMsg) and (TaskbarCreatedMsg <> 0) then
begin
IconAdded := False;
AddSysTrayIcon;
DisplayBalloonTips;
end
else begin
Message.Result := DefWindowProc(IconData.Wnd, Message.Msg, Message.WParam, Message.LParam);
end;
end;
...
end.