Josip Zohil, Koper, Slovenija, January, 2015

BINDEVENT and errors in Visual FoxPro

When we bind two events using a Visual FoxPro (VFP) BINDEVENT command, the event that happens first is removed from a Call Stack. In cases we have an error in the second function (delegate), we don't know from where it was called. We would "enrich" the BINDEVENT function and extend its functionality to report the whole chain of function calls.

The problem

For example, we have a form with a button and its Click event. In it is an error.

Define Class mform As Form

Add Object btn As CommandButton

Function btn.Click()

Local T, ex

Try

T=z   && error, z is not defined

Catch To ex

Throw ex

Endtry

Endfun

Enddef

Example without a BINDEVENT

EXAMPLE PROGRAM

Clear

Local oForm As Form, ex1, ot, ox

Try  && centralized error catch

oForm=Createobject("mFORM")

ot=Createobject("Timer")

ot.Interval=1000

* limited error call trace

* bindevent(ot,"timer",oForm.btn,"Click",1)

*centralized error catch

*obt=Createobject("bindTracer",ot,"timer",oForm.btn,"Click")

*non centralized error catch

*obt=Createobject("bindTracer",ot,"timer",oForm.btn,"Click","ffail")

oForm.Show()

Read Event

Catch To ex1

Activate Screen

ox=ex1.UserValue

?ox.Message,ox.LineContents,ex1.LineContents,ox.Details,ex1.Details,ex1.Lineno,ox.Procedure

Finally

Clear Events

Endtry

Return

We run this program and  click on a button. VFP reports an error in a usual way.

Example with a BINDEVENT

Uncomment this line:  bindevent(ot,"timer",oForm.btn,"Click",1) and run the program again. It reports the same error as in the first example.

We don't know from where the btn.click event was raised: from a timer or from a mouse click? There is no evidence or an indication of the former presence or existence of an action that initiate the Click event.

BINDEVENT and a bindtrace object

 

Let us define a class BindTracer that will attach (bind) additional information to the eventual Exception object.   This class has a method bindtrace. In it, we add some information to the eventual Exception object.

Define Class BindTracer As Custom

oObj=Null         && first parameter of a bindevent

ofun=""             && second parameter of a bindevent

producer=Null && object to be bound, third parameter of a bindevent

tobindfun=""   && function to be bound, fourth parameter of a bindevent

fail=""               && eventual function to report errors

Function Init(ot, ft, oprod, fun, ffail)

This.producer=oprod

This.tobindfun=fun

This.oObj=ot

This.ofun=ft

This.fail=ffail

Bindevent(This.oObj,This.ofun,This,"bindtrace",1)  && bind a first function to the function bindtrace

Endfunc

Function bindTrace()     && ofun as parameter

Local ex As Exception, la As String, res, lof, ox

la=""

res=""

Try

lof="this.producer."+This.tobindfun+"()"

res=Evaluate(lof)     && evaluate the delegate function (second)

Catch To ex

* like aevents function

la=this.oObj.Name+" "+this.ofun+" "+Program()+" "+ This.producer.Name+" "+ This.tobindfun

ox=Createobject("exception")

ox=ex.UserValue

ox.Message=ex.UserValue.Message+la

If Vartype(This.fail)$"NL" Or Len(Trim(This.fail))=0   && fail function is present ?

Throw ox

Else

With ox

Evaluate(This.fail+"()")  && evaluate the fail function

Endwith

Endif

Endtry

Return res

Endfunc

Enddef

We evaluate the second function of a BINDEVENT command. In case it throws an exception, we create a new exception object and add it the information about the previous event handler (the event source). After that we throw a new exception in an alternative way: If the Bindtrace object has a fail function, we pass the exception to this function, otherwise we throw it.

Behind the scene, in the init procedure, we bind the timer's timer event to the bindtrace function. Implicitly, when a timer event happens, it activates the function bindtrace and the last evaluates the cmd.Click event handler (the delegate). We have wrapped the function click inside a function bindtrace.

The class bindtrace accepts four parameters, we usually pass to the bindevent function; arbitrary we can pass also the fifth parameter, a function to present the eventual exception information. It accepts " as a parameter" the exception object.

Example with a BINDEVEND with centralized error catch

 

In the previous example comment the line * bindevent(ot,"timer",oForm.btn,"Click",1)

and uncomment the line

obt=Createobject("bindTracer",ot,"timer",oForm.btn,"Click")

This command creates the object bindTrace. Its parameters are similar to a bindevent function.

We run the modified program from the EXAMPLE PROGRAM. It reports an error. In it are included also the information about the first function of a bindevent.

All the complexity of error management is hidden inside the object bindTrace. We use this object in a similar way as a BINDEVENT function.

Example with a BINDEVEND with non centralized error catch

The object bindTrace accepts also the fifth parameter, a string, a name of an error function, that is a member of an exception object. For example, the next function ffail print the error message on the screen.

Function ffail()

Activate Screen

?"Non centralized error catch:"

?.Message, .LineContents, .Details, .Lineno, .Procedure, Program()

Clear Events

Endfunc

We uncomment this line: obt=Createobject("bindTracer",ot,"timer",oForm.btn,"Click","ffail")  of the EXAMPLE PROGRAM. This command has also a fifth parameter "ffail". We shall catch the eventual errors of a bindevent function using a ffail function. Errors will not be propagated to the central error catch construction. It will be printed on a screen.

Conclusion

Using the object bindTrace we can bind  events in a similar way as using a bindevent function. The bindTrace object has the additional capabilities to manage eventual errors inside the delegate (second) function of a bindevent command. We can use a centralized or non centralized way to manage errors.

BINDEVENT behaves as an asynchronous function; It fires a first event (put in a call stack the first event handler) and after that remove it and put the delegate function in a call stack. When this second function executes it has not information about the previous function on a call stack; there is a "break" between the two. We must "help" a VFP debugger.

The delegate function (a second function, in our case a cmd.click) is a callback function; it reacts as a consequence of a first event (in our case a timer event). It is without a callback in case of eventual error. The function ffail is a callback for an eventual error. We have two execution path:

·       a normal - CMD.CLICK,

·       an error - ffail.