Live Debugging in VFP
De standaard manier voor een VFP ontwikkelaar om in de code te kunnen stoppen is door gebruik te maken van het SET STEP ON commando. Deze toont het Trace-window, waar de ontwikkelaar de volgende regels van de code stap voor stap kan doorlopen om te kijken waar het probleem ligt.
Een andere truc in het arsenaal van de ontwikkelaar is het gebruik van breakpoints. Deze zijn in potentie krachtiger, aangezien je conditioneel kunt aangeven wanneer er gestopt moet worden met de programma-uitvoer. Het vereist echter wel dat de debugger actief is, wat niet altijd even prettig werkt.
In dit artikel geef ik een extra alternatief: ‘Run-time debugging’.
SET STEP ON
Iedere VFP ontwikkelaar gebruikt bovenstaand commando regelmatig om zo vanaf een specifiek punt de programma-uitvoer te stoppen. Dit zal gebeuren zodra het programma dit commando ten uitvoer brengt. Op het moment dat het commando wordt gestart, toont VFP het trace-window met een pijl die de huidige regel aangeeft. Met behulp van de diverse mogelijkheden in het trace-window kun je vervolgens stap voor stap je programma doorlopen of bepaalde regels overslaan, om zo te bepalen waar het probleem zich bevindt. Het probleem hierin is dat je, op het moment dat je een probleem heb geconstateerd, eerst je source-code moet openen, daar een ‘set step on’ aanroep moet plaatsen, en het programma opnieuw starten om zo de programma uitvoer op het gewenste moment te kunnen stoppen.
BREAKPOINT

Fig. 1: Plaatsen van een breakpoint
De standaard manier waarop een breakpoint wordt gebruikt is door in de sourcecode te dubbelklikken op de grijze marge voor de coderegel. Er wordt op dat moment een rode stip zichtbaar. Dit betekent dat er op dat punt in de code een breakpoint gezet is. Als je vervolgens kijkt in de breakpoint-dialoog zie je dat de regel waar je net een breakpoint hebt gezet, nu is toegevoegd aan de lijst van breakpoints. Je kunt in deze dialoog meer dan alleen de breakpoints bekijken; je kunt namelijk ook breakpoints aanpassen en toevoegen. Met andere woorden: terwijl je een programma aan het testen bent, kun je een breakpoint toevoegen op een regel in dat programma om zo de programma-uitvoer te pauzeren.
Verder kun je in de breakpoint-dialoog eventuele voorwaarden invoeren waarbij een breakpoint daadwerkelijk moet pauzeren. Verder is een voor mij favoriete optie de checkbox ‘Display breakpoint messages’. Deze zorgt ervoor dat als je een breakpoint op een variabele hebt gezet, niet meer de melding dat de waarde is gewijzigd gegeven wordt. Er wordt gewoon gepauzeerd.

