*!* 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
**********************************
FUNCTION 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