Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

For Vladimir Stefanovic : TAnimatedTrayIcon

94 views
Skip to first unread message

JD

unread,
Mar 22, 2006, 6:00:12 AM3/22/06
to

Vladimir:

I never expected so many people to use TAnimatedTrayIcon
so I never even considered version numbers but with the new
TTrayIcon in BDS, while better than the old, it's still weak so
I'd expect that the more people that are aware of it, the more
people that will use it. Since you last saw it, I've made
several changes to the component and even a couple of custom
jobs for individuals so I've labeled it's version as 1.5.0.

The changes that have been made no longer require it to be
Owned by the main form and it should be fine with multiple
instance in the application but I just haven't had time to get
around to changing the code and testing it.

The only question that I have with it is with the
DetermineShellVersion method that sets the FCanBalloon
property. Currently, ballooning is only allowed if the
SHELL32.dll version is greater than or equal to 5.0 but
I ran across something on MSN that makes me think that
ballooning should also be allowed for win2k (4.0) but I
don't have that OS to test it.

I also haven't tested it as a component installed in the IDE
but I did code it to be installed so I'm presuming that all is
well on that front.

Let me know if you find any bugs or make any improvements.

~ JD

//---------------------------------------------------------------------------
//- -
//- TAnimatedTrayIcon version 1.5.0 -
//- -
//- You may use this code in whole or in part for any purpose except that -
//- you may not sell it as a component or part of a component pack. It's -
//- free for any one who wants it - period. -
//- -
//- Direct questions, comments and changes to ide...@hotmail.com or post -
//- to the vcl.using group. -
//- -
//---------------------------------------------------------------------------
#ifndef AnimatedTrayIconH
#define AnimatedTrayIconH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <vector>
//---------------------------------------------------------------------------
// M$ says this should be 0x0500 for shellapi >= 5.0
// but cpp needs it to be 0x0501 or 0x0600
#undef _WIN32_IE
#define _WIN32_IE 0x0501
#include <shellapi.h>
#include <shlwapi.h>
#include <mmsystem.h>
//---------------------------------------------------------------------------
#include "TrayThread.h"
//---------------------------------------------------------------------------
enum TBalloonIconType
{
itNone = 0,
itInformation,
itWarning,
itError,
itQuestion,
itNoSound,
itUser
};
//---------------------------------------------------------------------------
class TAnimatedTrayIcon : public TComponent
{
private:
#define NIIF_QUESTION 0x07
#define NIIF_USER 0x14
typedef TComponent inherited;
LPVOID MinimizeWave, RestoreWave;
DWORD MinimizeSize, RestoreSize;
unsigned int ClickTimer;
unsigned int CM_TRAYNOTIFY;
unsigned int CM_TRAYCREATED;
unsigned int CM_TASKBARCREATED;
std::vector<HWND>vZOrder;
std::vector<TForm*>vForms;

HWND FHandle;
TForm *FParentForm;
TRect BoundsRect, TrayRect;
TAnimatedTrayIconThread *FThread;
bool FMinimized;
bool FHooked;
bool FInstalled;
bool FAnimating;
bool FCanBalloon;

bool FAnimateWindow;
bool FLoopSound;
bool FUseTray;
int FDelay;
int FStaticIndex;
bool FActive;
int FTrayID;
int FFromIndex;
int FToIndex;

TThreadPriority FPriority;
AnsiString FStaticHint;
AnsiString FAnimationHint;
AnsiString FAnimateWave;
TImageList *FImageList;
TPopupMenu *FPopupMenu;
HIMAGELIST hImageList;

AnsiString FBalloonCaption;
AnsiString FBalloonText;
TBalloonIconType FBalloonIconType;
UINT FBalloonIcon;
UINT FBalloonTimeout;

DWORD FStartTickCount;
TShiftState FLeftClickShift;
TMouseEvent FOnMouseUp;
TMouseEvent FOnMouseDown;
TNotifyEvent FOnClick;
TNotifyEvent FOnDblClick;
TNotifyEvent FOnBalloonClick;
TNotifyEvent FOnBalloonShow;
TNotifyEvent FOnBalloonHide;
TNotifyEvent FOnBalloonClose;
TNotifyEvent FOnBalloonTimeOut;

void __fastcall LoadSound( AnsiString ID, DWORD &Size, LPVOID &hMemWave );
void __fastcall SetClickTimer( bool Enable );
void __fastcall ModifyTray();
void __fastcall DetermineShellVersion();
void __fastcall GetSysTrayRect();

// setter methods
void __fastcall SetActive( bool State );
void __fastcall SetStaticIndex( int Index );
void __fastcall SetStaticHint( AnsiString Hint );
void __fastcall SetImageList( TImageList* );
void __fastcall SetPopupMenu( TPopupMenu* );
protected:
virtual void __fastcall Loaded();
virtual void __fastcall Notification( TComponent *AComponent, TOperation Operation );
virtual void __fastcall WndProc( TMessage &Message );
void __fastcall MouseDown(TMouseButton Button, TShiftState Shift );
void __fastcall MouseUp(TMouseButton Button, TShiftState Shift );
bool __fastcall AppHook( TMessage &Message );
public:
void __fastcall StartAnimation();
void __fastcall StopAnimation();
bool __fastcall Minimize();
bool __fastcall Restore();
void __fastcall DoBalloonTip();
__fastcall TAnimatedTrayIcon(TComponent* Owner);
__fastcall TAnimatedTrayIcon::~TAnimatedTrayIcon();
__published:
__property TMouseEvent OnMouseUp = { read = FOnMouseUp, write = FOnMouseUp };
__property TMouseEvent OnMouseDown = { read = FOnMouseDown, write = FOnMouseDown };
__property TNotifyEvent OnClick = { read = FOnClick, write = FOnClick };
__property TNotifyEvent OnDblClick = { read = FOnDblClick, write = FOnDblClick };
__property TNotifyEvent OnBalloonClick = { read = FOnBalloonClick, write = FOnBalloonClick };
__property TNotifyEvent OnBalloonShow = { read = FOnBalloonShow, write = FOnBalloonShow };
__property TNotifyEvent OnBalloonHide = { read = FOnBalloonHide, write = FOnBalloonHide };
__property TNotifyEvent OnBalloonClose = { read = FOnBalloonClose, write = FOnBalloonClose };
__property TNotifyEvent OnBalloonTimeOut = { read = FOnBalloonTimeOut, write = FOnBalloonTimeOut };
__property bool AnimateWindow = { read = FAnimateWindow, write = FAnimateWindow, default = false };
__property int AnimationDelay = { read = FDelay, write = FDelay, default = 50 };
__property int TrayID = { read = FTrayID, write = FTrayID, default = 1643 };
__property int AnimateIndexStart = { read = FFromIndex, write = FFromIndex };
__property int AnimateIndexEnd = { read = FToIndex, write = FToIndex };
__property AnsiString AnimationHint = { read = FAnimationHint, write = FAnimationHint };
__property AnsiString AnimateWaveSound = { read = FAnimateWave, write = FAnimateWave };
__property TThreadPriority AnimationPriority = { read = FPriority, write = FPriority, default = tpNormal };
__property TImageList *ImageList = { read = FImageList, write = SetImageList };
__property TPopupMenu *PopupMenu = { read = FPopupMenu, write = SetPopupMenu };
__property bool LoopAnimateSound = { read = FLoopSound, write = FLoopSound, default = true };
__property bool Active = { read = FActive, write = SetActive, default = false };
__property int StaticIndex = { read = FStaticIndex, write = SetStaticIndex };
__property AnsiString StaticHint = { read = FStaticHint, write = SetStaticHint };
__property bool MinimizeToTray = { read = FUseTray, write = FUseTray, default = true };
__property AnsiString BalloonCaption = { read = FBalloonCaption, write = FBalloonCaption };
__property AnsiString BalloonText = { read = FBalloonText, write = FBalloonText };
__property TBalloonIconType BalloonIconType = { read = FBalloonIconType, write = FBalloonIconType, default = itInformation };
// HICON is not an allowed type so I just used UINT and cast it when it's used
__property UINT BalloonIcon = { read = FBalloonIcon, write = FBalloonIcon };
__property UINT BalloonTimeout = { read = FBalloonTimeout, write = FBalloonTimeout, default = 1500 };
};
//---------------------------------------------------------------------------
#endif


