Put any application on systray knowing its HWND
![]()
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
Important:All Codes above are tested on VFP9SP2 & windows 10 pro 64 bits version 1709(fall creator) & IE11 emulation. Navigator: firefox - screen:32 pouces.