Op de SDGN nieuwsgroep voor Visual Objects ontstond laatst een draad van berichten over de nieuwe libraries bij VO-2.6. Één van deze libraries kwam speciaal aan de orde: VO-Script. Dit product heb ik in de afgelopen periode naar volle tevredenheid toegepast in een aantal VO-applicaties. In dit artikel wil ik dan ook ingaan op hoe VO-Script te gebruiken is.
Installatie en werking
Installatie van de library is eenvoudig. Het bestaat uit een Aef bestand dat geïmporteerd kan worden in een project. Dit moet gecompileerd worden en je kunt de library gaan gebruiken. Dat betekent dan ook dat het gebruik van een extra DLL of iets dergelijks verder niet nodig is. Het enige wat je moet doen is de VO-Script library opnemen in je applicatie library list.
De opzet van VO-Script is eenvoudig. Aan de functies van VO-Script wordt een tekst meegegeven waarin een stukje VO programma code is opgenomen. De library heeft een interface die bestaat uit een viertal functies, te weten compilecode, compilescript, executecode en executescript. De script varianten zijn daarbij ook nog eenvoudige uitbreidingen op basis van een memoread waarbij een tekstfile gelezen wordt die vervolgens weer met behulp van de code varianten verder verwerkt wordt.
De compilecode doet iets vreemds. Allereerst wordt de tekststring met de programmacode omgezet naar een array waarin de regels opgenomen worden. Daarin zit dan nog een functie die ervoor zorgt dat selectie en iteratie-functies als IF ENDIF en DO ENDDO worden omgezet naar meerdimensionale arrays. Vervolgens wordt deze array weer omgezet naar een tekststring. Dit is de return waarde van de functie.
De tekststring - noem het gecompileerde code - wordt vervolgens als parameter meegegeven aan de executecode functie. Hierbinnen wordt de string weer omgezet naar een array die regel voor regel als codeblock uitgevoerd wordt. Vandaar dat het omzetten van iteraties en selecties naar een multidimensionaal array verklaarbaar is.
Reden voor het omzetten van programmacode naar een string van omgezette programmaregels is, denk ik, tweeledig. De eerste is dat de programmacode niet meer leesbaar is en dus gebruikt kan worden om scripts versleuteld te gebruiken. Daarnaast heeft de compilecode routine de mogelijkheid om de programmacode op correctheid te controleren, waarbij de executecode sneller kan werken omdat compileren al heeft plaatsgevonden.
In principe is dit alles wat te vertellen is over VO-Script. Er is nog te noemen dat er ook een class variant is voor de scripts. Echter mijn ervaringen met scripting zijn zodanig dat ik steeds meer mogelijkheden zag om scripts te gebruiken om mijn applicaties dynamiek te geven. In de rest van dit artikel wil ik ingaan op deze mogelijkheden. Hierbij worden voorbeelden gebruikt die ik heb toegepast in het ontwikkelen van een CASE tool.
Code genereren
De eerste opzet waarvoor ik VO-Script ben gaan gebruiken, is voor het genereren van programmacode. VO-Script is hiervoor een goed hulpmiddel. De scripts zijn aan te passen zonder dat de toepassing zelf veranderd hoeft te worden. Dit maakt de werking van een CASE tool flexibeler, omdat gebruikers vaak eigen programmeerstandaards (en zelfs ontwikkelomgevingen) hebben. Vanuit de applicatie wordt een script met behulp van de volgende methode aangeroepen:
METHOD ExecuteText( ) CLASS WndScript
LOCAL cCode AS STRING
SELF:Pointer := Pointer{ POINTERHOURGLASS }
cCode := oDCActionMLE:TextValue
cCode:=CompileCode(cCode)
IF !Empty(cCode)
ExecuteCode(cCode, oMainWindow)
ENDIF
SELF:Pointer := Pointer{ POINTERARROW }
Zoals te zien is, is de code rechttoe rechtaan. Er zit één bijzonderheid in: de parameter die meegegeven wordt aan de ExecuteCode functie. De eerste parameter is de gecompileerde code in de vorm van een tekstvariabele. De tweede is een verwijzing naar het shellwindow van de actieve applicatie. Door deze parameter is het mogelijk om gebruik te maken van de GUI objecten die in de applicatie zitten, met name de windows, maar ook het menu van de applicatie. Hierdoor kunnen we vanuit het script windows openen en parametriseren die binnen de applicatie zitten. Zo heb ik in mijn applicatie een aantal generieke windows opgenomen waarmee je uit lijstjes één of meerdere items kunt selecteren, een waarde kunt invullen of een JaNee vraag kunt stellen. In onderstaande afbeelding is te zien hoe een generiek window vanuit het script geopend en geinstantieerd is.