//---------------------------------------------------------------------------
#include <vcl.h>
//---------------------------------------------------------------------------
#pragma hdrstop
//---------------------------------------------------------------------------
#include "AnimatedTrayIcon.h"
//---------------------------------------------------------------------------
// Tray.res is not included with the sample.
// All it is [was] is the sound resources.
//#pragma resource "Tray.res"
//---------------------------------------------------------------------------
#pragma package(smart_init)

// Used to ensure only a single instance of this object type application wide
TAnimatedTrayIcon *ThisAnimatedTrayIconInstance = NULL;
//---------------------------------------------------------------------------
__fastcall TAnimatedTrayIcon::TAnimatedTrayIcon( TComponent *Owner ) : TComponent(Owner)
{
if( ThisAnimatedTrayIconInstance )
{
throw Exception("TAnimatedTrayIcon : Only one instance is allowed per application.");
}
if( !Owner )
{
throw Exception("TAnimatedTrayIcon : No Owner assigned.");
}
FParentForm = dynamic_cast<TForm*>( Owner );
if( !FParentForm )
{
throw Exception("TAnimatedTrayIcon : Owner must be a TForm.");
}
ThisAnimatedTrayIconInstance = this;

FThread = NULL;
ClickTimer = NULL;
RestoreWave = NULL;
MinimizeWave = NULL;
FBalloonIcon = NULL;
RestoreSize = 0;
MinimizeSize = 0;
FInstalled = false;
FAnimating = false;
FHooked = false;
FHandle = NULL;

if( !ComponentState.Contains(csDesigning) )
{
GetSysTrayRect();
DetermineShellVersion();

// load from resource - ok if no resource
LoadSound( "MINIMIZE", MinimizeSize, MinimizeWave );
LoadSound( "RESTORE", RestoreSize, RestoreWave );

CM_TRAYNOTIFY = ::RegisterWindowMessage( "TAnimatedTrayIconNotifyMessage" );
CM_TRAYCREATED = ::RegisterWindowMessage( "TAnimatedTrayIconCreatedMessage" );
CM_TASKBARCREATED = ::RegisterWindowMessage( "TaskbarCreated" );

Application->HookMainWindow( AppHook );
FHooked = true;

FHandle = AllocateHWnd( WndProc );

::PostMessage( FHandle, CM_TRAYCREATED, 0, 0 );
}
}
//---------------------------------------------------------------------------
__fastcall TAnimatedTrayIcon::~TAnimatedTrayIcon()
{
SetActive( false );
if( ClickTimer ) ::KillTimer( FHandle, ClickTimer );
if( FHandle ) DeallocateHWnd( FHandle );
if( FHooked ) Application->UnhookMainWindow( AppHook );
if( MinimizeWave ) ::VirtualFree( MinimizeWave, MinimizeSize, MEM_FREE );
if( RestoreWave ) ::VirtualFree( RestoreWave, RestoreSize, MEM_FREE );
ThisAnimatedTrayIconInstance = NULL;
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::Notification( TComponent *AComponent, TOperation Operation )
{
inherited::Notification( AComponent, Operation );
if( Operation == opRemove )
{
if( AComponent == FImageList ) SetImageList( NULL );
else if( AComponent == FPopupMenu ) SetPopupMenu( NULL );
}
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::Loaded()
{
inherited::Loaded();
if( FActive ) ModifyTray();
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::GetSysTrayRect()
{
::GetWindowRect( ::FindWindowEx(::FindWindow("Shell_TrayWnd", NULL), NULL, "TrayNotifyWnd", NULL), &TrayRect );
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::DetermineShellVersion()
{
WORD Major = 0;
HINSTANCE hShell = ::GetModuleHandle( "SHELL32" );
if( hShell )
{
DLLGETVERSIONPROC lpDllGetVersion = (DLLGETVERSIONPROC) ::GetProcAddress( hShell, "DllGetVersion" );
if( lpDllGetVersion )
{
DLLVERSIONINFO dvi = { sizeof(DLLVERSIONINFO), 0 };
if SUCCEEDED(lpDllGetVersion(&dvi)) Major = dvi.dwMajorVersion;
}
}
if( Major < 5 ) FCanBalloon = false;
else FCanBalloon = true;
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::LoadSound( AnsiString ID, DWORD &Size, LPVOID &MemoryWave )
{
HRSRC SoundResource = ::FindResource( HInstance, ID.c_str(), "WAVE" );
if( SoundResource )
{
Size = ::SizeofResource( HInstance, SoundResource );
if( Size )
{
HGLOBAL SoundLoad = ::LoadResource( HInstance, SoundResource );
if( SoundLoad )
{
LPVOID SoundPointer = ::LockResource( SoundLoad );
if( SoundPointer )
{
MemoryWave = ::VirtualAlloc( NULL, Size, MEM_COMMIT, PAGE_READWRITE );
if( MemoryWave ) ::CopyMemory( MemoryWave, SoundPointer, Size );
}
}
}
}
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::ModifyTray()
{
NOTIFYICONDATA IconData = { 0 };
IconData.cbSize = sizeof( NOTIFYICONDATA );
IconData.hWnd = FHandle;
IconData.uID = FTrayID;
if( ComponentState.Contains(csDestroying) )
{
if( FInstalled ) ::Shell_NotifyIcon( NIM_DELETE, &IconData );
}
else if( !ComponentState.Contains(csLoading) && !ComponentState.Contains(csDesigning) )
{
IconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
IconData.uCallbackMessage = CM_TRAYNOTIFY;
IconData.hIcon = NULL;
StrLCopy( IconData.szTip, FStaticHint.c_str(), sizeof(IconData.szTip) - 1 );
if( FActive )
{
if( !FImageList )
{
FActive = false;
throw Exception("TAnimatedTrayIcon : ImageList not assigned.");
}
if( FStaticIndex >= FImageList->Count )
{
FActive = false;
throw Exception("TAnimatedTrayIcon : Index range error.");
}
IconData.hIcon = ::ImageList_GetIcon( hImageList, FStaticIndex, ILD_TRANSPARENT );
if( !FInstalled )
{
::Shell_NotifyIcon( NIM_ADD, &IconData );
FInstalled = true;
}
else ::Shell_NotifyIcon( NIM_MODIFY, &IconData );
::DestroyIcon( IconData.hIcon );
}
else if( FInstalled )
{
::Shell_NotifyIcon( NIM_DELETE, &IconData );
FInstalled = false;
}
}
}
//----------------------------------------------------------------------------
//--- ---
//--- M M OOO U U SSSS EEEEE ---
//--- MM MM O O U U S E ---
//--- M M M O O U U SSS EEEE ---
//--- M M O O U U S E ---
//--- M M OOO UUU SSSS EEEEE ---
//--- ---
//----------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::MouseDown( TMouseButton Button, TShiftState Shift )
{
if( FOnMouseDown )
{
POINT P;
::GetCursorPos( &P );
FOnMouseDown( this, Button, Shift, P.x, P.y );
}
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::MouseUp( TMouseButton Button, TShiftState Shift )
{
POINT P;
::GetCursorPos( &P );
if( Button == mbRight )
{
if( FPopupMenu )
{
::SetForegroundWindow( Application->MainForm->Handle );
FPopupMenu->PopupComponent = this;
FPopupMenu->Popup( P.x, P.y );
::PostMessage( Application->MainForm->Handle, WM_NULL, 0, 0 );
}
}
else if( Button == mbLeft )
{
if( FOnClick ) FOnClick( this );
else Restore();
}
if( FOnMouseUp ) FOnMouseUp( this, Button, Shift, P.x, P.y );
}
//----------------------------------------------------------------------------
//--- ---
//--- H H OOO OOO K K SSSS ---
//--- H H O O O O K K S ---
//--- HHHHH O O O O K K SSS ---
//--- H H O O O O K K S ---
//--- H H OOO OOO K K SSSS ---
//--- ---
//----------------------------------------------------------------------------
bool __fastcall TAnimatedTrayIcon::AppHook( TMessage &Message )
{
if( Message.Msg == WM_SYSCOMMAND )
{
switch( Message.WParam )
{
case SC_RESTORE:
case SC_MAXIMIZE: return Restore();
case SC_MINIMIZE: return Minimize();
}
}
return false;
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::WndProc( TMessage &Message )
{
/****
Adding mouse events presented a problem because of the way that windows
generates the WM_ messages. In response to a double click, Windows actually
generate 4 messsages: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, and
WM_LBUTTONUP again. The problem this creates is being able to distinguish
between a single mouse click and the first click of a double click. In
addition, the final WM_LBUTTONUP message signals another click.

How I handled this was to use a timer which is enabled on the first
WM_LBUTTONUP and disabled on the WM_LBUTTONDBLCLK. If a WM_LBUTTONDBLCLK is
not detected within the specified system time for a double click, the timer
activates and MouseUp is executed. If a WM_LBUTTONDBLCLK is detected, the
timer is disabled, causing the first WM_LBUTTONUP to be ignored, and the
double click is processed.

The last problem was to deal with the final WM_LBUTTONUP message. I handled
this by making the calls to SetClickTimer (in a fashion) nested. This way,
when WM_LBUTTONDBLCLK is processed, by disabling the timer a second time,
the next call to enable it does nothing - which is exactly what needs to
happen when that final WM_LBUTTONUP is detected.
****/

if( Message.Msg == CM_TRAYNOTIFY )
{
TShiftState Shift = KeysToShiftState( Message.WParam );
switch( Message.LParam )
{
case WM_LBUTTONUP: FLeftClickShift = Shift;
SetClickTimer( true );
break;
case WM_MBUTTONUP: MouseUp ( mbMiddle, Shift ); break;
case WM_RBUTTONUP: MouseUp ( mbRight, Shift ); break;
case WM_LBUTTONDOWN: MouseDown( mbLeft, Shift ); break;
case WM_MBUTTONDOWN: MouseDown( mbMiddle, Shift ); break;
case WM_RBUTTONDOWN: MouseDown( mbRight, Shift ); break;
case WM_LBUTTONDBLCLK: SetClickTimer( false );
SetClickTimer( false );
if( FOnDblClick ) FOnDblClick( this );
else Restore();
break;
case NIN_BALLOONUSERCLICK: if( FOnBalloonClick ) FOnBalloonClick( this ); break;
case NIN_BALLOONSHOW: if( FOnBalloonShow ) FOnBalloonShow( this ); break;
case NIN_BALLOONHIDE: // This message is only sent if the icon is deleted
// from the tray while the balloon is still active.
// IMO, since the programmer controls when the icon
// is deleted from the tray, it's seems to be a
// rather useless notification but it's included
// here because it's part of the object as defined
// by MS and *might* be wanted by someone.
if( FOnBalloonHide ) FOnBalloonHide( this ); break;
case NIN_BALLOONTIMEOUT: // This message is sent when the balloon times out
// *or* when the user clicks the close button.
if( ::GetTickCount() > (FStartTickCount + FBalloonTimeout) )
{
if( FOnBalloonTimeOut ) FOnBalloonTimeOut( this );
}
else
{
if( FOnBalloonClose ) FOnBalloonClose( this );
}
break;
}
}
else if( Message.Msg == WM_TIMER && (unsigned)Message.WParam == ClickTimer )
{
SetClickTimer( false );
MouseUp( mbLeft, FLeftClickShift );
}
else if( Message.Msg == CM_TRAYCREATED )
{
if( Application->MainForm )
{
if( FParentForm != Application->MainForm )
{
MessageDlg( "TAnimatedTrayIcon can only be placed on the Main Form.", mtError, TMsgDlgButtons() << mbOK, 0 );
Application->Terminate();
}
else
{
if( IsIconic(Application->Handle) ) FMinimized = true;
else FMinimized = false;
BoundsRect = Application->MainForm->BoundsRect;
}
}
else ::PostMessage( FHandle, CM_TRAYCREATED, 0, 0 );
}
else if( Message.Msg == CM_TASKBARCREATED && CM_TASKBARCREATED != 0 )
{
GetSysTrayRect();
if( FInstalled )
{
if( FLoopSound ) ::PlaySound( NULL, NULL, NULL );
if( FAnimating ) StopAnimation();
FInstalled = false;
ModifyTray();
}
}
else Message.Result = DefWindowProc( FHandle, Message.Msg, Message.WParam, Message.LParam );
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::SetClickTimer( bool Enable )
{
static int Count = 0;
if( Enable )
{
if( Count == 0 )
{
ClickTimer = ::SetTimer( FHandle, FTrayID, ::GetDoubleClickTime(), NULL );
}
++Count;
}
else
{
if( ClickTimer )
{
::KillTimer( FHandle, ClickTimer );
ClickTimer = NULL;
}
--Count;
}
}
//----------------------------------------------------------------------------
//--- ---
//--- SSSS EEEEE TTTTT TTTTT EEEEE RRRR SSSS ---
//--- S E T T E R R S ---
//--- SSS EEEE T T EEEE RRRR SSS ---
//--- S E T T E R R S ---
//--- SSSS EEEEE T T EEEEE R R SSSS ---
//--- ---
//----------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::SetActive( bool State )
{
if( FActive != State )
{
FActive = State;
ModifyTray();
}
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::SetPopupMenu( TPopupMenu *PopupMenu )
{
if( FPopupMenu != PopupMenu )
{
FPopupMenu = PopupMenu;
if( FPopupMenu ) FPopupMenu->FreeNotification( this );
}
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::SetStaticIndex( int Index )
{
if( FStaticIndex != Index )
{
FStaticIndex = Index;
ModifyTray();
}
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::SetStaticHint( AnsiString Hint )
{
if( FStaticHint != Hint )
{
FStaticHint = Hint;
ModifyTray();
}
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::SetImageList( TImageList *ImageList )
{
if( FImageList != ImageList )
{
bool WasActive = FActive;
if( FAnimating ) StopAnimation();
FImageList = ImageList;
if( FInstalled ) SetActive( false );
if( FImageList )
{
hImageList = (HIMAGELIST)FImageList->Handle;
FImageList->FreeNotification( this );
if( FImageList->Count > 0 )
{
FStaticIndex = 0;
if( FImageList->Count > 1 )
{
FFromIndex = 1;
FToIndex = FImageList->Count - 1;
}
else
{
FFromIndex = 0;
FToIndex = 0;
}
if( WasActive ) SetActive( true );
}
else
{
FStaticIndex = -1;
FFromIndex = -1;
FToIndex = -1;
}
}
else hImageList = NULL;
}
}
//----------------------------------------------------------------------------
//--- ---
//--- PPPP U U BBBB L IIIII CCCC ---
//--- P P U U B H L I C ---
//--- PPPP U U BBBB L I C ---
//--- P U U B B L I C ---
//--- P UUU BBBB LLLLL IIIII CCCC ---
//--- ---
//----------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::StartAnimation()
{
if( !FThread )
{
if( !FImageList ) throw Exception("TAnimatedTrayIcon : ImageList not assigned.");
if( FFromIndex < 0 || FToIndex < 0 || FFromIndex >= FImageList->Count || FToIndex >= FImageList->Count || FFromIndex > FToIndex ) throw Exception("TAnimatedTrayIcon : Index range error.");
if( FDelay < 1 ) throw Exception("TAnimatedTrayIcon : Invalid Delay factor.");

// Play sound directly from resource
unsigned int Flags = SND_ASYNC | SND_NODEFAULT | SND_NOWAIT;
if( FLoopSound ) Flags |= SND_LOOP;
::PlaySound( FAnimateWave.c_str(), HInstance, Flags );

FThread = new TAnimatedTrayIconThread( // current tray info
FInstalled,
FStaticIndex,
FStaticHint,
FTrayID,
hImageList,
FHandle,
CM_TRAYNOTIFY,
// new tray info
FFromIndex,
FToIndex,
FAnimationHint,
// thread info
FPriority,
FDelay );
FAnimating = true;
}
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::StopAnimation()
{
if( FThread )
{
::PlaySound( NULL, NULL, NULL );
FThread->Terminate();
FThread->WaitFor();
delete FThread;
FThread = NULL;
FAnimating = false;
}
}
//---------------------------------------------------------------------------
bool __fastcall TAnimatedTrayIcon::Minimize()
{
FMinimized = true;
if( FUseTray && FActive )
{
if( !FAnimating || !FLoopSound )
{
::PlaySound( (LPCSTR)MinimizeWave, NULL, SND_MEMORY | SND_ASYNC | SND_NODEFAULT | SND_NOWAIT );
}
if( FAnimateWindow )
{
BoundsRect = Application->MainForm->BoundsRect;
::DrawAnimatedRects(Application->MainForm->Handle, IDANI_CAPTION, &BoundsRect, &TrayRect );
}
if( Application->MainForm->FormStyle != fsMDIForm )
{
for( int x = 0; x < Screen->FormCount; ++x )
{
if( Screen->Forms[x]->Visible ) vForms.push_back( Screen->Forms[x] );
}
HWND hWnd = ::GetTopWindow( Application->MainForm->Handle );
while( hWnd )
{
vZOrder.push_back( hWnd );
hWnd = ::GetNextWindow( hWnd, GW_HWNDNEXT );
}
for( unsigned int x = 0; x < vForms.size(); ++x ) vForms[x]->Visible = false;
}
else Application->MainForm->Visible = false;
Application->ShowMainForm = false;
::ShowWindow( Application->Handle, SW_HIDE );
::SetWindowLong( Application->Handle, GWL_STYLE, ::GetWindowLong(Application->Handle, GWL_STYLE) | WS_MINIMIZE );
return true;
}
return false;
}
//---------------------------------------------------------------------------
bool __fastcall TAnimatedTrayIcon::Restore()
{
bool Result = false;
if( FUseTray && FActive && FMinimized )
{
if( !FAnimating || !FLoopSound )
{
::PlaySound( (LPCSTR)RestoreWave, NULL, SND_MEMORY | SND_ASYNC | SND_NODEFAULT | SND_NOWAIT );
}
if( FAnimateWindow )
{
::DrawAnimatedRects(Application->MainForm->Handle, IDANI_CAPTION, &TrayRect, &BoundsRect );
}
if( Application->MainForm->FormStyle != fsMDIForm )
{
for( int x = vForms.size() - 1; x > -1; --x ) vForms[x]->Visible = true;
for( int x = vZOrder.size() - 1; x > -1; --x ) ::ShowWindow( vZOrder[x], SW_SHOW );
vZOrder.clear();
vForms.clear();
}
else
{
Application->MainForm->Visible = true;
Application->MainForm->ArrangeIcons();
}
Application->ShowMainForm = true;
::ShowWindow( Application->Handle, SW_SHOW );
Application->Restore();
Application->BringToFront();
Result = true;
}
FMinimized = false;
return Result;
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::DoBalloonTip()
{
if( FCanBalloon && FActive )
{
NOTIFYICONDATA IconData = { 0 };
IconData.cbSize = sizeof( IconData );
IconData.hWnd = FHandle;
IconData.uID = FTrayID;
IconData.uTimeout = FBalloonTimeout;
IconData.uFlags = NIF_INFO;
StrLCopy( IconData.szInfo, FBalloonText.c_str(), 255 );
StrLCopy( IconData.szInfoTitle, FBalloonCaption.c_str(), 63 );

switch( FBalloonIconType )
{
case itNoSound: IconData.dwInfoFlags = NIIF_NOSOUND; break;
case itError: IconData.dwInfoFlags = NIIF_ERROR; break;
case itWarning: IconData.dwInfoFlags = NIIF_WARNING; break;
case itQuestion: IconData.dwInfoFlags = NIIF_QUESTION; break;
case itInformation: IconData.dwInfoFlags = NIIF_INFO; break;
case itUser: if( FBalloonIcon )
{
IconData.dwInfoFlags = NIIF_USER;
IconData.uFlags |= NIF_ICON;
IconData.hIcon = (HICON) FBalloonIcon;
}
else IconData.dwInfoFlags = NIIF_NONE;
break;
default : IconData.dwInfoFlags = NIIF_NONE;
}
Shell_NotifyIcon( NIM_MODIFY, &IconData );
FStartTickCount = ::GetTickCount();
}
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#ifndef TrayThreadH
#define TrayThreadH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <ImgList.hpp>
//---------------------------------------------------------------------------
class TAnimatedTrayIconThread : public TThread
{
private:
bool Installed;
int StaticIndex;
AnsiString StaticHint;
int TrayID;
HIMAGELIST hImageList;
HWND hWnd;
unsigned int CM_TRAYNOTIFY;
int FromIndex;
int ToIndex;
AnsiString AnimateHint;
int Delay;
protected:
void __fastcall Execute();
public:
__fastcall TAnimatedTrayIconThread( bool, int, const AnsiString&, int, HIMAGELIST, HWND, unsigned int, int, int, const AnsiString&, TThreadPriority, int );
};
//---------------------------------------------------------------------------
#endif


//---------------------------------------------------------------------------
#pragma hdrstop
//---------------------------------------------------------------------------
#include "TrayThread.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
__fastcall TAnimatedTrayIconThread::TAnimatedTrayIconThread( bool IsInstalled,
int CurrentIndex,
const AnsiString &SHint,
int ID,
HIMAGELIST hList,
HWND hwnd,
unsigned int TRAY_MESSAGE,
int FIndex,
int TIndex,
const AnsiString &AHint,
TThreadPriority APriority,
int Count )
: TThread( true )
{
Installed = IsInstalled;
StaticIndex = CurrentIndex;
StaticHint = SHint;
TrayID = ID;
hImageList = hList;
hWnd = hwnd;
CM_TRAYNOTIFY = TRAY_MESSAGE;
FromIndex = FIndex;
ToIndex = TIndex;
AnimateHint = AHint;
Priority = APriority;
Delay = Count;
Resume();
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIconThread::Execute()
{
NOTIFYICONDATA IconData = { 0 };
IconData.cbSize = sizeof( NOTIFYICONDATA );
IconData.hWnd = hWnd;
IconData.uID = TrayID;
IconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
IconData.uCallbackMessage = CM_TRAYNOTIFY;
IconData.hIcon = NULL;
StrLCopy( IconData.szTip, AnimateHint.c_str(), sizeof(IconData.szTip) - 1 );

if( !Installed )
{
IconData.hIcon = ::ImageList_GetIcon( hImageList, FromIndex, ILD_TRANSPARENT );
::Shell_NotifyIcon( NIM_ADD, &IconData );
}

while( !Terminated )
{
for( int x = FromIndex; x <= ToIndex && !Terminated; ++x )
{
::DestroyIcon( IconData.hIcon );
IconData.hIcon = ::ImageList_GetIcon( hImageList, x, ILD_TRANSPARENT );
::Shell_NotifyIcon( NIM_MODIFY, &IconData );
::Sleep( Delay );
}
}


if( !Installed ) ::Shell_NotifyIcon( NIM_DELETE, &IconData );
else
{
::DestroyIcon( IconData.hIcon );
IconData.hIcon = ::ImageList_GetIcon( hImageList, StaticIndex, ILD_TRANSPARENT );
StrLCopy( IconData.szTip, StaticHint.c_str(), sizeof(IconData.szTip) - 1 );
::Shell_NotifyIcon( NIM_MODIFY, &IconData );
}
::DestroyIcon( IconData.hIcon );
}
//---------------------------------------------------------------------------


Usage:

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Main.h"
#include "Unit2.h"
#include "Unit3.h"
#include "AnimatedTrayIcon.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

HICON hIcon = NULL;
TAnimatedTrayIcon *pTray = NULL;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
hIcon = ::ImageList_GetIcon( (HIMAGELIST)ImageList1->Handle, 8, ILD_TRANSPARENT );

pTray = new TAnimatedTrayIcon( this );
pTray->PopupMenu = PopupMenu1;
pTray->ImageList = ImageList1;
pTray->StaticIndex = 8;
pTray->TrayID = 1643;
pTray->AnimateIndexStart = 0;
pTray->AnimateIndexEnd = 8;
pTray->AnimationDelay = 100;
pTray->AnimationPriority = tpNormal;
pTray->StaticHint = "This is static hint";
pTray->AnimationHint = "This is animated hint";
pTray->AnimateWaveSound = "ANIMATE";
pTray->LoopAnimateSound = true;
pTray->AnimateWindow = true;
pTray->MinimizeToTray = true;
pTray->OnClick = TrayClick;
pTray->OnDblClick = TrayDoubleClick;
pTray->BalloonCaption = "Test Balloon Caption";
pTray->BalloonText = "This is line 1\nLine 2\nLine 3";
pTray->BalloonIconType = itUser;
pTray->BalloonIcon = (UINT) hIcon;
pTray->BalloonTimeout = 15000;
pTray->OnBalloonClick = BalloonClick;
pTray->OnBalloonShow = BalloonShow;
pTray->OnBalloonHide = BalloonHide;
pTray->OnBalloonClose = BalloonClose;
pTray->OnBalloonTimeOut = BalloonTimeOut;
pTray->Active = true;
}
//---------------------------------------------------------------------------
__fastcall TForm1::~TForm1()
{
if( hIcon ) ::DestroyIcon( hIcon );
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ToolButton1Click(TObject *Sender)
{
// Demonstrates balloon tip
pTray->DoBalloonTip();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ToolButton2Click(TObject *Sender)
{
// Demonstrates OnBalloonHide if balloon tip is showing
if( pTray->Active ) pTray->Active = false;
else pTray->Active = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ToolButton3Click(TObject *Sender)
{
// Demonstrates z-order retention
if( !pTray->Active ) pTray->Active = true;
Form2->Show();
Form3->ShowModal();
::Sleep( 500 ); // confirm form2 is above main form
Form2->Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::TrayClick(TObject *Sender)
{
if( IsIconic(Application->Handle) ) pTray->Restore();
else pTray->Minimize();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::TrayDoubleClick(TObject *Sender)
{
pTray->Restore();
ShowMessage("Tray Double Clicked");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BalloonClick(TObject *Sender)
{
ShowMessage("Balloon Clicked");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BalloonShow(TObject *Sender)
{
ShowMessage("Balloon OnShow");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BalloonHide(TObject *Sender)
{
ShowMessage("Icon deleted while balloon was active.");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BalloonClose(TObject *Sender)
{
ShowMessage("Balloon Closed by user");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BalloonTimeOut(TObject *Sender)
{
ShowMessage("Balloon Timed out");
}
//---------------------------------------------------------------------------

~ End

Vladimir Stefanovic

unread,
Mar 22, 2006, 3:55:13 PM3/22/06
to
Hi JD,

Thank you very much for the effort and for sharing
the latest code with us:)

Now, we can track changes and possibly participate in
improving it, if there is room yet.

I'll do my best to find some bugs :)


--
Best Regards,
Vladimir Stefanovic


> Vladimir:
>
> I never expected so many people to use TAnimatedTrayIcon
> so I never even considered version numbers but with the new
> TTrayIcon in BDS, while better than the old, it's still weak so
> I'd expect that the more people that are aware of it, the more
> people that will use it. Since you last saw it, I've made
> several changes to the component and even a couple of custom
> jobs for individuals so I've labeled it's version as 1.5.0.

> [...]

Chris

unread,
Mar 22, 2006, 4:51:23 PM3/22/06
to
Brilliant.

Chris

PS. How would I set my app to start minimized to the tray?


JD

unread,
Mar 23, 2006, 3:54:40 AM3/23/06
to

"Chris" <nos...@nospam.com> wrote:
>
> [...] How would I set my app to start minimized to the tray?

Wise Guy!! I don't know why I never thought of that option and
I wish I had because the design as it is will make it difficult
to implement.

Ok, here's the deal ... a propper solution requires more work
than I have time to spend right now but I do have a temporary
solution for you. The easiest thing is to just post a message
in the component's constructor. For example:

if( !ComponentState.Contains(csDesigning) )
{
....
// as the last line
::PostMessage( Application->Handle, WM_SYSCOMMAND, SC_MINIMIZE, NULL );
}

That will get the form minimized but it still gets shown prior
to minimizing. If you want to prevent that (seeing the form
for a moment), you need to add a line of code to the WinMain:

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->ShowMainForm = false; // <-- add this here
....
}
....
}

If you do change the WinMain, then you need to also add an
additional line of code to counter act it in the component's
Restore method:

bool __fastcall TAnimatedTrayIcon::Restore()
{
bool Result = false;
if( FUseTray && FActive && FMinimized )
{

....


::ShowWindow( Application->Handle, SW_SHOW );

Application->MainForm->Visible = true; // <- add this here
....
}
....
}

For an SDI application, that's all that's needed. For an MDI
application, the application's Icon show's up on the TaskBar
for an instant and then disappears. To prevent this, one needs
to override the main form's CreateParams method so that the
WS_EX_TOOLWINDOW flag can be applied to it's ExStyle. For
example:

protected:
virtual void __fastcall CreateParams( TCreateParams &Params );

void __fastcall TForm1::CreateParams( TCreateParams &Params )
{
TForm::CreateParams( Params );
Params.ExStyle |= WS_EX_TOOLWINDOW;
}

However, if you do that, the icon will never appear on the
TaskBar even when the application is restored unless you call
SetWindowLong from somewhere - most likely some sort of timer.
For example:

::SetWindowLong( Application->Handle, GWL_EXSTYLE, ::GetWindowLong(Application->Handle, GWL_EXSTYLE) & ~WS_EX_TOOLWINDOW );

~ JD

JD

unread,
Mar 23, 2006, 4:37:22 PM3/23/06
to

"JD" <nos...@nospam.com> wrote:
>
> [...] the design as it is will make it difficult to
> implement.

Hello Chris:

Well ,,, it was easier than I expected so here's the fix for
minimizing to the tray at start-up and it works for SDI and
MDI applications but only if you dynamically allocate the
object (note FBeginMinimized set in the constructor). If the
component is installed into the IDE, the Loaded method would
have to be overriden so it could know what to do after the
property values have been streamed in. I'm thinking that to
solve the conflict with dynamically allocated and streamed
that an overloaded constructor would take care of it but
that's not going to happen any time soon.

Good Luck.

~ JD

private:
#define UWM_MDISTARTMINIMIZED (WM_USER + 100 )
bool FBeginMinimized;
unsigned int FExStyle;
void __fastcall BeginMinimized();

//---------------------------------------------------------------------------
__fastcall TAnimatedTrayIcon::TAnimatedTrayIcon( TComponent *Owner ) : TComponent(Owner)
{

....
FBeginMinimized = true;
if( !ComponentState.Contains(csDesigning) )
{
....


::PostMessage( FHandle, CM_TRAYCREATED, 0, 0 );

if( FBeginMinimized ) BeginMinimized(); // <-- add this here
}
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::BeginMinimized()
{
BoundsRect = FParentForm->BoundsRect;
if( FParentForm->FormStyle != fsMDIForm )
{
::SendMessage( Application->Handle, WM_SYSCOMMAND, SC_MINIMIZE, NULL );
vForms.clear();
vZOrder.clear();
vForms.push_back( FParentForm );
vZOrder.push_back( FParentForm->Handle );
}
else
{
FExStyle = ::GetWindowLong( Application->Handle, GWL_EXSTYLE );
if( FExStyle & WS_EX_TOOLWINDOW )
{
::SetWindowLong( Application->Handle, GWL_EXSTYLE, FExStyle | WS_EX_TOOLWINDOW );
::PostMessage( FHandle, UWM_MDISTARTMINIMIZED, 0, 0 );
}
}
FParentForm->Visible = false;


Application->ShowMainForm = false;
::ShowWindow( Application->Handle, SW_HIDE );

if( FParentForm->FormStyle == fsMDIForm )
{
::SendMessage( Application->Handle, WM_SYSCOMMAND, SC_MINIMIZE, NULL );
}
FMinimized = true;
}
//---------------------------------------------------------------------------


bool __fastcall TAnimatedTrayIcon::AppHook( TMessage &Message )
{
if( Message.Msg == WM_SYSCOMMAND )
{
switch( Message.WParam )
{
case SC_RESTORE:
case SC_MAXIMIZE: return Restore();

case SC_MINIMIZE: if( FBeginMinimized )
{
FBeginMinimized = false;
}
else return Minimize();


}
}
return false;
}
//---------------------------------------------------------------------------
void __fastcall TAnimatedTrayIcon::WndProc( TMessage &Message )
{

....
else if( Message.Msg == UWM_MDISTARTMINIMIZED )
{
::SetWindowLong( Application->Handle, GWL_EXSTYLE, FExStyle & ~WS_EX_TOOLWINDOW );
}
....
}
//---------------------------------------------------------------------------

~ End

Chris

unread,
Mar 24, 2006, 3:11:59 AM3/24/06
to
Fantastic, works like a charm.

Thanks for all the hard work and for sharing this.

Chris

PS. May I respectfully suggest a slight modification:

Unless I missunderstood, there are a few occasions where the balloon tip
might not be supported, if this is correct could you add some way to check
whether it will work or not i,e

bool __fastcall BallonTipSupported()

or perhaps change

void __fastcall DoBalloonTip() to bool __fastcall DoBalloonTip()

which would allow code like this:

if (!pTray->DoBalloonTip())
ShowMessage(pTray->BalloonText);

Thanks again.


JD

unread,
Mar 24, 2006, 2:42:14 AM3/24/06
to

"Chris" <nos...@nospam.com> wrote:
>
> [...] May I respectfully suggest a slight modification:

You could even do it in a disrespectful way! If it has merit,
it will be considered.

> Unless I missunderstood, there are a few occasions where the
> balloon tip might not be supported, if this is correct

That is correct but I'm unclear as to how early it was first
supported which depends on the Shell32.dll. My information is
it has to be v5.0 but I saw something that I haven't tracked
down that hinted that v4.0 will also support ballooning.

> could you add

There's nothing that's stopping you from making any changes
that you desire. I would ask only that you send Vladimir or
myself information on the changes that you (or anyone else
for that matter) have implemented.

> some way to check whether it will work or not

That's reasonable and I think the best way to accomplish that
is to add a read-only property (which I have done). For example:

__published:
__property bool CanBalloon = { read = FCanBalloon };

Now your sample would become:

if( pTray->CanBalloon )
{
pTray->DoBalloonTip();
}
else
{
// programmers choice
}

~ JD

0 new messages