Version: 1.00.00 - last update: Wednesday, August 30, 2011, 14:25:00
Recently, I reviewed some of my elder VFP event binding code and stumbled upon some unknown(?) COOL behavior!
Intro
Instantiating objects from classes, calling their methods, passing (parameter) values back and forth between them, is our bread and butter business. Since VFP introduced event binding a whole new universe of possibilities arose. This post cannot cover the whole subject in detail; one has to write a book for that to come close. Thus, I am going to show you the most interesting (partially unknown!) aspects of event binding today.
What You Should Know
Well, you should have worked with VFP’s event binding. At least, you should have read about it! I’m going to discuss the following commands and functions:
BINDEVENT(oEventSource, cEvent, oEventHandler, cDelegate [, nFlags])
UNBINDEVENTS(oEventSource, cEvent, oEventHandler, cDelegate)
AEVENTS( ArrayName [, 0 | 1 | oEventObject ] )
RAISEEVENT( oEventSource, cEvent [, eParm1...] )
You should be familiar with the above commands, especially with the correct application of the [, nFlags] parameter! At least you should know the difference between nFlags:= 0 and nFlags:= 1 and you should also know the difference between that first pairing and the second set: nFlags:= 2 and nFlags:= 3.
Event binding makes it possible to extend otherwise sealed classes, even at runtime! Event binding makes your class design extendable and thus, reusable by others without touching your source code at all! Great benefits often have some drawbacks, which especially is true in case of VFP’s event binding implementation. Binding to VFP’s events is quickly learned; securely implementing custom events and handle unsolicited binding-clients the right way seems to be a little bit more challenging! Finally, binding parameterized delegates to scalar and non-scalar properties, can be regarded mastering the fine art of event binding!
A Deeper Understanding
To gain a better understanding of what’s going on we have to make clear some terms and talk about some differences, first. A lot of VFP-newbies make errors in reasoning, while implementing method code for VFP events like shown below:
Answer the following questions:
“What kind of code is the above line after we saved the form class?
Is the call to “THIS.oLstGLOBALMEM.Requery()” the Form1.Activate() event now?
Can we manipulate VFP’s form activation behavior through adding code, maybe like so:
Can we hinder VFP to activate the form through code inserted here, anyway?
The answer simply is: “NO!”
VFP’s form activate event is a simple NOTIFICATION. The code we are adding in the editor has nothing to do with handling the form activation itself! VFP has already done all that handling completely and transparently behind the scenes before passing back programmatic control to our code block above. Our code solely is a NOTIFICATION REACTION. The only reason to add custom code to such kind of VFP event is that we want some code to be executed automatically every time our form gets activated (maybe to signal the form activation to another observer instance within our framework).
There are other kinds of native VFP events. First, let’s look at the INIT() event which is a public feature of every VFP class:
Everybody knows: returning a Boolean FALSE from Init() will cancel the whole instantiation process. In terms of completing object construction, our code can be considered an addition to VFP’s internal event sequence. VFP’s Init() events are notifications as well. But, they are also EXTENDABLE. Seen from that point of view, the event acts like a program hook and our code is some kind of a POST-EVENT HOOK IMPLEMENTATION.
Finally, there is a third kind of native VFP events. One of them, the KeyPress() event, is shown below:
Code entered here may override VFP’s native implementation completely. Typically, in such a case you are using the NODEFAULT keyword to tell VFP that you are going to override it’s native behavior. You may re-enable (execute) VFP’s native behavior at any time through issuing a DODEFAULT() in your source code. Thus, I call those native VFP events OVERRIDABLE EVENTS and code added here (as long as it overrides VFP’s native behavior) is the real EVENT IMPLEMENTATION.
Let us summarize VFP’s event types:
NOTIFICATIONS are VFP post-event hooks where we can add our own REACTIONS.
EXTENSIONS are VFP post-event hooks where we can add our own IMPLEMENTATIONS.
OVERRIDES are VFP pre-event hooks where we can substitute VFP’s native behavior with our own IMPLEMENTATION.
Calling vs. Raising
An event must be raised! We neither call, nor do we raise any native VFP event implementation directly! Instead, what we do is, we call or raise our custom code added to a native VFP event. Internally, VFP treats our custom code like any other ordinary method. If you visit VFP help on BINDEVENT() you can read what is explained in case nFlag := 3 >> Call event code before delegate code. Do not trigger event (call delegate code) when simple method calls occur. VFP is able to differentiate between a “simple method call” and the “raising of an event”, which is done using the RAISEEVENT() command. Superficially, it doesn’t make any difference at all because our method code gets executed just fine in any case.
Another newbie error in reasoning is to assume, one could trigger VFP’s native behavior through raising the event. No, very sorry, but we CANNOT move the mouse over a VFP form instance by doing something like this:
RAISEEVENT(oForm1 ,"MouseMove", 10, 10)
Nothing would happen – I’m afraid, we have to use VFP’s MOUSE command further on. BTW: Reading VFP’s help file, RAISEEVENT() still is called a function there, although only returning TRUE like any other VFP procedure. Maybe some of you remember VFP’s early implementations of RAISEEVENT()better than I do! I believe version #1 of RAISEEVENT()was able to return values from the handler methods (if they had some RETURN <whatever> code implemented). That behavior was dropped in a later VFP release, because it caused some unsolvable erroneous side-effects. Nowadays, RAISEEVENT()does not return a value other than any other regular procedure does: TRUE. Thus, IMHO - RAISEEVENT()is not a function, but a command!
VFP’s help on RAISEEVENT()starts with >> You can use RAISEEVENT() to raise, or trigger, an event from a custom method. Though RAISEEVENT() applies primarily to custom methods, you can use it for raising native events and methods, too. It is true that in most cases we will use RAISEEVENT() to “ennoble” our custom methods (to become distinguishable events)!
Implementing a Custom Event
Let’s implement our own custom event to see how things work together. These are the steps we need:
- Define event (create a method with or without parameters).
- Optionally, implement event behavior within the event method.
- Optionally, you may want to implement security as described further below.
- Optionally, describe event signature and binding flag values.
- Implement a trigger either using RAISEEVENT() somewhere in your code, or within a distinct trigger method.
The easiest step is the first: Define the event method and its signature. If no parameters have to be passed and you want to create a simple NOTIFICATION, just add an empty method to your class and you’re done! We already talked about the missing return value because raising an event is not comparable to calling a function (which should always return exactly one value), but more to executing a command (which never should return a value). We could argue to compensate a missing return value by passing parameters by reference (to get back some results from there). Obviously, this was “foreseen” by some joker at Microsoft, because this option was dropped completely! If we bind to an event method with one or more parameters in its signature and the event gets raised with one or more values passed in by reference, a trappable error is thrown and our RAISEEVENT()call will fail. The best alternative we have is using Parameter Objects, as objects are always passed by reference. Thus, my favorite minimal default event implementation looks like this:
PROCEDURE LogOn(toPara AS OBJECT) AS VOID *\\ this event gets raised when the user *\\ clicks the [Submit]-button on the *\\ login dialog form ENDPROC
The event source code above consists of descriptive remark lines only. The parameter object passed in toPara is a minimalistic data container object coming from a tiny factory method like shown below:
FUNCTION CreatePara(tcName AS String, tcPassword AS String) AS ParaObject LOCAL loEvtMsgInfo AS Object m.loEvtMsgInfo = CREATEOBJECT("EMPTY") ADDPROPERTY(m.loEvtMsgInfo, "Name", m.tcName) ADDPROPERTY(m.loEvtMsgInfo, "Password", m.tcPassword) RETURN m.loEvtMsgInfo
ENDFUNC
Because there is no event behavior to implement, I am ready to implement my trigger code (which is step #5 in the above enumeration). The submit-button’s click() event is the perfect place to implement my trigger code:
PROCEDURE oSubmitButton.Click() AS VOID *\\ RAISEEVENT(THIS.oHandler ,; "LogOn" ,; goFactory.CreatePara(Thisform.TxtName.Value,; Thisform.txtPassWord.Value)) *// ENDPROC
Consuming a Custom Event
Every event handler who wants to consume our new event must bind to it using VFP’s BINDEVENT() function. Terms used, when talking about event binding, come in pairings like event source and event handler, or raise event and bind event. Finally there is a flag value used in VFP’s BINDEVENT() function to fine-tune internal signaling. To successfully establish a native VFP event binding we need two object references, as it is not possible to use an old-fashioned procedure as the event handler! (BTW: There is a VFPX project called “VFP2C32”, maintained by Christian Ehlscheid, that gives you the ability to use procedural delegates – but only for Win-Event binding.) In any case, one object is called the source of the event, the other the handler of the event. There are other naming pairs like publisher and subscriber often used in COM documentations, or signal and slot used by Nokias Qt (C++ IDE). Anyway, the event source has a public event (-method or -property) we must know by name and what signature is used when the event is raised by the trigger, so that we can bind our event handler object to it. Our event handler object must have a corresponding delegate method (no properties - only methods - are allowed as delegates) with the method signature that has at least enough parameters to receive all values passed in by the RAISEEVENT() trigger.
Sidekick: Property-based Event Binding Part I
Keep in mind: When binding to a PROPERTY-based event, our handler should be a parameter-less delegate method. At this moment, it doesn’t make much sense to use a delegate with one or more parameters, because a simple value assignment to an event-bound property CANNOT lead to a signature mismatch. There are two other things to keep in mind about property binding:
- Property binding can be compared to implementing a PropertyName_Assign() method. The delegate gets called every time some value is stored in the so decorated property, just like any _ASSIGN() method is triggered. Read access NEVER leads to delegate invocation.
- Executing a RAISEEVENT()on a property causes nothing else but a re-assignment of the current property value. In other words, the actual property value stays unchanged. Therefore, oForm1.HEIGHT = oForm1.HEIGHT can be considered to be internally treaded like a “simple method call”, whereas RAISEVENT(oForm1, “HEIGHT”) internally is recognized as the “event of property value re-assignment”.
Most of this is theory only, taken from VFP’s help files! You will get more comprehensive insights later in this post (in property-based event binding part II below).
Generic Events and Handlers
Even carefully reading of VFP’s help may lead to some misunderstanding! This is what is written about the delegate parameter of BINDEVENT():
cDelegate:
Specifies the method, or "delegate", that handles the event for oEventHandler. The delegate method must have the same parameters as the event specified in cEvent. You can call the AEVENTS( ) function to retrieve an object reference to the event source. If the delegate method does not have enough parameters to handle those passed by the event, Visual FoxPro generates an error.
The underlined sentence above may make you believe, that both, the event’s and the delegate’s signatures must be identical. This is not true! Only the last sentence fully tells the truth: the handler’s delegate signature must be capable to receive all parameters that were passed in during event invocation. This is nothing really new. We all have seen parameterization errors stemming from method calls in cases a caller tried to pass more parameters than there were implemented. On the other hand, calling any method passing less parameters than the implementation has, does work like a charm, at least the invocation. The same is true while raising an event. Under the hood, VFP does not draw a distinction between event methods and other methods when it comes to parameter passing. Thus, it is absolutely legal to create a set of generic event handler methods that can be bound to any (generic) event method! Keep in mind: VFP’s maximum number of parameters that can be interchanged during all kind of calls is #26, which is more than enough, IMHO. This capacity also is the upper limit of VFP’s RAISEEVENT()command. If you really need more, switch to a parameter object!
A “Generic” Event
An event cannot be generic in the truest sense of the word! Only the event method signature can be generic, in that the method accepts all possible 26 input parameters!
Let’s create a form and add an event method with such generic signature. Next, we will add some event behavior (just for initial testing). The final result looks like so:
Lets DO the form and invoke the EvtSendOut() method using a regular method call from VFP’s command window:
m.testform2.EvtSendOut("What","a","difference","a","day","makes!")
The result, as expected, is shown below:
Now, raise the EvtSendOut() method as a custom event typing the following line of code into VFP’s command window:
RAISEEVENT(m.testform2, "Evtsendout", "What","a","difference","a","day","makes!")
Again, the result is the same, as shown below:
You may play with different parameterizations of the RAISEEVENT() command. As long as you do not try to pass more than 26 parameters, all invocations will be executed successfully.
Let’s prove that we cannot raise a custom event passing in a parameter by reference. With the test form still running type the following code lines into VFP’s command window and execute them one by one:
x = "Hallo" m.testform2.Evtsendout(x) m.testform2.Evtsendout(@x) RAISEEVENT(m.testform2, "Evtsendout",x) RAISEEVENT(m.testform2, "Evtsendout",@x)
Only the last command will throw an exception, like shown below:
It is the “Missing operand” error #1231. Well, “Missing operand” sounds strange indeed, but there is an error – as I promised :-)
A “Generic” Delegate
What’s true for the event is true for the delegate, too: A delegate cannot be generic in the truest sense of the word! Only the delegate method signature can be generic, in that the method accepts all possible 26 input parameters! Let’s complete this first test-drive. Initially, we filled our event (method) with code that would be better placed in a delegate (method). Let’s add such a delegate to our form and copy the whole implementation to the new delegate. Keep only the LPARAMETERS statement in the event. The intermediate result should look like this:
Again, we will use VFP’s command window for manually binding and triggering. Save the form and create two named instances like so:
DO Form c:\<your path>\testform2.scx NAME Sender DO Form c:\<your path>\testform2.scx NAME Receiver m.Sender.Caption = "Sender" m.receiver.Caption = "Receiver"
Now it is time to link the receiver to the sender using an appropriate BINDEVENT() command. Our first binding does not use a special nFlags value. VFP defaults to nFlags = 0 in that case.
? BINDEVENT(m.sender, "EvtSendOut", m.Receiver, "OnSendOut")
Now, trigger the event on the sender form like so:
RAISEEVENT(m.sender, "Evtsendout", "What","a","difference","a","day","makes!")
The delegate code of the receiver form executes and echoes the text out on the currently activated form:
Changing the parameter count (within the valid boundaries) executes just fine! Try the following, if you like:
RAISEEVENT(m.sender, "Evtsendout", "What","a","difference","a","day","makes!", "Come", "on", "Baby!")
Keep both forms open and read ahead – we will reuse them in the next chapter.
Benefits of Using Generic Signatures
Using generic signatures with your events and delegates frees you from keeping track of parameter count changes, which otherwise must be kept in sync over three different places (event, delegate and trigger) if you raise the parameter count used by the trigger. This does not mean that you must not keep your implementations in sync with the parameters coming in :-) Keep in mind: Always placing 26 LPARAMETERS in your event-related methods slows down program execution slightly, because VFP has to create 26 local variables each time the methods must be invoked. I’m using my own special breed of generic method signatures within my frameworks and never encountered any performance hits, because I reduced the parameter count of my framework class methods down to three. The first parameter is the name of the method/service, the second is a parameter object which may take as many parameter properties as needed, the last parameter is the object reference of the sender.
Implementing a Custom Event-Handler
We have seen that calling a method must not differ from raising it, seen from the view-point of someone who triggers the execution in one way or the other. A noticeable difference may only appear for the event handler side of the binding. Using nFlags values of 2 and 3 in the BINDEVENT() command only invoke the event handler’s delegate when the event was triggered using RAISEEVENT(). Using nFlags values of 0 and 1 in the BINDEVENT() command additionally also invoke the event handler’s delegate when the event was triggered using a regular method call. Check it out! You can do that with only two lines of code if the above 2-forms test environment is still set and running:
UNBINDEVENTS(m.sender) BINDEVENT(m.sender, "EvtSendOut", m.Receiver, "OnSendOut", 2)
With the nFlags value set to 2, try to invocate the event handler’s delegate through triggering the event using a simple method call like so:
m.sender.EvtSendOut("EvtSendOut", "What","a","difference","a","day","makes!", "Come", "on", "Baby!")
Nothing happens – as I told you!
After all, IMHO, only BINDEVENT() commands in code using nFlags values of 2 or 3 should be legitimate for today’s event binding implementations, which must not mean that there are no good reasons for using nFlags values of 0 and 1. If one has to bind to legacy methods that were realized a long time ago (when event binding still was a foreign word for VFP developers), one is forced to use nFlags values of 0 or 1 while binding to methods that never will be raised but always gets called (the old-fashioned way). In addition to that, there are some native VFP “events” (like GotFocus(), LostFocus(), InteractiveChange(), and ProgrammaticChange()) that are treated as method calls internally by VFP, so that we are forced to use a nFlags value of either 0 or 1. My suggestion always is: NEVER bind to VFP’s native properties and methods/events directly, but implement your own custom event system/scheme. I will show you my own (more sophisticated:-) implementation in a future posting…
The table below shows all four possible values (bit flag values) counting the bits starting with #0. The “trigger all” row represents both bit#0 states, the “trigger only raised” row shows the two bit#1 states. Both bits are OR-combined. The four resulting values are printed in bold within the matrix. Just to make this part of my writing complete: Bit #3 of the nFlags parameter is used to prevent recursion in case of Window Message Binding (which isn’t discussed in this posting).
nFlags values |
invoke delegate
|
invoke delegate
|
Bit #0 >> trigger all |
0 |
1 |
bit #1 >> trigger only raised |
2 |
3 |
Seen from my personal point of view (as a framework developer), there are many different objects at runtime lurking around to bind to my events. I divide these event handler objects into two groups: one group want to process parameters BEFORE my own implementation processes them, the other (lazy:-) group can wait until my event code is finished, receiving the parameters AFTER my implementation returns.
Sidekick: Property-based Event Binding Part II
Binding to properties makes it even easier to understand what’s going on under the hood: Those delegates that were bound to a scalar property with nFlags values of 0 or 2 can only show the old property value (before the assignment takes place), delegates that were bound to the property with nFlags values of 1 or 3 will always show the new property value (the “after-assignment-value”). The following listings show us two generic property-delegates. You may add them to our form before test-drive them. Bind both delegates to a property like shown below. BTW, the method signature of a regular property-delegate always is empty because we do not need any parameters, as long as we are solely assigning new values. I underlined the word “need” bold italic because it is important to stress this for a good reason:
we do not need any parameters, but it is not forbidden to implement some, as I will show you later in this chapter!
This is the test code we will run next:
ADDPROPERTY(_SCREEN, "nTest") DO Form c:\<your path>\testform2.scx BINDEVENT(_Screen, "nTest", m.testform2, "OnPropertyBefore") BINDEVENT(_Screen, "nTest", m.testform2, "OnPropertyAfter", 1) _Screen.nTest = 1 RAISEEVENT(_SCREEN, "nTest")
You may also want to implement some ”laBindings[3]“– test code like the following two listings show:
PROCEDURE OnPropertyAfter() AS VOID *\\ a generic property-event delegate LOCAL ARRAY laBindings[3] AS Variant LOCAL lnCount AS Integer *\\ AEVENTS( ) returns a three-element array containing *\\ an object reference to the current event source, *\\ the name of the triggered event, *\\ and how the event was triggered. 1 - RAISEEVENT() *\\ 2 - Method call lnCount = AEVENTS(laBindings, 0) IF m.lnCount > 0 LOCAL eValue AS Variant WITH laBindings[1] eValue = EVALUATE("." + laBindings[2]) ENDWITH ? "New value is " + TRANSFORM(m.eValue) ENDIF ENDPROC
PROCEDURE OnPropertyBefore() AS VOID *\\ a generic property-event delegate LOCAL ARRAY laBindings[3] AS Variant LOCAL lnCount AS Integer *\\ AEVENTS( ) returns a three-element array containing *\\ an object reference to the current event source, *\\ the name of the triggered event, *\\ and how the event was triggered. 1 - RAISEEVENT() *\\ 2 - Method call lnCount = AEVENTS(laBindings, 0) IF m.lnCount > 0 LOCAL eValue AS Variant WITH laBindings[1] eValue = EVALUATE("." + laBindings[2]) ENDWITH ? "Value assignment for " + laBindings[2] + " triggered!" ? "Old value is " + TRANSFORM(m.eValue) ENDIF ENDPROC
VFP returns a “trigger type”-value (0-System, 1-RaiseEvent, 2-MethodCall) in the third array element of the local array “laBindings”. Okay, you may think, all this is pretty straight forward and matches VFP’s help documentation one by one. But, this is not true! There are some important points: 1st, VFP’s event binding related help is not correct in some places and 2nd, some helpful things are not mentioned at all.
1a.) You can read about the AEVENTS(arrayname,0) version in VFP’s help file: “The third array element indicates how an event was triggered. If the event is a property, this value can be 1 or 2. The value is 2 if the property is set or assigned.” Simply, this is not true! In our above example laBindings[3] never will hold the value 2 (two). During my testing sometimes I got a zero or the value one but never a two! Thus, the correct statement must be: “The third array element indicates how an event was triggered. If the event is a property, this value can be 0 or 1. The value is 1 (= RAISEEVENT()) if the delegate was triggered using the RAISEEVENT() function! In all other cases the third array element is 0 (= System).”
1b.) Assigning a value to any scalar property with bound delegates always fires all delegates! In other words: You may bind to a property with a nFlags-value of 2 or 3 which would suppress delegate execution in case of a simple method call when applied to a VFP method. Later, when assigning a value to the property, you might expect that the delegates will not be called. Sorry, but that doesn’t work for scalar values! This leads to the next oddity:
1c.) Assigning a value to any non-scalar property (to an array property) just works as expected – well, almost! Instead: An array value assignments do not trigger bound delegates at all! At least: doing a RAISEEVENT() on an array-property fires ALL bound delegates just like described in 1b.) above!
Let’s create some examples to make things a little bit clearer:
Given one source- and one target object named “oSource” as the event source object and “oHandler” as the event handler object, let us define two properties on the source object: “oSource.cProp” and “oSource.aProp[1]”, so that we have one scalar and one non-scalar property at our disposal. Now, let’s add two _Assign() methods for both properties: “oSource.cProp_Assign()” and “oSource.aProp_Assign()”. Finally, let us add four delegate methods called “delegate0()” to “delegate3()” to our handler object.
Now, we can run code like this from VFP’s command window:
oSource = NEWOBJECT("cSource", "testlib.vcx") oTarget = NEWOBJECT("cTarget" , "testlib.vcx") *\\ bind to the scalar property BINDEVENT(oSource, "cProp", oTarget, "Delegate0", 0) BINDEVENT(oSource, "cProp", oTarget, "Delegate1", 1) BINDEVENT(oSource, "cProp", oTarget, "Delegate2", 2) BINDEVENT(oSource, "cProp", oTarget, "Delegate3", 3) *\\ now bind to the non-scalar property BINDEVENT(oSource, "aProp", oTarget, "Delegate0", 0) BINDEVENT(oSource, "aProp", oTarget, "Delegate1", 1) BINDEVENT(oSource, "aProp", oTarget, "Delegate2", 2) BINDEVENT(oSource, "aProp", oTarget, "Delegate3", 3)
The first version of our delegate code is almost identical in all event handler methods . Is echoes out the three AEVENTS(array,0) array values.
LOCAL ARRAY laEvt[3] IF AEVENTS(laEvt,0) > 0 ? "Handler 0 ---------------------" ? "laEvt[1] = " + TRANSFORM(laEvt[1]) ? "laEvt[2] = " + TRANSFORM(laEvt[2]) ? "laEvt[3] = " + TRANSFORM(laEvt[3]) ENDIF
You only have to vary the handler number. Replace “Handler 0 ----“ with “Handler 1 ----“, “Handler 2 ----“, and “Handler 3 ----“ in the four different delegates.
Now, add some echo statements to the cProp_Assign() hook like:
LPARAMETERS vNewVal
? "cProp_Assign() before assignment - value is '" + TRANSFORM(THIS.cprop) + "'"
THIS.cprop = m.vNewVal
? "cProp_Assign() after assignment - value is '" + TRANSFORM(m.vNewVal) + "'"
and for the aProp_Assign() hook and like:
LPARAMETERS vNewVal, m.nIndex1 ? "aProp_Assign() before assignment" IF ISNULL(m.nIndex1) && user didn't pass in a subscript ?? " - value is '" + TRANSFORM(THIS.aprop) + "'" THIS.aprop = m.vNewVal ELSE ?? " - value is '" + TRANSFORM(THIS.aprop[m.nIndex1]) + "'" THIS.aprop[m.nIndex1] = m.vNewVal ENDIF ? "aProp_Assign() after assignment - value is '" + TRANSFORM(m.vNewVal) + "'"
Now we can assign a value e.g. to the scalar cProp property: oSource.cProp = "Hello World!" and watch the sequence that gets echoed out to VFP’s desktop:
The first line is generated by the first ? "cProp_Assign() before assignment - value is '" + TRANSFORM(THIS.cprop) + "'" expression.
During execution of the second statement: THIS.cprop = m.vNewVal all four event handlers are raised, which generate the “Handler x ------------------------------“ … output lines, one after the other.
Finally, the second ? "cProp_Assign() after assignment - value is '" + TRANSFORM(m.vNewVal) + "'" expression is executed.
As you can see, the handlers #1 and #3 are definitely firing AFTER the value assignment, whereas handlers #0 and #2 only can show the initial empty string value. Anyway, all four handlers fired!
Now let’s have a short look what happens if we assign a value to the (non-scalar) array property like so: oSource.aProp = "Hello World!" and watch the sequence that gets echoed out to VFP’s desktop:
As you can see, no event handler gets called at all!
1d.) RAISING a scalar property like so: RAISEEVENT( oSource, "cProp") does NOT trigger an existing _Assign() method, because internally VFP does not execute a real self-assignment, but only invokes all bound delegates. This produces the following output:
The output shown above is the same when we are raising our non-scalar array property! Thus, we can state that VFP’s property-raising behavior is consistent for both property types (scalar and non-scalar ones)!
2a.) VFP’s help file is missing some interesting things that should also be known! What’s about resetting a property to the class-default using VFP’s ResetToDefault() method?
This is what will happen, if you issue the following commands:
oSource.ResetToDefault("aProp") or
oSource.ResetToDefault("cProp")
They yield in the following output ( I added this: ? 'ResetToDefault("' + m.cProperty + '")' to the form’s ResetToDefault() method):
When resetting the form’s non-scalar .aProp[] property only a single
is echoed out. The two most interesting parts to remember are:
1st) Resetting a (non-scalar) array property neither raises an event handler nor does it call an existing _ASSIGN() method!
2nd) On the other hand, resetting a scalar property does it all, but with one interesting difference: all event handlers, as well as the _Assign() method call itself are invoked AFTER VFP’s internal ”resetting process” has finished work! You can see above that the property value already is empty when the Assign() method call (followed by the event handler invocations) takes place. What exactly is going on while resetting an array property cannot be monitored due to the 1st limitation.
3rd) One final thing to mention is, that trying to reset an array property back to it’s class default using code like: .ResetToDefault("array_property_name") is useless! Neither a changed array dimension will be restored, nor will the remaining array elements be set to .F. again. This is the default behavior documented in VFP’s help file.
The following section is for all VFP PROs (who think they know all and everything ;-)
2b.)
We can bind any event handler to a property. Better said, we can parameterize a property-event handler without limitation (okay, within VFP’s maximum parameter count limit). The best is jet to come: We can raise a property with as much parameters as we want (within VFP’s maximum parameter count limit), as long our delegate is able to receive/handle the parameter values! You are not that exited? Wait, better think twice!VFP’s AEVENTS() function is a nice to have thing, when we need to find out what the source of the event was. Creating a local 3-element array that gets filled with the appropriate information first, sometimes sounds like creating to much overhead. At least in tight loops raising slow delegates very often can become a speed-bottleneck at runtime. Naturally, after having mentioned that we can bind any kind of parameterized delegate to a property, it is easy to guess, what comes next: Let us use parameterized delegates on properties!
The diagram below seems a little bit confusing at first sight. Thus, let me explain what it should depict. To keep things as simple as possible at the beginning there are 4 object in our example: one form (on the GUI layer) with two interface objects (at the wiring layer) and one manager/controller object (in the business/system layer).
A reference of each form interface object will be stored in the form (the thick black lines). The form’s Resize() and Moved() events may implement trigger code like the following (a reference to the “Position&Size” interface object is stored in the form’s “oInterfacePosAndSize” property:
*\\ Trigger code in the form's MOVE() event RAISEEVENT( THIS.oInterfacePosAndSize,; "Position", ; THIS, ; THIS.Left, ; THIS.Top, ; THIS.Width, ; THIS.Height )
and
*\\ Trigger code in the form's RESIZE() event RAISEEVENT( THIS.oInterfacePosAndSize,; "Size", ; THIS, ; THIS.Left, ; THIS.Top, ; THIS.Width, ; THIS.Height )
The cool thing about these constructs is that they run without errors even if there is no delegate attached/bound. In other words: we can set up a series of EMPTY-based interface objects like shown above, link them to their implementation instances and bind triggers and delegates to their properties. You can move such a form and resize it as you like, there will be no delay. The form instance behaves absolutely “normal”! Firing triggers that raise events on unbound properties is a totally transparent process.
Now, what’s next? Okay, our form layout controller instance is very interested in what users are doing with our forms. But, instead of holding/storing a form reference (working directly with the public interface of each form), the form layout controller only binds his delegates and triggers to the properties “Position” and “Size” of the form’s interface object. The signatures of the delegates are shown below:
*\\ Handler signature in the form layout controller's OnPosChange() delegate LPARAMETERS oSender AS Object, ; tnLeft AS Integer, ; tnTop AS Integer, ; tnWidth AS Integer, ; tnHeight AS Integer
and
*\\ Handler signature in the form layout controller's OnSizeChange() delegate LPARAMETERS oSender AS Object, ; tnLeft AS Integer, ; tnTop AS Integer, ; tnWidth AS Integer, ; tnHeight AS Integer
Now, moving or resizing the form will cause invocation of the bound delegates of the layout controller. Of course, you have to implement something useful there. I leave that to your imagination. One thing should also be mentioned: Using parameterized event raising on properties passing a self reference as one of the parameters frees the event handler from using VFP’s AEVENTS() function to retrieve such a sender reference!
Additionally , you may want to shorten the signature of your “raise-events”. Two parameters are enough: the sender reference and a parameter object. This is just the way most of .NET’s event binding works.
What we have now is a ultra-lightweight EMPTY object containing as much scalar properties as we need. We only need to know the name of the property, because the property only has to exist – the property value doesn’t matter, it is irrelevant. The property can be seen as some kind of binding-handle: a unique address we can bind our handlers to and raise our events. Our delegate implementations should test then for a parameter count > 0 to reassure the property-event was RAISED and not triggered by a simple value assignment, like so:
*\\ Handler code in the form layout controller's OnSizeChange() delegate LPARAMETERS oSender AS Object, ; tnLeft AS Integer, ; tnTop AS Integer, ; tnWidth AS Integer, ; tnHeight AS Integer *\\ only process invocations stemming from a RAISEEVENT() IF PCOUNT() = 0 RETURN ENDIF *//
More benefits
The technique I presented here is based on using EMPTY-based objects to setup some kind of generic event binding. Sure, you can use other “full-blown” VFP classes instead to add more functionality to your interface objects. In my own FoxQuill framework I call these extended interfaces “smart interfaces”, because they are able to host contracts to validate incoming, as well as outgoing parameter values. This is my way to completely separate inter-object messaging/communication from function implementation.
Passing ultra-lightweight interface objects around saves you from spreading your real object references all over the place. Dangling references are almost unknown to my personal framework, since I’m using these binding-proxy objects. Another benefit is: now we can split our objects’ interfaces and group them on a logical basis: one set representing queries, another set represents commands, and two others hold events and exceptions.
Finally imagine: you will be able to pass all those interfaces into an environment (I call mine “The Matrix”:-) where some generic (data-driven) mechanism links logical children to their parents and/or service-providers to their service-subscribers. That way, all your (implementing) objects can be stored in one place (I call mine “The Construct”:-) and the inter-object communication system can be set up (even dynamically at runtime). Implementing security related message redirections or adding some extensions to an existing workflow has never been easier to realize!
Protect Your Events
Let’s assume there is a password dialog object asking your end-user to enter her name and password in two textboxes. If the password dialog class is one of your framework goodies, maybe your way of returning the user name and password is based on event binding. Thus, your dialog class may have an event called Logon(). You are raising the Logon() event when the end-user clicks the login-button on your dialog, passing an EMPTY-based object containing to fields: UserName and UserPass. Let’s further assume that password encryption and verification is part of another business-layer and not implemented in your GUI (bravo!). Then, there is a short timeframe, since you’ve raised Logon(), where the password exists unprotected in a data capsule that may be intercepted by anyone who had access to your login dialog before! Shure, there are better ways to pass sensitive data in such a case, but this is only an example!
What Do We Need
We need a little VFP project we can use for our experiments. Let’s set up a tiny login dialog class and a second class representing the junior version of a Password Manager just to have two instances between which we can establish our binding.
Finally, let’s build a spy class that tries to monitor the login dialog’s Logon() event to intercept and catch the user’s name/password combination.
Securing Events
Back to the golden thread! Let’s use the form we already created for our first test-drive. I saved it under a new name “LOGIN.SCX”. Below there are two screenshots showing the login form’s GUI layout in development mode and at runtime:
Clicking the [Submit (ENTER)]-button executes the following code:
During form’s initialization I added a data object like so:
Pressing ESC or clicking the [Abort (ESC)]-button executes the following code:
My event method .EvtSendOut() stays untouched. I added a new .Trigger() method from where .EvtSendOut() is raised or called, which depends on the 3rd parameter that can be passed into my .Trigger()method: 1 – RAISEEVENT() | 2 – Method call. The 4th parameter of my .Trigger()method passes security-related information. I decided to decode the security test type numbering so that it is easy maps to VFP’s nFlags values. The table below tells you about the integer mapping:
value | description |
0 | no security testing at all (just pull the trigger :-) |
1 | unbind all event handlers that are using nFlags = 0 for their delegate bindings |
2 | unbind all event handlers that are using nFlags = 1 for their delegate bindings |
4 | unbind all event handlers that are using nFlags = 2 for their delegate bindings |
8 | unbind all event handlers that are using nFlags = 3 for their delegate bindings |
16 | repeat check (to protect against timer-driven spy-ware) |
Needless to say, the bit values from above can be combined to “filter out” more than one group. If you want to block all delegates bound with nFlags values of 0, 1, and 2, just pass a seven (7) to the .Trigger()method. Naturally, there is no native VFP way to filter out established bindings. Thus, we have to get rid of the unwanted bindings using old-fashioned “hammer-style”: Just Unbind Them :-) Of course, good spy-ware would try to re-establish it’s “wiretapping”. The only way to achieve that successfully, is using a timer. Therefore, I added the option (add +16 to the 4th parameter) to set a marker flagging that the security test is no longer optional!
The screenshot above shows my test environment with actually 4 running spy instances. Each spy has bound itself to the login form (that is the caption- and borderless child window inside the Password Manager) with a different nFlags value (to proof that my protection scheme really works as expected) My login form’s trigger method was called from the [Submit]-button allowing only delegates bound with nFlags = 3 to stay linked, because (as I mentioned before) the guys with nFlags 0 or 1 are no real event handlers today (IMHO)! The rest of the guys has to wait, until I finish processing – thus, they must bind with AFTER INVOCATION := 3! This gives me room to process the unprotected login data (you can see it below: the new caption of my Password Manager form displays it). Naturally, before returning from my password manager, I changed the originally entered name and password to some less valuable text ;-) which was captured by spy #4 (numbered from top to bottom on the screen shot). Have a good time with that, SPY#4!
Where is the Beef?
<<< Here it is! Use this download link to get all sources to play with…
Final Thoughts
Native event binding is one of the most useful features that were added to VFP over the time! To be honest, I started developing my own VFP framework not until event binding was introduced! Even though there are many little bumps on the floor (especially, when applying event binding to VFP’s native properties), up to now, I never found another development environment that gave me more freedom to create extraordinary specialties with ease!
HAPPY BIRTHDAY BURKHARD!
ReplyDeletelg nat
Hey, Burkhard, long time I haven't seen a post of yours!
ReplyDeleteHow are you doing?
Best regards,
A reader.-
@Nat - Many thanx! How time flies:-)
ReplyDelete@Fernando - spend a lot of time on further education and internet programming. Since March 2012 I'm finally back on (my) VFP9 track. Thus, be prepared to see some new and extended publications here in the near future.
All the best!
This is an absolutely fantastic post. Invaluable; thank you.
ReplyDeleteAbsolutely fantastic. This is my go-to resource for any and all event-related questions. Fabulous work.
ReplyDeleteError instantiating class. Cannot find d:\download\cloneobjects\foxcloneapp.vcx.
ReplyDelete@Vilciu Constantin: Hi! Thank you for your hint! You are right, there was an unresolved reference to another form baseclass written into my testform.scx accidentally. I corrected that (and added another missing include file at the same time:-)
ReplyDeleteThanx for viewing my files!
Hey, Burkhard, how are you?
ReplyDeleteI've just read on your blog that now you have a new full VFP job, nice news! Congratulations!
In the case you work with SourceControl I want to share something with you, my new Open Source project at VFPX that you may find useful:
https://vfpx.codeplex.com/wikipage?title=FoxBin2prg
Hope to read you!
Best regards.-
ReplyDeleteThe best reference on the net for this subject. Thanks.!