Het bijbehorende VO-script ziet er als volgt uit:
FUNCTION SybaseStatic(oShell)
LOCAL aOptions
LOCAL oWndOptions
aOptions := {}
Aadd(aOptions, "Create Tables ")
Aadd(aOptions, "Create Constraints")
Aadd(aOptions, "Drop Tables")
oWndOptions:=WndMultiSelect{ oShell, aOptions }
oWndOptions:Caption:="Select the actions to execute"
IF oWndOptions:Show() = 0
RETURN
ENDIF
aOptions:=oWndOptions:Result
Het script laat een aantal eigenschappen van VO-script zien. Allereerst zie je dat een script met het woord function begint. Ook andere woorden zoals procedure en method zijn mogelijk. Vervolgens is te zien dat je variabelen kunt declareren. Je kunt de objecten echter niet benoemen met AS. Dat komt doordat het script met behulp van macro’s wordt verwerkt. Macro’s kennen altijd late binding, omdat ze runtime worden uitgevoerd. Dat betekent voor objecten dat er geen problemen zijn. De meeste objecten in VO zijn ook op basis van late binding opgezet om overerving te implementeren. Bij functies kunnen problemen optreden. Functies die strong typed zijn kunnen niet in het script opgenomen worden. Dit vanwege de early binding wat met macro’s dus niet mogelijk is. Er is wel een eenvoudige workaround voor deze functies: neem in je VO-applicatie weak typed functies op als een schil om de strong typed functie met een iets andere naam.
Als laatste is te zien dat de code in VO-script 100% VO is, waarbij het eenvoudig is om gebruik te maken van zelf gedefinieerde objecten.
DbServers openen
Naast de toegang die je hebt tot de windows en menu’s via het shellwindow kun je met VO-Script ook toegang krijgen tot je eigen DbServer objecten: gewoon door ze te declareren als object en te bewerken in je script. In het code voorbeeld een stukje code met mijn eigen dbserver objecten. Hierbij is het belangrijk te weten dat dit objecten zijn waarin mijn eigen overervingsstructuur is opgenomen. Dus ClassServer overerft van AbstractDbServer wat overerft van DbServer etc.
LOCAL oClass // AS ClassServer
LOCAL oTF // AS TextFile
LOCAL cLine // AS STRING
oClass:=ClassServer{}
oTF:=TextFile{}
oTF:Open(DefaultFile("VOCODE.PRG")
oClass:MakeRelations()
oClass:GoTop()
DO WHILE .NOT. oClass:Eof
cLine:= "CLASS " + Alltrim(oClass:ClassName)
…
oClass:PropertyServer:GoTop()
DO WHILE .NOT. oClass:PropertyServer:Eof
…
oTF:WriteLine(cLine)
oClass:PropertyServer:Skip()
ENDDO
oClass:Skip(1)
ENDDO
…
Het voorbeeld laat zien hoe je met een DO-WHILE loop door het class object kunt lopen en vervolgens stukjes code kunt aanmaken die weggeschreven worden naar een textfile object. De navigatie kan gewoon met de standaard methoden zoals skip en gotop. Ook is in het voorbeeld te zien hoe eigen methoden aangeroepen kunnen worden (makerelations).
DbServers aanpassen
Binnen de CASE toepassing die ik ontwikkeld heb, is een routine gemaakt waarbij je bij het openen van de applicatie een directory kunt opgeven waar de repository (een set DBF bestanden) staat. Geef je hier een niet bestaande directory op, dan maakt de applicatie deze directory aan en maakt daarin vervolgens op basis van de interne structuur, zoals deze in mijn DbServer objecten zit, de repository zelf aan. Hiermee is het mogelijk geworden om meerdere repositories naast elkaar te gebruiken. Er ontstond echter al snel behoefte aan extra functionaliteit. Zo is in het ene project behoefte aan extra eigenschappen voor bijvoorbeeld webpagina’s, terwijl in het andere project tijdens het modelleren meer behoefte bestaat aan extra projectmanagement gegevens. Het moest dus mogelijk zijn om extra velden toe te voegen en deze zichtbaar te maken binnen de toepassing.
Nu was het een geluk dat ik de DbServer objecten al gebruikte als container voor allerlei zaken zoals:
· Validatie van invoer
· Het tonen van controls op basis van voorwaarden
· Definiëren van een domein voor velden (voor comboboxen)
· Opbouw van databrowser objecten.
Door gebruik te maken van VO-Script was het mogelijk om deze eigenschappen van mijn DbServer objecten aan te passen, en daarnaast om nieuwe eigenschappen toe te voegen aan het DbServer object. De opzet is eenvoudig: in de postinit methode wordt gecontroleerd of er een tekstbestand is met Server aanpassingen. Is dit het geval, dan wordt het script uitgevoerd, waarbij het DbServer object als parameter wordt meegegeven. Dit script bestaat uit een DO CASE die voor ieder object kijkt of er aanpassingen zijn. Zie als voorbeeld het onderstaande script:
FUNCTION LoadExtraInfo(oServer)
LOCAL cCurrentServer AS STRING
cCurrentServer := oServer:GetClassName()
DO CASE
CASE cCurrentServer ="CLASSSERVER"
oServer:SetDisplayExpression("oS:CLASSNAME")
oServer:SetScriptExpression([SaveTrim(oS:CLASSName)])
oServer:ReturnField := "CLASSNAME"
// nieuw veld definieren
oServer:AddFieldDescription(
“MDW”, “Naam medewerker”,
“Geef de naam van de medewerker”,
“C”, 25, 0)
//verplichte velden instellen
oServer:AddNotNullField("CLASSNAME")
// default waarde instellen
oServer:AddInitVal("CONCRETE", .T.)
// sorteervolgorde instellen
oServer:AddOrderInfo(
"CLASSNAME", "Upper(CLASSNAME)", "Classname")
// domein opgeven: kan zowel statisch als
// een dbserverobject zijn
oServer:AddFieldDomain("INHEFROM", "CLASSSERVER")
oServer:AddFieldDomain(
"LAYER",
{{ "Business domain", "1"},
{"Working Organisation", "2"},
{"Presentation", "3"}})
// instellen databrowser kolommen
oServer:AddBrowserColumn("CLASSNAME")
oServer:AddBrowserColumn("LAYER")
..
Zoals te zien is in de code, is het mogelijk om de belangrijkste zaken voor velden binnen het DbServer object in te stellen en eventueel aan te passen. De opzet is dat er eerst een standaard script wordt uitgevoerd en dat vervolgens het tekstbestand met script wordt verwerkt. Hierdoor kunnen default instellingen eventueel worden overschreven. Op basis van de instellingen zoals hierboven genoemd worden allerhande zaken ingesteld zoals indexen en domeinen, maar ook databrowsers en invoer/bewerk windows. De applicatie is hiermee bijzonder dynamisch geworden.
Aanpassen van tabbladen
In de CASE applicatie worden de gegevens in een overzicht getoond door middel van tabbladen. Voordeel hiervan is dat datawindows op deze tabbladen wel of niet aan elkaar gerelateerd kunnen worden via de setselectiverelation methode van DbServers. Het kan echter handig zijn om in bepaalde overzichten de gerelateerde DbServers wel of niet te tonen. Dit is afhankelijk van het soort project of de fase in het project. In de afbeelding een voorbeeld van de tabbladen opzet

In het codefragment is een deel van het script opgenomen waarmee een tabblad window opgebouwd wordt. Met de methode NewTabPage wordt een server toegevoegd aan de lijst met servers. Een extra optie is om een array van veldnamen mee te geven waarmee het mogelijk wordt de besturingselementen in het detail of invoerscherm te beperken. Hierdoor is het systeem dus nog verder aanpasbaar en kunnen bijvoorbeeld ook zelfgedefinieerde velden aan een detailwindow toegevoegd worden.
FUNCTION DefaultTabPages(oWindow, cClass)
DO CASE
…
CASE cClass = "MSACLASSEVENT"
oWindow:Server:OOMethod:="MSA"
oWindow:Caption := "DLA Classes & Events"
oWindow:NewTabPage(oWindow:Server,
{ "CLASSNAME", "LAYER", "CLASSDES" })
oWindow:NewTabPage(oWindow:Server:EventServer,
{ "EVENTNAME", "LAYER", "EVENTDES" })
…
ENDCASE
oWindow:SelectTab(1)
RETURN NIL
Tot slot
Het werken met VO-Script is eenvoudig in opzet. Met een half uurtje werken is het al mogelijk om een eenvoudig script uit te voeren vanuit een VO-applicatie. Door het toegankelijk maken van het objectmodel van je applicatie in het script is het mogelijk om het gedrag van de applicatie met scripting te veranderen.
Doordat het scripten erg open is kun je op eenvoudige wijze met scripting extra functionaliteit toevoegen aan je applicatie. Met name het dynamische aspect dat met scripting mogelijk wordt, is een extra dimensie aan softwareontwikkeling. Scripting is dan ook een mooie uitbreiding.
Er zijn een paar maren te noemen. Het scripten gaat niet altijd goed; als er bijvoorbeeld fouten gemaakt worden bij het declareren van variabelen wil nog wel eens een GPF optreden. Daarnaast is scripten erg functiegericht en kunnen er helaas geen objecten gedefinieerd worden. Ondanks deze maren kan ik het scripten iedereen aanbevelen. Mocht iemand vragen hebben over het werken met VO-Script, schroom dan niet om contact met mij op te nemen op bert.dingemans@hetnet.nl. Verder is een groot aantal voorbeelden te vinden op http://home.hetnet.nl/~bert.dingemans.
Bert Dingemans is een oude bekend van de SDGN. Hij ontwikkelt in een aantal ontwikkelomgevingen waaronder MS-Office en Powerbuilder. Zijn oude liefde Visual Objects gebruikt hij ook regelmatig. Op dit moment werkt hij aan een nieuwe CASE-tool DLA-Architect genaamd, die gebouwd wordt in Visual Objects. Bert werkt bij het Bureau Jeugdzorg in Utrecht. Hij is bereikbaar op bert.dingemans@hetnet.nl .