Why does BINDEVENT not work with StatusBar_Change?
A customer had a question and sent some code:
PUBLIC oHandler
oHandler = NEWOBJECT("StatusBarText")
?BINDEVENT(_VFP,[STATUSBAR],oHandler,[STATUSBAR_CHANGE],1)
_VFP.StatusBar = "AAAA" && triggers handler
SET MESSAGE TO "1111" && does not trigger handler
*navigate menus: does not trigger handler
DEFINE CLASS StatusBarText AS Session
PROCEDURE StatusBar_Change
?PROGRAM(),_vfp.StatusBar
RETURN
ENDDEFINE
This code demonstrates 3 ways to change the status bar text, but only one fires the hooked event.
The status bar text can change in various ways, which means there are various code paths internal to VFP that are followed. When changing the status bar text directly via direct assignment to _VFP.Statusbar, the normal VFP code (the object manager) that changes an object’s property gets invoked, which knows to check for and execute any BindEvent code.
When the user activates a menu, the WM_ENTERIDLE Notification is sent to VFP’s Window procedure with WPARAM = MSGF_MENU, which tells VFP to draw the menu message on the status bar.
When the user executes SET MESSAGE TO, the internal VFP object manager is completely bypassed to change the status bar text.
The BINDEVENT feature allows the user to bind events to when an object property or method is invoked or changed. When I implemented the feature, I modified the internal VFP object manager to check for user bound events. I paid no attention to the SET MESSAGE TO and the WM_ENTERIDLE code, which had been in existence for several versions.
I suppose one could argue that this is a bug, but the documentation says:
to bind events, properties, or methods from native Visual FoxPro objects to other Visual FoxPro objects.
There is a simple workaround: use Bindevents to bind to the WM_ENTERIDLE message and change SET MESSAGE TO cNewMessage to _VFP.StatusBar=cNewMessage
#define GWL_WNDPROC (-4)
#define WM_ENTERIDLE 0x0121
#define MSGF_DIALOGBOX 0
#define MSGF_MESSAGEBOX 1
#define MSGF_MENU 2
#define MSGF_SCROLLBAR 5
PUBLIC oHandler
oHandler = NEWOBJECT("StatusBarText")
?BINDEVENT(_VFP,[STATUSBAR],oHandler,[STATUSBAR_CHANGE],1)
?BINDEVENT(_vfp.HWnd,WM_ENTERIDLE,oHandler, "HandleMsg",5)
_VFP.StatusBar = "AAAA" && triggers handler
SET MESSAGE TO TRANSFORM(SECONDS()) && does not trigger handler
DEFINE CLASS StatusBarText AS Session
dwOrigWindProc=0
PROCEDURE init
DECLARE integer GetWindowLong IN WIN32API ;
integer hWnd, ;
integer nIndex
DECLARE integer CallWindowProc IN WIN32API ;
integer lpPrevWndFunc, ;
integer hWnd,integer Msg,;
integer wParam,;
integer lParam
THIS.dwOrigWindProc =GetWindowLong(_VFP.HWnd,GWL_WNDPROC)
PROCEDURE HandleMsg(hWnd as Integer, msg as Integer, wParam as Integer, lParam as Integer)
LOCAL nRetvalue
nRetvalue=0
* ?PROGRAM(),msg,TRANSFORM(wParam,"@0x"),TRANSFORM(lParam,"@0x")," "
DO CASE
CASE msg =WM_ENTERIDLE
IF wParam = MSGF_MENU
*call default processing to set menu message
nRetvalue=CallWindowProc(this.dwOrigWindProc,hWnd,msg,wParam,lParam)
?PROGRAM(),_vfp.StatusBar
ENDIF
ENDCASE
RETURN nRetvalue
PROCEDURE StatusBar_Change
?PROGRAM(),_vfp.StatusBar
RETURN
ENDDEFINE
See also How to hook command window keystrokes.