Put any application on systray knowing its HWND

Published on by Yousfi Benameur

    

this code can make vfp reside on the systray (traybar) and used to hide/show vfp application.
it uses ShowWindow API exclusively and systray class(to avoid some errors,this last must copied in source folder(vcx+vct)-the code does that).
it works with the handle of the window: here _vfp.hwnd
it also can be applied to any form (because having a handle:form.hwnd).
i present here a general solution to put any window on systray knowing only its handle HWND( can be an application,a form,or could be any window with its handle).
if dont know the window handle can use firstly the API hwnd=findWindow(null,"exact window caption)-hwnd of course must be not null.

[Post 266]


Click on code to select [then copy] -click outside to deselect


*1* created on friday 09 of march 2018
*this code can reside on the systray (traybar),to hide/show vfp application
*it uses ShowWindow API exclusively and systray class(this last must copied in source folder).(to avoid some errors,this last must copied in source folder(vcx+vct)-this code does that).
*it works with the handle of vfp window: here _vfp.hwnd
*its also can applied to any form (because having a handle:form.hwnd).
*i present here a general solution to put any window on systray knowing only its handle HWND( can be an application,a form,or any window with its handle).
*if dont know the window handle can run the app and use firstly the API hwnd=findWindow(null,"window caption)-hwnd must be not null.

set defa to addbs(justpath(sys(16,1)))
if ! file ("systray.vcx")
copy file home(1)+"samples\solution\toledo\systray.vcx" to systray.vcx
copy file home(1)+"samples\solution\toledo\systray.vct" to systray.vct
endi

Declare Integer ShowWindow In WIN32API Integer, Integer
#Define SW_SHOW  5
#Define SW_HIDE  0
#Define SW_MINIMIZE  6
#Define SW_RESTORE  9

Publi m.osystray
m.osystray = Createobject("mySystray")
m.osystray.ShowBalloonTip('Hide/show the vfp',"MySystray",0,2)  &&shellversion>=5
Read Events

Define Class mySystray As Systray Of  "SYSTRAY.VCX"     &&systray.vcx class must be in the source folder mandatory (set path...dont work !)
  IconFile = Home(1) + "Graphics\Icons\Misc\Face03.ico"  
  TipText = "hide/show vfp"
  MenuText = "1;1. Hide vfp ; 2;2. Show VFP ;3;3. Systray help; 4;Change icon;5; Exit"
  MenuTextIsMPR = .F. &&if .t. can use a complex mpr menu(menu designer generation ..)
  *This is the simplest method for using menus with the Systray class. Define a menu using numeric tokens and text, and place that in the MenuText property.
  *After the menu has been cleared by the user, either by selecting an item or clicking outside the menu, Systray calls the ProcessMenuEvent() method,
  *passing it the numeric token for the menu item that the user selected PROCEDURE ProcessMenuEvent

  Procedure ProcessMenuEvent
    Lparameters nMenuItemID
    Do Case
      Case nMenuItemID = 0
        * User cleared the menu. Do nothing.
      Case nMenuItemID = 1
        * Réduit et cache le bureau FoxPro
        ShowWindow(_vfp.HWnd, SW_MINIMIZE)
        ShowWindow(_vfp.HWnd, SW_HIDE)

      Case nMenuItemID = 2
        ShowWindow(_vfp.HWnd, SW_SHOW)
        ShowWindow(_vfp.HWnd, SW_RESTORE)
      Case nMenuItemID = 3
        Local m.o
        m.o=Newobject("hyperlink")
        m.o.NavigateTo(Home(1)+"samples\solution\toledo\systray_readme.htm")
        m.o=Null
       
      Case nMenuItemID = 4
			local m.xicon
			m.xicon=getpict('ico')
			if empty(m.xicon) or !lower(justext(m.xicon))=="ico"
			return .f.
			endi
			this.iconfile=m.xicon

      Case nMenuItemID = 5
        * Exit Application
        This.RemoveIconFromSystray()
        DoEvent   &&to clear icon without passing mouse over
        m.osystray=null
        release m.osystray
        Clear Events
    Endcase
  Endproc

Enddefine


Click on code to select [then copy] -click outside to deselect

 *2*
	ShowWindow API constants (https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx)

	#define SW_FORCEMINIMIZE 11  &&Minimizes a window, even if the thread that owns the window is not responding. This flag should only be used when minimizing windows from a different thread.
	#define SW_HIDE 0       &&Hides the window and activates another window.
	#define SW_MAXIMIZE 3   &&Maximizes the specified window.
	#define SW_MINIMIZE 6   &&Minimizes the specified window and activates the next top-level window in the Z order.
	#define SW_RESTORE  9   &&Activates and displays the window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when restoring a minimized window.
	#define SW_SHOW    5   &&Activates the window and displays it in its current size and position.
	#define SW_SHOWDEFAULT 10  &&Sets the show state based on the SW_ value specified in the STARTUPINFO structure passed to the CreateProcess function by the program that started the application.
	#define SW_SHOWMAXIMIZED 3   &&Activates the window and displays it as a maximized window.
	#define SW_SHOWMINIMIZED 2  &&Activates the window and displays it as a minimized window.
	#define SW_SHOWMINNOACTIVE 7  &&Displays the window as a minimized window. This value is similar to SW_SHOWMINIMIZED, except the window is not activated.
	#define SW_SHOWNA 8 &&Displays the window in its current size and position. This value is similar to SW_SHOW, except that the window is not activated.
	#define SW_SHOWNOACTIVATE 4  &&Displays a window in its most recent size and position. This value is similar to SW_SHOWNORMAL, except that the window is not activated.
	#define SW_SHOWNORMAL 1  &&Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time.


! /N  or Run /N  vfp command uses the same constants
A Windows-based application executed with RUN /N or ! /N behaves the same way the application does when you open it through the Windows Explorer or by selecting Run from the Start menu. You can switch between the application and Visual FoxPro or FoxPro for Windows using the standard Windows operations.
You can include an optional numeric value immediately after /N to specify how the Windows-based application is opened. Do not include any spaces between /N and the numeric value. The following table lists the numeric value you can include and describes the state of the Windows-based application when opened.
Value  Application attributes  
1  Active and normal size.
2  Active and minimized.
3  Active and maximized.
4  Inactive and normal size. 
7  Inactive and minimized.
 
API Shellexecute uses also same constants to work with any app window. 


Click on code to select [then copy] -click outside to deselect


*3* created on saturday 10 of march 2018 -adaptation of the original vfp6 code as commented below.
*! * original code TRAY.PRG (VFP6) 20/04/2000
*! * Description - this code hide/show vfp in systray  dynamically .
*! * Auteur - Zakirov Orion.
*! * Paramètres - lcIconNameFile: icon filename, added gandle HWND of window
*! * Syntaxe - TRAY (hwnd,<filename.ico>)
*! * function long2str is from Microsoft knowledge base.
*! *  originally code uses librairy foxtools.fll...now this is not recquired with the code below.
*!* note: this makes only an icon on traybar (no contextuel menu)
*!* can call the function from anywhere with this syntax(suppose saved as ytray.prg):
*!* do ytray with _vfp.hwnd,some_icon_filename.

Lparameters HWnd,lcIconNameFile
If Pcount()<2 Or !Vartype(HWnd)="N" Or  !( Lower(Justext(lcIconNameFile))=="ico")
  Messagebox("Incorrect parameters...cancelling.",16+4096,'',1200)
  Return .F.
Endi

If Vartype(lcIconNameFile)="C" And !Empty(lcIconNameFile)
  If Atc(".ico",lcIconNameFile)=0
    lcIconNameFile = lcIconNameFile + ".ico"
  Endif
  _Screen.Icon = lcIconNameFile
Else
  If Empty(_Screen.Icon)
    Messagebox ("Le fichier d'icône n'est pas défini.", 48, "Erreur")
    Return .F.
  Else
    lcIconNameFile = _Screen.Icon
  Endif
Endif
If !File(_Screen.Icon)
  Messagebox ("Fichier d'icône introuvable", 48, "Erreur")
  Return .F.
Endif

If !File(lcIconNameFile)
  Messagebox ("Fichier d'icônes non trouvé" + lcIconNameFile + ".", 48, "Erreur")
  Return .F.
Endif


* declare WinAPI
Declare Integer ShowWindow In WIN32API Integer, Integer
Declare Integer GetAsyncKeyState In "user32" Long
Declare Integer SetForegroundWindow In "USER32" Integer
Declare Integer Shell_NotifyIcon In "SHELL32" Long, String
Declare Long ExtractIconEx In "shell32.dll" String lpszFile, Long nIconIndex, Long @phiconLarge, Long @phiconSmall, Long nIcons
Declare Long DestroyIcon In "user32" Long hIcon

* initit constants
Local VK_LBUTTON, VK_RBUTTON, NIM_ADD, NIM_MODIFY, NIM_DELETE, NIF_MESSAGE, NIF_ICON, NIF_TIP, ;
  SW_HIDE, SW_SHOW, SW_MINIMIZE, SW_RESTORE

SW_HIDE = 0
SW_SHOW = 5
SW_MINIMIZE = 6
SW_RESTORE = 9

VK_LBUTTON = 1
VK_RBUTTON = 2

NIM_ADD = 0
NIM_MODIFY = 1
NIM_DELETE = 2
NIF_MESSAGE = 1
NIF_ICON = 2
NIF_TIP = 4

* init variables
Local HWnd, phiconLarge, phiconSmall, lcTip, lcStartNotifyString, lcNotifyString, lnKey

* tip text
lcTip = _Screen.Caption +" Click to restore original  window."   && 64 chars max

* Variables  to store icon descriptors
phiconLarge=0
phiconSmall=0
* return the icon descriptors
ExtractIconEx(lcIconNameFile,0,@phiconLarge,@phiconSmall,1)

* build a string of parameterss (as NOTIFYICONDATA structure)
lcStartNotifyString = long2str(HWnd) + long2str(0) + long2str(Bitor(Bitor(NIF_ICON,NIF_TIP),NIF_MESSAGE))+ long2str(0x201)
lcNotifyString = lcStartNotifyString + long2str(phiconSmall)+ Alltrim(Padr(Left(lcTip,64),64," "))
lcNotifyString = Padr(long2str(Len(lcNotifyString)+4),4," ") + lcNotifyString

Shell_NotifyIcon(NIM_ADD, lcNotifyString) && ajouter une icône à TrayBar

* reduce and hide vfp window
ShowWindow(HWnd, SW_MINIMIZE)
ShowWindow(HWnd, SW_HIDE)

Do While .T.  &&&& waits indefinitely for a keystroke.(M: check for a mouse click)
  Inkey(0,"M")
  lnKey = GetAsyncKeyState(VK_LBUTTON)
  If lnKey<>0
    Exit
  Endif
Enddo

* make vfp forward
SetForegroundWindow(HWnd)

*compose a string of parameters (as NOTIFYICONDATA structure)
lcNotifyString = lcStartNotifyString + long2str(0)
lcNotifyString = Padr(long2str(Len(lcNotifyString)+4),4," ") + lcNotifyString
Shell_NotifyIcon(NIM_DELETE, lcNotifyString) && supprimer l'icône de TrayBar
DestroyIcon(phiconLarge)
DestroyIcon(phiconSmall)

* reduce and hide vfp window
ShowWindow(HWnd, SW_SHOW)
ShowWindow(HWnd, SW_RESTORE)

Return

Function long2str
  * Passed : 32-bit non-negative numeric value (m.longval)
  * Returns : ASCII character representation of passed  value in low-high format (m.retstr)
  * Example :
  * m.long = 999999
  * m.longstr = long2str(m.long)
  Parameters m.longval

  Private i, m.retstr
  m.retstr = ""
  For i = 24 To 0 Step -8
    m.retstr = Chr(Int(m.longval/(2^i))) + m.retstr
    m.longval = Mod(m.longval, (2^i))
  Next
  Return m.retstr


Click on code to select [then copy] -click outside to deselect

*4* created on saturday 10 of march 2018
*can insert in main vfp menu this PAD to hide/show vfp in systray (traybar).
*run the code ,vfp is in systray(with a bullet when mousemove)
*clicking on systray icon vfp is restored.
*this is also available with any window( handle).
*correct paths in code because define bar  dont accept variable as home(),... in its command line (paths hardcoded only !)...

set sysmenu to defa
DEFINE PAD padReports OF _MSYSMENU   PROMPT "\<mysystray"   MESSAGE "Show/hide vfp window in traybar"
DEFINE POPUP popReports MARGIN
ON PAD padReports OF _MSYSMENU ACTIVATE POPUP popReports

DEFINE BAR 1 OF popReports   PROMPT "1. Hide/show vfp in systray"    MESSAGE "SHow/hide vfp window in traybar" pictres _mtl_wzrds
on selection bar 1 of popReports  do  "D:\________YTEST2017\___YCURRENT\YTRAYBAR\TRAYBAR_ICON-RU_FORM.PRG"  with _vfp.hwnd,"C:\Program Files (x86)\Microsoft Visual FoxPro 9\graphics\icons\misc\face03.ico"
*can be also on a vfp top level form menu
retu


Click on code to select [then copy] -click outside to deselect


*5* created on saturday 10 of march 2018
*this is the systray class translated as flat prg code for use.
*can be used directly as com object on a prg with createObject,NewObject...
*-- Class Library:    systray.vcx
*
DEFINE CLASS systray AS hyperlink
	Height = 23
	Width = 23
	*-- Text to be displayed in standard tooltip above the Taskbar Notification Area ("systray").
	tiptext = ""
	*-- Defines a menu to be displayed when user clicks on icon.  Specify a numeric ID and text for each item, separating each by a semicolon.  (e.g.  1;Activate; 2; Exit).  The ID is passed to the ProcessMenuEvent method after the user has made a menu selecti
	menutext = ""
	*-- Used internally to store a unique id for our icon.  This way, multiple instances of this class can be used without interfering with each other.
	HIDDEN iconidentifier
	iconidentifier = 0
	HIDDEN iconcount
	iconcount = 0
	*-- Contains Unique ID for the systray token that this object creates.
	systrayiconid = 0
	*-- Determines which of the currently loaded icons is displayed in the Taskbar Notification Area ("systray").
	currenticonindex = 0
	*-- If only one icon is used, it can be specified here.  If multiple icons are to be used (animation), use the AddIconToIconList method.
	iconfile = ""
	*-- Essentially the width of your menu in pixels.  This is an offset used when the TaskBar is docked on the right, to prevent the menu from being drawn underneath the taskbar.
	menuoffsetfromright = 200
	*-- If .T., the icon will be added to the Taskbar Notification Area ("systray") when this class is instantiated.  If .F., you must call the AddIconToSystray() method.
	addicontosystrayatinit = .T.
	*-- Contains the numeric version of the Shell communication method that we've established.
	shellversion = 0
	*-- Previous setting of SYS(2060).
	HIDDEN nprevmwaccrual
	nprevmwaccrual = 0
	*-- XML Metadata for customizable properties
	_memberdata = ""
	Name = "systray"

	*-- Set to true if the MenuText property contains the name of a MPR to run. The MPR must define a shortcut menu.  To pass parameters to the MPR, call ShowMenu method from the IconClick event.
	menutextismpr = .F.

	*-- Is .T. when we have an icon in the Taskbar Notification Area ("systray").
	HIDDEN enabled

	*-- Array containnig Icons and handles.
	HIDDEN aiconlist[1]
	HIDDEN goback
	HIDDEN goforward
	HIDDEN navigateto


	*-- Receives the events the Notify Icon.
	PROCEDURE receiveiconevent
		LPARAMETERS nDirection, nShift, nXCoord, nYCoord
			* This is the procedure that receives the Notify Icon events.  Since
			* we execute the BindEvent() function from within this class, this
			* procedure can be hidden.
			* nDirection contains our icon identifier.

		IF nDirection != this.SystrayIconID
			* Not our icon, or this a real MouseWheel event on the VFP window.
			RETURN .F.
		ENDIF

		*  These are the events that we receive from the Taskbar icon.
		#DEFINE WM_MOUSEMOVE		0x0200
		#DEFINE WM_LBUTTONDOWN		0x0201
		#DEFINE WM_LBUTTONUP		0x0202
		#DEFINE WM_LBUTTONDBLCLK	0x0203
		#DEFINE WM_RBUTTONDOWN		0x0204
		#DEFINE WM_RBUTTONUP		0x0205
		#DEFINE WM_RBUTTONDBLCLK	0x0206
		#DEFINE WM_MBUTTONDOWN		0x0207
		#DEFINE WM_MBUTTONUP		0x0208
		#DEFINE WM_MBUTTONDBLCLK	0x0209
		*  Mousewheel events also get passed, but are difficult to decipher.
		#define WM_CONTEXTMENU      0x007B	&& Same as RightClick, but used when
											&& Version 5 events have been specified.
											&& (See the DisplayBalloonTip method.)

		#define WM_USER             0x0400
		#define NIN_SELECT          WM_USER + 0
		#define NINF_KEY            0x1
		#define NIN_KEYSELECT       BITOR(NIN_SELECT , NINF_KEY)

		* Balloon events supported on Windows ME and Windows XP, and later. Not supported on Win2k.
		#DEFINE NIN_BALLOONSHOW     (WM_USER + 2)
		#DEFINE NIN_BALLOONHIDE     (WM_USER + 3)
		#DEFINE NIN_BALLOONTIMEOUT  (WM_USER + 4)
		#DEFINE NIN_BALLOONUSERCLICK (WM_USER + 5)

		* Even though this isn't a real MouseWheel event on the VFP window,
		* VFP gives us the coordinates with respect to the location of the
		* main VFP window.  Must change back to global coordinates.
		nXCoord = nXCoord + _screen.Left

		IF nXCoord <> WM_MOUSEMOVE	&& Ignore mousemove events.
			* The Version 5 events are more difficult to deal with,
			* but Version 5 supports Balloon Tips.  So the extra work
			* is worth it.
			IF THIS.ShellVersion >= 5
				DO CASE
				CASE nXCoord = NIN_SELECT OR nXCoord = NIN_KEYSELECT
					THIS.IconClickEvent
				CASE nXCoord = WM_LBUTTONDBLCLK ;
						OR nXCoord = WM_RBUTTONDBLCLK ;
						OR nXCoord = WM_MBUTTONDBLCLK
					* We'll just use one DoubleClick event, and ignore what button was used.
					THIS.IconDblClickEvent
					RETURN
				CASE nXCoord = WM_CONTEXTMENU  && Shell version 5 and later
					THIS.IconRightClickEvent
					RETURN
				CASE nXCoord = WM_MBUTTONDOWN
					THIS.IconMiddleClickEvent
					RETURN
				CASE nXCoord = NIN_BALLOONSHOW
					THIS.BalloonShowEvent
					RETURN
				CASE nXCoord = NIN_BALLOONHIDE
					THIS.BalloonHideEvent
					RETURN
				CASE nXCoord = NIN_BALLOONTIMEOUT
					THIS.BalloonTimeoutEvent
					RETURN
				CASE nXCoord = NIN_BALLOONUSERCLICK
					This.BalloonClickEvent
					RETURN
				OTHERWISE
					*  Unknown event.  Just ignore it.
					RETURN
				ENDCASE
			ELSE
				*  Using Version 4 events.
				DO CASE
				CASE nXCoord = WM_LBUTTONDOWN
					THIS.IconClickEvent
				CASE nXCoord = WM_LBUTTONDBLCLK ;
						OR nXCoord = WM_RBUTTONDBLCLK ;
						OR nXCoord = WM_MBUTTONDBLCLK
					* We'll just use one DoubleClick event, and ignore what button was used.
					THIS.IconDblClickEvent
					RETURN
				CASE nXCoord = WM_RBUTTONDOWN
					THIS.IconRightClickEvent
					RETURN
				CASE nXCoord = WM_MBUTTONDOWN
					THIS.IconMiddleClickEvent
					RETURN
				OTHERWISE
					*  Unknown event.  Just ignore it.
					RETURN
				ENDCASE
			ENDIF

		ENDIF
	ENDPROC

	*-- By default, brings the form that contains this object to the foreground.  You can also  specify a different window to bring forward by passing a numeric hWnd parameter.
	PROCEDURE setforegroundwindow
		LPARAMETERS lhWnd

		* Brings the specified top-level window to the top of the Windows z-order.
		* If no parameter specified, then THISFORM is made the foreground window.

		IF TYPE("m.lhWnd") = "N"
			DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd
			WinAPI_SetForegroundWindow(m.lhWnd)
			RETURN
		ENDIF

		RETURN

		*  If we're here, then the user called this method without an
		*  hWnd parameter.  Assume it is thisform that is to be brought forward.
		IF VARTYPE(thisform) == "O"
			IF ! thisform.Visible
				thisform.Visible = .t.
			ENDIF

			*  First make sure it isn't minimized.
			IF thisform.WindowState= 1
				thisform.WindowState = 0
			ENDIF

			*  If necessary, make sure the main VFP window is not minimized.
			IF _screen.Visible AND thisform.ShowWindow = 0
				IF _screen.WindowState = 1
					_screen.WindowState = 0	&& Set back to normal
				ENDIF
			ENDIF

			DECLARE INTEGER SetForegroundWindow IN user32.dll AS WinAPI_SetForegroundWindow INTEGER hWnd
			WinAPI_SetForegroundWindow(thisform.HWnd)
		ENDIF
	ENDPROC


	*-- Clear all icons from the current list.  If an icon is currently displayed in the taskbar notification area (systray), it is removed.
	PROCEDURE cleariconlist
		*  Removes all icons from the icon list.

		*  In detail:
		*  Releases the resource handles of all icons that have been loaded,
		*  clears the array and sets the count to zero.  This method is called
		*  by the Destroy event, to make sure all system resources are correctly
		*  released.

		WITH this
			IF .IconCount = 0
				* No icons in list
				RETURN
			ENDIF

			*  Have to release all icon resources
			DECLARE INTEGER DestroyIcon IN user32.dll AS WinAPI_DestroyIcon ;
				INTEGER hIcon

			FOR nCurrentIcon = 1 TO ALEN(.aIconList,1)
				WinAPI_DestroyIcon(.aIconList[m.nCurrentIcon])
			ENDFOR

			DIMENSION .aIconList[1]
			.IconCount = 0
			.CurrentIconIndex = 0
		ENDWITH
	ENDPROC

	*-- Adds an icon file to list of icons.  Accepts a string parameter containing a filename.  Call the SwitchIcon method to switch to the next icon.
	PROCEDURE addicontoiconlist
		LPARAMETERS cIconFileName

		IF !FILE(cIconFileName)
			RETURN .f.
		ENDIF

		* We must load the icon as a Windows resource.
		nHandle = THIS.LoadIcon(m.cIconFileName)
		IF m.nHandle = 0
			RETURN .f.
		ENDIF

		* We have a resource handle for the icon. Now we store that
		* handle (integer) in our array of icon resource handles.
		WITH THIS
			.IconCount = .IconCount + 1
			DIMENSION .aIconList[.IconCount]
			.aIconList[.IconCount] = m.nHandle
			IF .CurrentIconIndex = 0
				.CurrentIconIndex = 1	&& Updates Systray immediately
			ENDIF
		ENDWITH

		RETURN .T.
	ENDPROC

	*-- Changes the displayed icon to the next icon in the icon list.  Use AddIconToIconList method to populate the icon list, and ClearIconList to start over.
	PROCEDURE switchicon
		* Switches to the next icon in the iconlist.
		* Typically called by a timer to regularly change the icon,
		* to create animation.

		WITH THIS
		IF .IconCount > 1
			IF .CurrentIconIndex = .iconCount
				.CurrentIconIndex = 1
			ELSE
				.CurrentIconIndex = .CurrentIconIndex + 1
			ENDIF
		ENDIF
		RETURN .CurrentIconIndex
		ENDWITH
	ENDPROC

	*-- Adds the icon to the Taskbar Notification Area ("systray").
	PROCEDURE addicontosystray
		*!*	This is where the magic happens.
		*!*	This is an unabashed hack to allow us to get the full functionality
		*!*	of the Taskbar Notification Area ("System Tray") without having to
		*!*	use an external C++ library.
		*!*	To communicate with the systray, we tell it to send us messages via
		*!*	the MouseWheel event. (This is the only VFP event that doesn't alter
		*!*	or discard event data before firing the corresponding internal event.)
		*!*	The second trick is that only the main VFP window will accept the events
		*!*	without checking to see if the event coordinates are invalid. (The screen
		*!* doesn't even need to be visible, so you can have SCREEN=OFF in your
		*!* config.fpw file.)  So we use VFP8's BINDEVENT() function to bind to the
		*!* _SCREEN.MouseWheel event.
		*!*	This method sets up that communication path.
		*!*	Returns 1 if successful.

		LOCAL lcNotifyIconData

		** NOTIFYICON struct defines
		#define NIF_MESSAGE     0x00000001
		#define NIF_ICON        0x00000002
		#define NIF_TIP         0x00000004
		#define NIF_STATE       0x00000008  && Win2k and later.
		#define NIF_INFO        0x00000010	&& Use balloon tip.  Win2k and later.


		&& Notify Icon Infotip flags
		#define NIIF_NONE       0x00000000
		&& icon flags are mutually exclusive
		&& and take only the lowest 2 bits
		#define NIIF_INFO       0x00000001
		#define NIIF_WARNING    0x00000002
		#define NIIF_ERROR      0x00000003
		#define NIIF_ICON_MASK  0x0000000F
		#define NIIF_NOSOUND    0x00000010	&& Windows XP and later.

		#define NIM_ADD         0x00000000
		#define NIM_MODIFY      0x00000001
		#define NIM_DELETE      0x00000002
		#define NIM_SETFOCUS    0x00000003	&& Windows 2000 and later.
		#define NIM_SETVERSION  0x00000004	&& Windows 2000 and later.

		#define NOTIFYICON_VERSION 3
		#define NIS_HIDDEN              0x00000001
		#define NIS_SHAREDICON          0x00000002

		WITH THIS

		*  If no icons loaded, do nothing.
		IF .IconCount < 1 OR .CurrentIconIndex = 0
			RETURN 0
		ENDIF

		IF NOT .Enabled
			*  Each SystemTray icon in this process requires a unique ID. We'll
			*  use an _screen property to make sure each instance of this class
			*  gets its own ID.  Properties on _screen aren't affected by CLEAR ALL.
			*  Because VFP drops the least significant two bytes of the mousewheel event's
			*  WPARAM parameter, our unique SysTray Icon ID must use the most significant
			*  two bytes:
			#DEFINE MIN_SYSTRAY_ICON_ID	0x4000
			IF TYPE("_screen.nSysTrayCount") == "U"
				*  We're the first one here.
				_screen.AddProperty("nSysTrayCount", 0)
			ELSE
				IF _Screen.nSysTrayCount > 0x3FFF
					* User is apparently creating and destroying this object repeatedly.
					_Screen.nSysTrayCount = 0
				ENDIF
			ENDIF
			_screen.nSysTrayCount = _screen.nSysTrayCount + 1
			this.SystrayIconID = _screen.nSysTrayCount + MIN_SYSTRAY_ICON_ID


			*  By default, VFP combines MouseWheel events. (That is, if we receive
			*  a MouseWheel event from the OS, we check our internal queue to see
			*  if there is already a MouseWheel event that hasn't been processed yet.
			*  If found, we just add the number of MouseWheel turns to the existing
			*  event.)  This behavior makes our communication with the SystemTray
			*  unstable, so we must disable this combining of events.  SYS(2060)
			*  accomplishes this:
			THIS.nPrevMWAccrual = VAL(SYS(2060))
			SYS(2060, 1)
		ENDIF


		* Declare the WinAPI function that lets us install the icon.
		* We redeclare the function every time, just in case CLEAR DLLS is called
		* elsewhere in the app.
		DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ;
			INTEGER dwMessage, ;
			STRING @ PNOTIFYICONDATA

		IF THIS.ShellVersion = 0	&& If we haven't set the version yet.
			nTipTextMaxLength = 63	&& Default to version 4.
		ELSE
			nTipTextMaxLength = 127
		ENDIF

		*  Build NOTIFYICONDATA structure.
		lcNotifyIconData = .IntegerToString(_VFP.Hwnd)	&& Messages get sent to VFP's main window.
		lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000)
		lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON))	&&NIF_TIP,NIF_INFO,NIF_STATE
		lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A)	&& uCallback
		lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex])	&& icon handle
		lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), nTipTextMaxLength ), nTipTextMaxLength  + 1, CHR(0))	&& TipText
		IF .ShellVersion >= 5
			lcNotifyIconData = lcNotifyIconData + .IntegerToString(0)	&& dwState
			lcNotifyIconData = lcNotifyIconData + .IntegerToString(0)	&& dwStateMask
			lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256)	&& balloon tip.
			lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version
			lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64)	&& balloon tip title.
			lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags
		ENDIF

		lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData	&& length of structure

		IF NOT .Enabled		&& If not already in Taskbar Notification Area....

			*  Add icon to Taskbar Notification Area.
			nReturn = WinAPI_Shell_NotifyIcon( NIM_ADD, @lcNotifyIconData)
			IF nReturn <> 1
				*  Adding item to tray failed!!!
				IF _Screen.nSysTrayCount = 1
					* There has not been two simultaneous instances of this object
					* in the current session.  We can clean up after ourselves.
					_Screen.nSysTrayCount = 0
					SYS(2060, .nPrevMWAccrual)	&& Reset mousewheel behavior.
				ENDIF
				RETURN nReturn
			ENDIF
			*  Bind to the MouseWheel event on VFP's main window.
			nReturn = BINDEVENT(_screen,"MouseWheel",this,"receiveIconEvent",2)
			.Enabled = .t.

			* We've just added the icon.  Now we see if we can set to Version 5 shell behavior,
			* with larger tooltip lengths and support for balloon Tips.
			IF  .GetShellVersion() >= 5
				* This OS supports version 5 features.  Switch to version 5.

				* We have to inform the Shell that we want to use Version 5 features.
				* Build a version 5 structure for sending message to change version.
				lcNotifyIconData = .IntegerToString(_vfp.hwnd)
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000)
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON))	&&NIF_TIP,NIF_INFO,NIF_STATE
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A)	&& uCallback
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex])	&& icon handle
				lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0))	&& TipText
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(0)	&& dwState
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(0)	&& dwStateMask
				lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),256)	&& balloon tip.
				lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(NOTIFYICON_VERSION) && Timeout/Version
				lcNotifyIconData = lcNotifyIconData + REPLICATE(CHR(0),64)	&& balloon tip title.
				lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(0, 0x1F)) && Info flags
				lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData	&& length of structure

				*  First we check to see if we can switch to using version 5 events.
				nReturn = WinAPI_Shell_NotifyIcon( NIM_SETVERSION, @lcNotifyIconData)
				IF nReturn <> 1
					*  Couldn't switch to version 5 events
					RETURN -1
				ENDIF

				.ShellVersion = 5

				*  Now that we've updated to Version 5, we have to modify the existing icon
				*  if the .TipText is longer than 63 characters.
				IF LEN(.TipText) > 63
					nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData)
					IF nReturn <> 1
						RETURN nReturn
					ENDIF
				ENDIF
			ELSE
				.ShellVersion = 4
			ENDIF

		ELSE
			*  Already in the Taskbar Notification Area.  Just update existing icon.
			nReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData)
			IF nReturn <> 1
				MESSAGEBOX("		*  Modifying item in tray failed!!!")
				RETURN nReturn
			ENDIF
		ENDIF

		ENDWITH

		RETURN nReturn
	ENDPROC

	*-- Removes the icon from the Taskbar Notification Area ("systray").
	PROCEDURE removeiconfromsystray
		* Removes the icon from the Taskbar Notification Area ('System Tray').

		DECLARE INTEGER Shell_NotifyIcon IN shell32.dll AS WinAPI_Shell_NotifyIcon ;
			INTEGER dwMessage, ;
			string @ PNOTIFYICONDATA

		#define NIM_ADD         0x00000000
		#define NIM_MODIFY      0x00000001
		#define NIM_DELETE      0x00000002
		#define NIF_MESSAGE     0x00000001
		#define NIF_ICON        0x00000002
		#define NIF_TIP         0x00000004

		*#define NIF_STATE       0x00000008
		#define NIF_INFO        0x00000010

		LOCAL cNotifyIconData
		WITH THIS
		UNBINDEVENTS(_screen, "MouseWheel", this, "receiveIconEvent")
		.Enabled = .f.


		* Build structure.
		cNotifyIconData = .IntegerToString(_vfp.hwnd)
		cNotifyIconData = cNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000)
		cNotifyIconData = cNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON))	&&NIF_TIP,NIF_INFO,NIF_STATE, ,NIF_TIP
		cNotifyIconData = cNotifyIconData + .IntegerToString(0x20A)	&& uCallback
		cNotifyIconData = cNotifyIconData + .IntegerToString(0)	&& icon handle
		cNotifyIconData = cNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 63), 64, CHR(0))
		cNotifyIconData = .integertostring(LEN(cNotifyIconData) + 4) + cNotifyIconData
		ENDWITH
		nReturn = WinAPI_Shell_NotifyIcon(NIM_DELETE, @cNotifyIconData)
		THIS.ShellVersion = 0


		IF TYPE("_Screen.nSysTrayCount")="N" AND _Screen.nSysTrayCount=1
			* There has not been two simultaneous instances of this object
			* in the current session.  We can clean up after ourselves.
			_Screen.nSysTrayCount = 0
			SYS(2060, THIS.nPrevMWAccrual)	&& Reset mousewheel behavior.
		ENDIF
	ENDPROC


	*-- Displays the menu defined by the MenuText property, or runs an MPR that defines a shortcut menu.  Accepts string parameter containing the filename of the MPR to run, and optional second parameter to be passed to the MPR startup code.
	PROCEDURE showmenu
		LPARAMETERS lcMPRFileName, p1, p2, p3, p4, p5, p6
		* Displays the shortcut menu near the taskbar icon.
		* If the menu is defined as a set of tokens and text strings, the
		* menu is defined here.  Otherwise, the specified MPR is called.
		* If the first parameter is blank or not specified, the contents
		* of the MenuText property are used to define/determine the menu.
		* If the first parameter is not blank, it is assumed to specify
		* the MPR to run. The MenuText property is ignored.
		*
		* The additional parameters are passed on to the MPR. They are only
		* used if the cMPRFileName parameter is specified and non-blank.
		*
		* In order to display the shortcut menu in the proper location,
		* we must activate it within a toplevel form (with .visible = .t.),
		* near the taskbar.
		* On WindowsME, Win2k and later, the form is made invisible by
		* calling a Windows API function. (See the INIT of the form class.)
		* But on Win98 and NT4, that API function didn't exist, so we make it
		* a "Desktop" form instead. This allows us to remove the titlebar and
		* make the form so small it won't be noticed. But it causes slightly
		* incorrect behavior when trying to clear the menu.

		LOCAL lhPreviousWindow, lcPOINT, lnReturn, lnXCoord, lnYCoord, lcClassLib, llContinue
		LOCAL lnTaskBarLeft, lnTaskBarTop, lnTaskBarRight, lnTaskBarBottom, lnTaskBarLocation
		LOCAL loTempForm as Form

		* Our invisible form must be made the top window in order to make the shortcut
		* menu get cleared properly if user clicks elsewhere. Therefore, we store the
		* handle of the current foreground window, so we can restore it later.
		DECLARE INTEGER GetForegroundWindow IN user32.dll AS WinAPI_GetForegroundWindow
		lhPreviousWindow = WinAPI_GetForegroundWindow()

		* Get location of the mouse pointer.
		DECLARE INTEGER GetCursorPos IN user32.dll AS WinAPI_GetCursorPos STRING @
		lcPOINT = REPLICATE(CHR(0), 8)
		lnReturn = WinAPI_GetCursorPos(@lcPOINT)
		lnXCoord  = THIS.StringToInteger(LEFT(m.lcPOINT,4))
		lnYCoord = THIS.StringToInteger(SUBSTR(m.lcPOINT, 5))

		* Get the position and coordinates of the taskbar.
		lnTaskBarLeft = 0
		lnTaskBarRight = 0
		lnTaskBarTop = 0
		lnTaskBarBottom = 0
		lnTaskBarLocation = THIS.GetTaskBarPosition(@lnTaskBarLeft, @lnTaskBarTop, @lnTaskBarRight, @lnTaskBarBottom)

		*  Need Top-Level form.
		*  It also has ShowInTaskBar=.F. so we don't get a large button on the taskbar.
		*  It is also invisible, using the SetWindowLong API function in the form's INIT.
		lcClassLib = "systray.vcx"
		llContinue = .T.
		DO WHILE llContinue
		TRY
			loTempForm = NEWOBJECT("x_frmInternalSystrayUseOnly", lcClassLib )
			llContinue = .F.
		CATCH
			IF lcClassLib = "systray.vcx"
				* Try to create the form by looking in the same place as this object
				lcClassLib = ADDBS(JUSTPATH(SYS(1271, this))) + "systray.vcx"
			ELSE
				loTempForm = .Null.
				llContinue = .F.
			ENDIF
		ENDTRY
		ENDDO


		* Now position form near mouse, but adjust for taskbar.
		DO CASE
		CASE m.lnTaskBarLocation = 1	&& Left
			loTempForm.top = m.lnYCoord
			loTempForm.Left = m.lnTaskBarRight + 7
		CASE m.lnTaskBarLocation = 2	&& Top
			* Ideally would determine height of titlebar, and subtract that.
			* But this is sufficient.
			loTempForm.top = m.lnTaskBarBottom - 8
			loTempForm.Left = m.lnXCoord
		CASE m.lnTaskBarLocation = 3	&& Right
			loTempForm.top = m.lnYCoord
			*loTempForm.left= m.lnXCoord -2*this.MenuOffsetFromRight
			loTempForm.Left = m.lnTaskBarLeft- this.MenuOffsetFromRight  && Roughly the width of a typical menu.
		OTHERWISE			&& Bottom, or coding error.
			*  Just place the window near the mouse.
			loTempForm.top = m.lnYCoord
			loTempForm.Left = m.lnXCoord
		ENDCASE

		* Window must be the foreground window when shortcut menu displayed.  Otherwise,
		* clicking in another window won't release the shortcut menu.
		loTempForm.Show
		THIS.SetForegroundWindow(loTempForm.hWnd)

		*  Now display the menu.
		DO CASE
		CASE (PCOUNT() > 0 AND TYPE("m.lcMPRFileName") = "C")
			* Want to run the MPR specified in parameter

			IF PCOUNT() = 1
				DO (m.lcMPRFileName)
				RELEASE loTempForm
			ELSE
				* Build parameter list
				LOCAL lcParamList, lnCount
				lcParamList = "p1"
				FOR nCount = 3 TO PCOUNT()	&& if PCOUNT() is only 2, don't enter loop.
					lcParamList = m.lcParamList + ", p" + TRANSFORM(m.lnCount - 1)
				ENDFOR

				DO (m.lcMPRFileName) WITH &lcParamList
				RELEASE loTempForm
			ENDIF

		CASE THIS.MenuTextIsMPR AND FILE(THIS.MenuText)
			* We want to run the MPR specified in MenuText property.
			DO (THIS.MenuText)
			RELEASE loTempForm

		OTHERWISE

			* Build menu from this.MenuText.
			LOCAL lnMenuItems, lcMenuName, laMenuItems[1], lnCurrentMenuItem
			LOCAL lnToken, lcText, lnBarNum
			lnMenuItems = ALINES(m.laMenuItems, THIS.MenuText, .T., ";")
			IF m.lnMenuItems = 0
				RELEASE loTempForm
				RETURN .f.
			ENDIF
			IF m.lnMenuItems % 2 != 0
				*  Incorrect number of items in MenuText property.
				IF m.lnMenuItems > 1
					lnMenuItems = lnMenuItems - 1
				ELSE
					RELEASE loTempForm
					RETURN .f.
				ENDIF
			ENDIF
			lcMenuName = SYS(2015)
			* Make private variable to receive the ID of the selected menu item.
			PRIVATE __systray_menu_return
			__systray_menu_return = 0

			*  Build the menu.
			DEFINE POPUP (m.lcMenuName) SHORTCUT RELATIVE FROM MROW(),MCOL()
			FOR lnCurrentMenuItem = 1 TO m.lnMenuItems STEP 2
				lnToken = VAL(m.laMenuItems[m.lnCurrentMenuItem])
				lcText = m.laMenuItems[m.lnCurrentMenuItem + 1]
				lnBarNum = FLOOR(m.lnCurrentMenuItem/2) + 1
				DEFINE BAR (lnBarNum) OF (m.lcMenuName) PROMPT (m.lcText)
				lcOnSelectionBar = "ON SELECTION BAR " + TRANSFORM(m.lnBarNum) + " OF " + ;
							TRANSFORM(m.lcMenuName) + " __systray_menu_return = " + TRANSFORM(m.lnToken)
				&lcOnSelectionBar
			ENDFOR
			SET ESCAPE off

			*  Display the menu and wait for user action.
			ACTIVATE POPUP (m.lcMenuName)

			RELEASE loTempForm
			THIS.ProcessMenuEvent(__systray_menu_return)
			RETURN __systray_menu_return

		ENDCASE

		* Menu has been deactivated.  Restore previous foreground window.
		This.SetForegroundWindow(m.lhPreviousWindow)
	ENDPROC

	*-- This event is called if user has selected an item from a menu defined by the MenuText property.  Add code here to act upon the user's selection.
	PROCEDURE processmenuevent
		LPARAMETERS nMenuItemID
		*  This event is fired after a menu has been clicked or cleared.
	ENDPROC

	*-- Occurs when the user clicks on the icon in the Taskbar Notification Area ("systray").
	PROCEDURE iconclickevent
		* This "event" is called when the user left-clicks on the icon.

		* Default behavior is to display the menu:
		IF NOT ISBLANK(THIS.MenuText)
			THIS.ShowMenu()	&& The ShowMenu method will handle any errors.
		ENDIF
	ENDPROC

	*-- Converts an integer to its binary representation.
	HIDDEN PROCEDURE integertostring
		LPARAMETERS nInteger
			* Changes an integer into the Intel representation of that integer.
			* Used to build structures.
			RETURN CHR(BITAND(nInteger, 255)) + CHR(BITAND(BITRSHIFT(nInteger, 8), 255)) + ;
					CHR(BITAND(BITRSHIFT(nInteger, 16), 255)) + CHR(BITAND(BITRSHIFT(nInteger, 24), 255))
	ENDPROC

	*-- Loads an Icon from a file, and returns an hIcon token.
	HIDDEN PROCEDURE loadicon
		LPARAMETERS cIconFileName
		*  Loads an icon as a Windows resource. Returns the resource handle.

		LOCAL cOldTalk, cTempFileName, hImage

		DECLARE INTEGER LoadImage IN user32.dll AS WinAPI_LoadImage ;
			INTEGER hinst, ;
		    STRING lpszName, ;
		    INTEGER uType, ; 	&& uint
		    INTEGER cxDesired, ;
		    INTEGER cyDesired, ;
		    INTEGER fuLoad	&& uint

		** LoadImage defines ****
		#define IMAGE_ICON          1
		#define LR_LOADFROMFILE     0x0010

		*  Assume that the icon is contained in the app/exe file.  Copy to temp before loading.
		IF FILE(cIconFileName)
			cTempFileName = ADDBS(GETENV("temp")) + "systray_temp_" + SYS(3) + ".icn"
			COPY FILE (cIconFileName) TO (cTempFileName)
		ELSE
			RETURN
		ENDIF

		m.hImage = WinAPI_LoadImage( 0, ;	&& Don't load from resource file
					cTempFileName, ;
					IMAGE_ICON, ;
					0, ;
					0, ;
					LR_LOADFROMFILE)

		*  Now need to erase the temp file.  SET TALK OFF to avoid the "File has been erased"
		*  message on the status bar.
		cOldTalk = SET("TALK")
		ERASE (cTempFileName)
		SET TALK &cOldTalk


		IF m.hImage = 0
			this.Error(0, "Failed to load icon '" + cIconFileName + "'. in " + PROGRAM(), LINENO())
		ENDIF

		RETURN m.hImage
	ENDPROC


	HIDDEN PROCEDURE tiptext_assign
		LPARAMETERS vNewVal
		* The text for the tooltip is being changed.
		THIS.tiptext = ALLTRIM(m.vNewVal)

		IF THIS.Enabled
			*  This icon is already displayed in the Taskbar Notification Area,
			*  so we must update the systray immediately:
			this.AddIconToSystray()
		endif
	ENDPROC


	PROCEDURE currenticonindex_assign
		LPARAMETERS vNewVal
		*  This is called when user changes the CurrentIconIndex property.

		THIS.currenticonindex = m.vNewVal
		IF this.Enabled
			* We're attached to systray.  Update immediately.
			this.AddIconToSystray
		ENDIF
	ENDPROC

	*-- Convert binary representation of integer into an integer data type.
	HIDDEN PROCEDURE stringtointeger
		LPARAMETERS lcPDWORD, lnBytes

		*  Converts a string from the Intel int, DWORD,WORD, and BYTE formats
		*  into a VFP integer.
		IF PCOUNT() < 2
			lnBytes = 4	&& No length provided, assume 4-byte integer.
		ENDIF
		LOCAL lnReturn, lnCurByte
		lnReturn = 0
		FOR lnCurByte = 1 to m.lnBytes
			lnReturn = m.lnReturn + ASC(SUBSTR(m.lcPDWord, m.lnCurByte, 1))*(256^(m.lnCurByte-1))
		ENDFOR
		RETURN lnReturn
	ENDPROC

	*-- Retrieves coordinates of TaskBar.  Also returns 0 if docked on bottom, 1 if docked left, 2 if docked top, 3 if docked right.
	PROCEDURE gettaskbarposition
		LPARAMETERS nLeft, nTop, nRight, nBottom

		* We position any menus near where the mouse just clicked. But we need to
		* fine-tune that position depending on which edge of the screen the
		* user has docked the taskbar.  This method retrieves the absolute
		* coordinates of the taskbar, and derives from those coordinates which
		* edge the taskbar is on.

		#DEFINE ABM_GETTASKBARPOS 0x00000005

		DECLARE INTEGER SHAppBarMessage IN shell32.dll as WinAPI_SHAppBarMessage ;
			INTEGER dwMessage, STRING @ PAPPBARDATA

		LOCAL cAPPBARDATA, nReturn

		WITH THIS
			cAPPBARDATA = .IntegerToString(0)	&& hWnd
			cAPPBARDATA = cAPPBARDATA + .IntegerToString(0)	&& uCallBackMsg
			cAPPBARDATA = cAPPBARDATA + .IntegerToString(0)	&& Edge
			cAPPBARDATA = cAPPBARDATA + REPLICATE(CHR(0), 4*4)	&& RECT structure
			cAPPBARDATA = cAPPBARDATA + .IntegerToString(0)	&& lParam
			cAPPBARDATA = .IntegerToString(LEN(cAPPBARDATA) + 4) + cAPPBARDATA

			nReturn = WinAPI_SHAppBarMessage(ABM_GETTASKBARPOS, @cAPPBARDATA)

			*  Taskbar coordinates should be in RECT structure.
			nLeft = .StringToInteger(SUBSTR(cAPPBARDATA, 17, 4))
			IF nLeft > 0xFFFFFF	&& If the number is ridiculously large, assume it was a negative number.
				nLeft = 0 - (0xFFFFFFFF - nLeft)
			ENDIF
			nTop = .StringToInteger(SUBSTR(cAPPBARDATA, 21, 4))
			IF nTop > 0xFFFFFF
				nTop = 0 - (0xFFFFFFFF - nTop)
			ENDIF

			nRight = .StringToInteger(SUBSTR(cAPPBARDATA, 25, 4))
			IF nRight > 0xFFFFFF
				nRight = 0 - (0xFFFFFFFF - nRight)
			ENDIF

			nBottom = .StringToInteger(SUBSTR(cAPPBARDATA, 29, 4))
			IF nBottom > 0xFFFFFF
				nBottom = 0 - (0xFFFFFFFF - nBottom)
			ENDIF

		ENDWITH

		DO CASE
		*  Must use approximations for taskbar coordinates, because they oare slightly different
		*  on different versions of Windows.
		CASE nLeft < 2 AND nTop < 2 AND nRight < 479 && Roughly minimum width in portrait mode.
			RETURN 1	&& Left
		CASE nLeft < 2 AND nTop > 0
			RETURN 0	&& Bottom
		CASE nLeft < 2 AND nTop < 2	&& Must be top, otherwise first case would take it.
			RETURN 2	&& Top
		OTHERWISE
			RETURN 3	&& Right
		ENDCASE


		*!*	typedef struct _AppBarData {
		*!*	    DWORD cbSize;
		*!*	    HWND hWnd;
		*!*	    UINT uCallbackMessage;
		*!*	    UINT uEdge;
		*!*	    RECT rc;
		*!*	    LPARAM lParam;
		*!*	} APPBARDATA, *PAPPBARDATA

		*!*	typedef struct _RECT {
		*!*	    LONG    left;
		*!*	    LONG    top;
		*!*	    LONG    right;
		*!*	    LONG    bottom;
		*!*	} RECT

	ENDPROC


	*-- Determines the Major and Minor version of shell32.dll.  Used to determine if the shell supports balloon tips on taskbar icons.
	PROCEDURE getshellversion
		DECLARE INTEGER DllGetVersion IN shell32.dll AS WinAPI_Shell32_DllGetVersion ;
			STRING @ DLLVERSIONINFO

		* Windows 98 typically returns 4.72, depending on version of IE installed.
		* Windows ME returns 5.5 or greater.
		* Windows 2000 returns 5.0
		* Windows XP returns 6.0

		LOCAL lcDLLVersionInfo, lnMajor, lnMinor

		WITH THIS
		lcDLLVersionInfo = .IntegerToString(20)
		lcDLLVersionInfo = m.lcDLLVersionInfo + REPLICATE(CHR(0), 16)

		WinAPI_Shell32_DllGetVersion(@lcDLLVersionInfo)

		lnMajor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 5, 4))
		lnMinor = .StringToInteger(SUBSTR(m.lcDLLVersionInfo, 9, 4))

		RETURN VAL(STR(m.lnMajor, 2, 0) + "." + ALLTRIM(STR(m.lnMinor,3,0)))
		ENDWITH
	ENDPROC

	*-- Occurs when the user right-clicks on the icon in the Taskbar Notification Area ("systray").
	PROCEDURE iconrightclickevent
		* This "event" is called when the user right-clicks on the icon.

		* Default behavior is to display the menu:
		IF NOT ISBLANK(THIS.MenuText)
			THIS.ShowMenu()	&& The ShowMenu method will handle any errors.
		ENDIF
	ENDPROC

	HIDDEN PROCEDURE iconfile_assign
		LPARAMETERS vNewVal
		*To do: Modify this routine for the Assign method
		THIS.ICONFILE = m.vNewVal

		* Updating this property will release all other icons.
		THIS.ClearIconList()
		THIS.AddIconToIconList(m.vNewVal)	&& Going from 0 to 1 icon updates systray immediately.
	ENDPROC

	*-- Displays a balloon tip over the icon in the Taskbar Notification Area.  Parameters: cBalloonText [, cBalloonTitle [, nIcon [, nTimeoutInSeconds]]]
	PROCEDURE showballoontip
		LPARAMETERS lcBalloonText, lcBalloonTitle, lnIcon, lnTimeout
		* Displays a balloon tip if the Operating System shell supports it.
		* The lnTimeout parameter is the number of SECONDS to display the balloon. This
		* timeout period is subject to minimum and maximum values set by the operating
		* system (typically 10 and 30 seconds).

		* The lnIcon parameter specifies the icon to display in the
		* balloon.  The values are as follows:
		*    0 = No icon
		*    1 = Info. A lowercase "i" in a small balloon
		*    2 = Warning. An exclamation point (!) in a triangle.
		*    3 = Error. A red disk with an X through it.
		*    Add 16 (0x10) to the value to mute the sound that is played when
		*    the balloon is displayed (Windows XP and later).

		* This method uses #DEFINES that are set in the AddIconToSystray method.

		IF PCOUNT() < 1
			*  Must at least include the balloon Text.
			RETURN -1
		ENDIF

		IF THIS.ShellVersion < 5	&&GetShellVersion() < 5.00
			*  This shell doesn't support balloon tips.
			RETURN -1
		ENDIF

		IF TYPE("m.lcBalloonText") <> "C"
			RETURN -1
		ENDIF

		IF TYPE("m.lcBalloonTitle") <> "C"
			lcBalloonTitle = ""
		ENDIF

		IF TYPE("m.lnIcon") <> "N"
			lnIcon = 0
		ENDIF

		IF TYPE("m.lnTimeout") <> "N"
			lnTimeout = 0
		ELSE
			lnTimeOut = m.lnTimeout * 1000	&& Convert seconds to Milliseconds.
		ENDIF

		LOCAL lcNotifyIconData, lnReturn

		WITH THIS
				*  Build NOTIFYICONDATA structure compatible with version 5 of the shell.
				lcNotifyIconData = .IntegerToString(_vfp.hwnd)
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(.SystrayIconID * 0x10000)
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(BITOR(NIF_TIP,NIF_MESSAGE ,NIF_ICON, NIF_INFO))	&&NIF_TIP,NIF_INFO,NIF_STATE
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(0x20A)	&& uCallback
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(.aIconList[.CurrentIconIndex])	&& icon handle
				lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(.TipText), 127), 128, CHR(0))	&& TipText
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(0)	&& dwState
				lcNotifyIconData = lcNotifyIconData + .IntegerToString(0)	&& dwStateMask
				lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonText), 255),256, CHR(0))	&& balloon tip.
				lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(m.lnTimeOut) && Timeout/Version
				lcNotifyIconData = lcNotifyIconData + PADR(LEFT(TRANSFORM(m.lcBalloonTitle), 63),64, CHR(0))	&& balloon tip title.
				lcNotifyIconData = lcNotifyIconData + .IntegerToSTring(BITAND(lnIcon, 0x1F)) && Info flags
				lcNotifyIconData = .IntegerToString(LEN(lcNotifyIconData) + 4) + lcNotifyIconData	&& length of structure


				*  Send request to display balloon.  This is asynchronous, so
				*  balloon may not be displayed immediately.
				lnReturn = WinAPI_Shell_NotifyIcon( NIM_MODIFY, @lcNotifyIconData)
		ENDWITH
	ENDPROC

	PROCEDURE Destroy
		WITH THIS
		IF .Enabled
			.RemoveIconFromSystray()
			.ClearIconList()	&& Release resources.
		ENDIF
		ENDWITH
	ENDPROC

	PROCEDURE Init
		*  If there is an initial icon specified, add it to the Icon List.
		IF TYPE("THIS.IconFile") = "C" AND LEN(THIS.IconFile) > 0
			THIS.AddIconToIconList(THIS.IconFile)
		ENDIF

		*  Add icon to systray if requested.
		IF this.AddIconToSystrayAtInit
			this.AddIconToSystray
		ENDIF
	ENDPROC

	*-- Occurs when user clicks on balloon tip.
	PROCEDURE balloonclickevent
	ENDPROC

	*-- Occurs when the BalloonTip is shown.  Balloon tips are queued by the system, so you balloon tip may not be displayed immediately after calling the DisplayBallloonTip method.
	PROCEDURE balloonshowevent
	ENDPROC

	*-- Occurs if the user clicks on the close button of the balloon tip.
	PROCEDURE balloonhideevent
	ENDPROC

	*-- Occurs if the system closes the balloon tip due to reaching the timeout.
	PROCEDURE balloontimeoutevent
	ENDPROC

	*-- Occurs when the user clicks the icon using the middle mouse button.
	PROCEDURE iconmiddleclickevent
	ENDPROC

	*-- Occurs when the user double-clicks on the icon in the Taskbar Notification Area.
	PROCEDURE icondblclickevent
	ENDPROC