Fig. 2: Breakpoint-dialoog
Het gebruik van het breakpoint-dialoog is al een verbetering ten opzichte van het openen van de sourcecode om een SET STEP ON te plaatsen, maar het instellen van een breakpoint is nog steeds erg bewerkelijk, aangezien je de bestandsnaam en het regelnummer (en eventueel object-referentie) moet aangeven waar het breakpoint moet staan. Verder zal de breakpoint ook alleen de programma-uitvoer pauzeren als de debugger actief is.
Dit delegeren van de afhandeling gebeurt ‘at run time’, met andere woorden, als het object al actief is
Nieuw in VFP8: BINDEVENT()
Sinds VFP8 hebben ontwikkelaars de mogelijkheid om als een event van een specifiek object wordt aangeroepen/afgevuurd, de afhandeling te laten plaatsvinden in een ander event, de zogenaamde ‘delegate’. Dit delegeren van de afhandeling gebeurt ‘at run time’, met andere woorden, als het object al actief is. Daarbij dient wel gezegd te worden dat de orginele broncode die eventueel in het afgevuurde event staat, wel wordt uitgevoerd. Afhankelijk van enkele parameters die je meegeeft bij het koppelen van het source-event en de delegate, kun je bepalen of het orginele event eerder of later wordt uitgevoerd dan de delegate.
Dit delegeren kun je bereiken door de BINDEVENT() functie. De functie heeft een vijftal
parameters, waarvan de vijfde optioneel is. In die parameters geef je aan welk event van welk object wordt gedelegeerd en waaraan.
Een voorbeeld waarbij het _Screen.Click event wordt gedelegeerd aan een custom object:
* Bindevents example
*- Creeer de click-handler
PUBLIC oClickHandler
oClickHandler = CREATEOBJECT("oHandleClick")
*- Delegeer _Screen.Click aan de oClickHandler.OnClick
BINDEVENT(_Screen, "Click", oClickHandler, "OnClick")
*- Definitie van de ClickHandler class
DEFINE CLASS oHandleClick AS Custom
PROCEDURE OnClick
*- Roep de AEVENT() aan.
*- Deze geeft bron aan waar event afgevuurd werd
IF AEVENT(laEvent, 0) > 0
?laEvent[1].Caption, "Clicked!"
ENDIF
ENDPROC
ENDDEFINE
Listing 1: Gebruik van BINDEVENT()
Hoewel het voorbeeld niet echt spannend is, levert de mogelijkheid om dynamisch methodes uit te laten voeren bij het afvuren van events ongekende mogelijkheden op. Ik noem als voorbeeld: een handler-object dat reageert op het click-event van een column-header en het sorteren op die kolom voor zijn rekening neemt, en dit zonder dat er code in de header-objecten hoeft te staan. Overigens reageert de delegate niet alleen op het afvuren van een event of methode, maar kun je de delegate ook koppelen aan een property, zodat deze ook wordt gestart bij het wijzigen van de waarde van die property.
Het mooie van BINDEVENT() is dat deze kan delegeren aan willekeurig welk VFP-object
Het mooie van BINDEVENT() is dat deze kan delegeren aan willekeurig welk VFP-object. Dit kan ieder object zijn dat ergens in VFP beschikbaar is. Deze mogelijkheid gebruik ik om te debuggen, want ik kan een object dat al actief is, koppelen aan een methode waar een SET STEP ON in staat. Op dat punt wordt dan de programma-executie gepauzeerd, en kan de ontwikkelaar met een ‘Step out’ belanden op de eerste regel van het gestarte event. Om dit eenvoudig en begrijpelijk voor elkaar te krijgen is een aantal dingen nodig:
- Een mogelijkheid om een actief object te selecteren.
- Een interface om een event/methode of property van het actieve object te selecteren.
- Een delegate-object waar de SET STEP ON wordt aangeroepen.
1. Selectie van een actief object.
Het selecteren van een actief object is eenvoudig, mits het gaat om een visueel zichtbaar object. Een aanroep van SYS(1270) geeft het object dat op dat moment onder de muiscursor aanwezig is. Koppel dat met een ON KEY LABEL LEFTMOUSE en we kunnen een object selecteren en meteen een objectreferentie daarvan bemachtigen.
Codeline1;
_Screen.AddObject("oDebugClickHandler",;
"cusDebugClickHandler")
WAIT WINDOW NOWAIT NOCLEAR;
"Click on the object you want to debug"
ON KEY LABEL LEFTMOUSE;
_Screen.oDebugClickHandler.ObjectClick(SYS(1270))
ON KEY LABEL RIGHTMOUSE;
_Screen.oDebugClickHandler.Cancel()
Listing 2: Selectie van een bestaand object.
Hier voeg ik een object genaamd oDebugClickHandler aan het _Screen object toe. Deze zal een objectreferentie doorgestuurd krijgen bij de aanroep van de ObjectClick() methode. De Cancel() methode wordt aangeroepen als rechtermuisknop wordt ingedrukt, zodat de ON KEY LABEL instellingen voor de linker- en rechtermuisknop ongedaan worden gemaakt.
2. Selecteren van methode, event of property van het object
Hiervoor maken we gebruik van de AMEMBERS() functie. Deze geeft ons inzicht in alle properties, methodes en events die voor een object beschikbaar zijn. Als deze in een overzichtelijk interface getoond worden, kunnen we daar een selectie maken welke methode we willen debuggen. Een voorbeeld van dat interface is als volgt:

Fig. 3: Selectie-interface
Binnen dit interface heb ik de volgende dingen verwerkt:
- Een filter-mogelijkheid om snel alleen de properties, de methodes, de events, of alleen de object-properties te tonen;
- Kleurcodering afhankelijk van het type;
- Mogelijkheid om meerdere onderdelen te selecteren;
- Mogelijkheid om een property van een andere waarde te voorzien;
- Mogelijkheid om een sub-object te selecteren om daar de properties, events en methodes van te selecteren;
- Mogelijkheid om van hieruit een methode of event te starten.
Ik zie ook nog mogelijkheden tot verdere uitbreiding. Er is nu namelijk nog geen mogelijkheid om onderdelen van eventuele collectie-objecten zoals de forms-collectie te selecteren. Ook een selectie van eventueel beschikbare public object-variabelen zou een goede uitbreiding zijn, om zo ook niet visueel beschikbare objecten te kunnen selecteren.
Wat je hier ziet is een cursor die opgebouwd wordt vanuit het resultaat van de AMEMBERS aanroep, waarbij een selectieveld is toegevoegd. Deze valt te activeren middels een checkbox in de getoonde grid. Als de gebruiker vervolgens het scherm sluit, zullen de geselecteerde methodes/properties worden gekoppeld aan een debug-object door een BINDEVENT()
3. Een delegate-object.
DEFINE CLASS cusdebugaction AS custom
PROCEDURE ondebug
LPARAMETERS txParam1, txParam2, txParam3, ;
txParam4, txParam5, txParam6, txParam7
LOCAL lcClass, lcMethod, lcMessage, lnCount, lxParam
LOCAL laEvent[1]
*- haal een object referentie naar
*- aanroepende object op
=AEVENTS(laEvent, 0)
IF VARTYPE(laEvent[1]) = "O"
*- Maak een bericht voor in de messagebox
lcClass = laEvent[1].Class
lcMethod = laEvent[2]
lcMessage = "Debugging "+lcClass+"."+lcMethod
IF PCOUNT() > 0
lcMessage = lcMessage + CHR(13) + CHR(10) +;
"Passed parameters:"
FOR lnCount = 1 TO PCOUNT()
lxParam = ;
EVALUATE("txParam" + TRANSFORM(lnCount))
lcMessage = lcMessage + CHR(13) + CHR(10) +;
" - Parameter " + TRANSFORM(lnCount) +;
" : " + TRANSFORM(lxParam) + "{" +;
VARTYPE(lxParam ) + "}"
NEXT
ENDIF && PCOUNT() > 0
*- Geef gebruiker de keus of-ie wil debuggen
IF MESSAGEBOX(lcMessage,4,"Debug?", 3000) = 6
PUBLIC glStartDebugging
glStartDebugging = .T.
*- start debuggin’
SET STEP ON
RELEASE glStartDebugging
ENDIF && MESSAGEBOX
ENDIF && VARTYPE(laEvent[1]) = "O"
ENDPROC
ENDDEFINE
Listing 3: Delegate methode voor debugging
Over bovenstaande code is een aantal dingen op te merken. Allereerst wordt door gebruik van de AEVENT() functie een referentie naar het bron-object opgehaald. Op die manier kan er een duidelijker bericht worden opgesteld over welk event werd afgevuurd. Verder wordt de gebruiker dus een vraag gesteld of hij daadwerkelijk wil gaan debuggen, waarbij ook de waardes van de parameters getoond wordt. Over de parameters gesproken: de delegate-methode wordt aangeroepen met alle parameters die ook doorgestuurd werden naar de bron. Als je b.v. koppelt aan het AfterRowColChange-event van een grid, zal de delegate één parameter meekrijgen: nColIndex. Omdat ik van te voren niet weet hoeveel parameters er doorgestuurd zullen gaan worden aan deze debug-methode heb ik in dit geval 7 parameters gedeclareerd. Als er een methode gekoppeld wordt met meer parameters zal er een foutmelding komen: ‘Error 94-Must specify additional parameters.‘
Conclusie
Met behulp van de BINDEVENT() functie hebben we voor het debuggen van een programma in VFP een extra mogelijkheid om te bepalen waar het probleem zit. Het is met niet al te veel code mogelijk om een generiek debugging object te maken dat dynamisch gekoppeld kan worden aan al actieve objecten om zo op een gewenst punt de programma-uitvoer te onderbreken en het probleem in de code te lokaliseren.
Sources
De sources die bij dit artikel horen kunt u downloaden via Wijnker_LiveDebugging_SRC.zip.