ENDDEFINE

*-- EndDefine: systray
*
DEFINE CLASS x_frminternalsystrayuseonly AS form
	Top = 0
	Left = 0
	Height = 34
	Width = 123
	ShowWindow = 2
	ShowInTaskBar = .F.
	DoCreate = .T.
	BorderStyle = 0
	Caption = ""
	ControlBox = .F.
	Closable = .F.
	MaxButton = .F.
	MinButton = .F.
	Movable = .F.
	MaxHeight = 1
	TitleBar = 0
	Themes = .F.
	Name = "x_frminternalsystrayuseonly"

	PROCEDURE Init
		*  For proper menu behavior, this form must be a Top-Level form, not a Desktop form.
		*  But Top-Level forms are required to have a titlebar, which can be visible from
		*  beneath the menu.  Therefore, we'll make this window invisible, before it is
		*  even displayed.

		#DEFINE GWL_EXSTYLE         -20
		*#DEFINE WS_EX_LAYERED       0x00080000
		#DEFINE WS_EX_TRANSPARENT   0x00000020

		DECLARE SetWindowLong IN USER32.DLL AS WinAPI_SetWindowLong Integer, Integer, Integer
		*DECLARE SetLayeredWindowAttributes In Win32Api Integer, String, Integer, Integer
		WinAPI_SetWindowLong(THIS.hWnd, GWL_EXSTYLE, WS_EX_TRANSPARENT)
		*SetLayeredWindowAttributes(THIS.hWnd, 0, 0, 2)

		*  Make window as small as possible so menu position will be more accurate.
		THIS.Width = 1
		THIS.Height = 1
	ENDPROC
ENDDEFINE
*-- EndDefine: x_frminternalsystrayuseonly



                     

Yousfi Benameur


Important:All Codes above are tested on VFP9SP2 & windows 10 pro 64 bits version 1709(fall creator) & IE11 emulation. Navigator: firefox - screen:32 pouces.

To be informed of the latest articles, subscribe:
Comment on this post