(c) Copyright 2001-2003 SDGN / Viafox, All rights reserved

Dit artikel is gepubliceerd in SDGN-Magazine nr. 60.

Thermometertje aan de wand, vertel mij eens even de tussenstand

Copyright Viafox

In het oude Foxpro for DOS/Windows had ik een thermometer met nogal wat nuttige functionaliteit aan boord. Even in mijn geheugen graven… ik herinner me een thermometerbalk, ruimte voor tekst erboven (een hoofdtaak), ruimte voor tekst eronder (een subtaak) en niet alleen het percentage werd vermeld, maar ook de voortschrijding in ‘absolute’ zin (bijv. 1307/3556) . Ach, vergeet ik bijna dat ook de verstreken tijd werd vermeld. En het bijhouden van die tijd bleek een schot in de roos, temeer daar ik er op zeker moment nog een logfunctie aan wist te koppelen. Door die logfunctie kunnen ook achteraf, in alle rust, met behulp van de pijltjestoetsen de diverse stappen nog eens ‘gelezen’ worden, inclusief de eveneens meegelogde tijden. Zo konden we zelfs een keer met harde cijfers aantonen dat een proces niet 25 minuten had geduurd (wat de eindgebruiker weeklagend beweerde), maar slechts 10 minuten. Tenslotte gebruikte ik de thermometer ook om het de gebruiker mogelijk te maken het proces af te breken door op de ESC-knop te drukken.

 

Het duurde even voor ik in VFP een thermometer ging maken. Mijn voorgevoel was dat die vorige thermometer niet echt meer zou werken. Bovendien vermoedde ik dat de timerclass een nieuwe dimensie zou kunnen gaan toevoegen. Na een jaartje voelde ik me sterk genoeg om de poging te wagen. Maar, zo bedacht ik me, was het niet zo dat je niet moet proberen het wiel opnieuw uit te vinden, hoe leuk dat eigenlijk ook is? Dus startte ik eerst een zoektocht op internet. Er werd wel het een en ander gevonden, maar enthousiast werd ik er niet van. Gelukkig, dacht ik, ik mag zelf aan de bak!

 

Het resultaat van mijn bevindingen is neergelegd in dit artikel. Dat ik niet helemaal het wiel heb uitgevonden, blijkt uit het feit dat de eigenlijke thermometer wel een reeds bestaande is, maar in feite is dat maar een klein stukje programmering dat er ook nog wel bij had gekund.

 

Van de hierboven beschreven thermometer zijn de tijdvermelding en logfunctie nog niet in de nieuwe thermometer doorgevoerd. Dat is misschien iets voor een vervolgartikel. Maar een aantal andere doorgevoerde principes maken de nieuwe thermometer in feite superieur aan de oude variant. Wat is er verwezenlijkt? Die lijst komt zo, maar ik wil eerst even overstappen van de term ‘thermometer’ op de term ‘progress indicator’. Daarmee realiseren we een directere aansluiting op de benamingen die gebruikt zijn in de classes en de documentatie. We spreken dan bijvoorbeeld van een ‘progress form’ en ‘progress bars’. Dan volgt nu de lijst van gerealiseerde onderdelen (zie ook de voorbeeldillustratie.):

·         Het werd behoorlijk echte OOP.

·         In de progress form kun je zoveel progress bars opnemen als je wijs acht, en meer. Ik ben een keer gegaan tot 300, het sloeg nergens op, maar het was een leuke test om te zien of VFP zelf het allemaal eigenlijk wel goed aankan. VFP slaagde met vlag en wimpel.

·         Iedere progress bar kreeg ruimte voor zijn eigen tekst.

·         Voor de hoofdtaakomschrijvingen besloot ik een ander principe te introduceren, de progress items. Je identificeert eerst de hoofdstappen van het proces en wijdt aan iedere stap een stukje tekst. Die items staan dan onder elkaar en de status ervan wordt links van de tekst weergegeven met behulp van bitmaps. Ook de progress items kunnen in ongekende aantallen worden toegevoegd, maar dat zou natuurlijk niet echt slim zijn.

·         Verder deed zich de behoefte voor om een meer algemene tekst toe te voegen. De layout daarvan moest die tekst onderscheiden van de progress items en ook mocht die tekst geen status kennen. De implementatie van die functionaliteit staat het toe een ongekend aantal teksten toe te voegen, maar in de praktijk zou één tekst moeten kunnen volstaan.

·         Tenslotte wilde ik persé de gebruiker de mogelijkheid geven het proces te onderbreken. De methode van het afvangen van de ESC-knop werd overgenomen uit de oude programmatuur. Maar ik wilde meer, namelijk een pushbutton ‘Onderbreken’. Dat bleek een rotklus te zijn, maar het is gelukt! Toen duidelijk was geworden hoe dat moet was het ook niet meer zo moeilijk om de form verplaatsbaar te maken! Je vraagt je misschien af waarom dat zo moeilijk was. Verderop wordt daar specifiek op ingegaan.


 

 


Het bouwen van zo’n progress class is één ding, maar het moet ook nog geïmplementeerd worden in de code welke het proces doet. Het is zeker geen kwestie van één keer de thermometer instantiëren en de rest gaat vanzelf. Je zal zelf in de code de status van de progress items moeten zetten, de progress bar(s) moeten bijwerken en moeten testen of de gebruiker misschien voor onderbreken heeft gekozen.

 

Hieronder staan de aanroepen die nodig zijn om bovenstaande illustratie te verkrijgen en die waarschijnlijk voor zich spreken. De thermometer-specifieke methods zijn vetgedrukt. (Neem me niet kwalijk dat er even public variables worden gebruikt. Die stellen je in staat vanuit de Command Window wat dingetjes uit te proberen.) In de documentatie van de progress class (progress.vcx) wordt elke method en property toegelicht.

 

public oProgress, oBar1, oText1, oEsc

public array oItem[4]

set escape off

set classlib to progress

oProgress = createobject( 'frm_progress' )

oText1 = oProgress.addText( 'Deze procedure verzorgt het afdrukken van uitnodigingen voor de ledenvergadering en de bijbehorende adres-labels, alsmede (optioneel) een presentielijst voor gebruik tijdens de ledenvergadering.',4)

oBar1 = oProgress.addProgressbar()

oItem[1] = oProgress.addProgressitem('Selecteren van de ledengegevens' )

oItem[2] = oProgress.addProgressitem('Afdrukken van de uitnodigingen voor de ledenvergadering',2)

oItem[3] = oProgress.addProgressitem('Adres-labels afdrukken')

oItem[4] = oProgress.addProgressitem('Presentielijst afdrukken')

oEsc = oProgress.addAllowBreak()

oProgress.caption = 'Uitnodigingen voor de ledenvergadering'

oProgress.show()

oItem[4].skip()

oItem[1].done()

oItem[2].now()

oBar1.update(25,'Lidnr. 12345')

 

Het bovenstaande aanschouwende zal het wellicht duidelijk zijn dat je code er al snel een rommelige indruk door krijgt. Al die aanroepen kunnen beter worden verhuisd naar een aparte method of functie ‑ laten we zeggen Thermo() - welke op zijn beurt vanuit de code her en der met simpele parameters kan worden aangeroepen. In Thermo() kan dan met een CASE-structuur worden gewerkt.

 

Het resultaat daarvan  is een ‘template’ van Thermo(), alsmede een voorbeeldprogrammaatje dat duidelijk maakt hoe die template moet worden gebruikt. Hieronder zie je een aangepast extract van het template; aangepast om het principe hier te illustreren.

 

FUNCTION Thermo

lparameter tnStep

local li, lcMsg

 

do case

case pcount() = 0 or tnStep < 0   && cleanup

  *

  if type( 'poThermo' ) = 'O'

    poThermo.release()

  endif

  RETURN .T.

                       

case tnStep = 0                   && Init

  *

  poThermo  = createobject('frm_progress')

  poText    = poThermo.addText('This is the main text.’)

  poBar     = poThermo.addprogressbar()

  poItem[1] = poThermo.addprogressitem('Item text 1')

  poEsc     = poThermo.addallowbreak()

  poThermo.caption = "Form's caption"

 

  <…>

 

  poBar.visible = .f.

  poThermo.show()

 

case tnStep=1                     && step 1

  *

  poItem[1].now()

 

case tnStep=2                     && step 2 init

  *

  poItem[2].now()

  poBar.visible = .t.

 

case tnStep=2.5                   && step 2 in scan

  *

  lcMsg = ltrim(str(recno()))+"/"+ltrim(str(reccount()))

  lcMsg = poBar.update(floor(recno()/reccount()*100),lcMsg)

 

<…>

 

endcase

           

if poThermo.escaped()

  go bottom

  if not eof()

    skip

  endif

  RETURN .F.

endif

 

RETURN .T.

 

Leerzame momenten

 

Ok, wat waren de leerzame momenten en welke uitvindingen moesten worden verricht? Ik moet dan denken aan de volgende items:

·         Een timer leek zo’n fraai toe te passen class, maar dat viel tegen.

·         Een ‘Onderbreken’ knop bleek moeilijk omdat je middenin een proces zit.

·         Een Release van het form bleek niet zomaar voldoende.

 

De timer  in VFP

 

De manier waarop de mensen bij Microsoft de timer class hebben geïmplementeerd in VFP is wat ongelukkig. Het ‘ding’ is gewoon niet onafhankelijk genoeg. Start maar eens een proces waarbij een tijdlang geen interactie tussen gebruiker en programma mogelijk is en kijk of in die tijd een vooraf gestarte timer event door VFP aangedaan wordt, nee dus. En daarmee onderscheidt VFP zich in negatieve zin van bepaalde andere ontwikkelomgevingen. Ik wil dat een timer event in VFP ook eerbiedigd wordt tijdens een proces. Zolang dat niet kan doet VFP op dit punt onder voor andere ontwikkelomgevingen.

 

Wie de moeite neemt om in de progress bar class te kijken ziet daar een timer object staan. Die was er door de originele maker van de progress bar class al ingezet. Er wordt om bovengenoemde reden door mij echter niks mee gedaan. De aansturing van 0 naar 100 procent zal geheel handmatig verricht moeten worden, bijvoorbeeld in een SCAN-lus.

 

Een Onderbrekenknop.

 

Gedurende het te volgen proces, of eigenlijk in de code, zijn er vast wel momenten dat de progress form even aangeroepen kan worden, bijvoorbeeld om te controleren of de ESC-toets ondertussen ingedrukt is. Dat kan in een SCAN-lus, maar ook bijvoorbeeld tijdens de executie van een report. Roep in dat laatste geval de juiste method aan vanuit de detailband of een groupband.

 

Ik creëerde hiervoor de method Escaped(). Reageren op de ESC-toets lukte prima, wat feitelijk inhoudt dat er gereageerd wordt op de functies CHRSAW() en INKEY(). Maar wat ik ook probeerde, klikken op een pushbutton ging voor geen meter.

 

Uiteindelijk bood SYS(1270) perspectief. Deze functie geeft een objectreferentie van het object waar de muiscursor op dat moment overheen dribbelt. Zo kon ik zien of die muiscursor misschien boven de ‘Onderbreken’ knop verbleef. Als dat zo was hield ik de aandacht even vast in het venster om met MDOWN() te testen of de gebruiker misschien ook de moeite nam de linkermuisknop aan te klikken. En als dat zo was, dan was het tijd voor het stellen van de vraag of er moest worden onderbroken. Maar eerst wilde ik dan natuurlijk die pushbutton even visueel ‘ingedrukt’ hebben. En dat gaat niet, want VFP heeft ons daarvoor geen toepasselijke pushbutton-property gegeven! Maar op zo’n moment komt het aan op creativiteit, dus werd de pushbutton vervangen door een checkbox waarvan de Style op 1-Graphical werd gezet. Het is dan net een pushbutton, maar wel eentje die ingedrukt oogt als de Value op 1 wordt gezet. En zodra MDOWN() niet meer True is, wordt de Value snel weer op 0 gezet, waarna de Messagebox() met de heikele vraag mag komen. Maar wat nu als de gebruiker niet op de linkermuisknop drukte? Bij de volgende keer dat het venster even bezocht wordt wil je dan niet dat er wederom gewacht wordt. Dus moet worden onthouden dat er een vorige keer al gewacht is, althans als de muis volstrekt niet van zijn plaats blijkt te zijn geweest. En snel door met de klus dan…

 

Deze methode bleek succesvol en ook geschikt om het venster verplaatsbaar te maken. De ruimte voor de caption bleek ook identificeerbaar met SYS(1270).

 

Toen ik dezelfde methode wilde gebruiken om de status-bitmaps te voorzien van een gesimuleerde tooltiptext, bleek mij dat zo’n simulatie helemaal niet nodig was, althans in VFP 6. Merkwaardig genoeg was het voldoende om de echte tooltiptext properties van de image objects, waarin de bitmaps zaten, te vullen. VFP 6 reageerde keurig, zelfs als het venster niet vanuit de code werd aangeroepen. Blijkbaar is de tooltiptext functionaliteit in VFP geïmplementeerd via een Windows API-call die zijn werk daarna geheel onafhankelijk doet. Zie je wel dat ze het wel kunnen bij Microsoft; zo zou de timer event ook moeten worden aangepakt! Overigens zul je in VFP 5 die tooltiptexts nog niet zien, omdat de image class die property toen nog niet had.

 

Release van de form

 

Na gebruik wil je natuurlijk van het venster verlost worden. Een simpele Release ervan bleek niet voldoende. Elke progress bar, elk progress item, enzovoort, wordt in de code uiteindelijk met de AddObject() method toegevoegd. Wat blijkt? Elk met AddObject() toegevoegd object moet in de Release method van het form ook weer expliciet verwijderd worden, met de RemoveObject() method. Alleen dan zal het venster echt kunnen verdwijnen.

 

Conclusie

 

Al met al is de progressform/thermometer een verrijking gebleken. Eigenlijk zijn juist wij, als bouwers van database-applicaties, verplicht de gebruikers van onze applicaties goed te informeren over de diverse stappen en de voortgang van processen zoals maandafsluiting, gegevensimport en rapportage, om maar eens wat te noemen. Wie tot nu toe nog geen thermometer aanbood, kan de hierboven beschreven thermometer gebruiken. De thermometer is ruim gedocumenteerd. De enige klus die je zelf zal moeten doen is het ding integreren in je eigen framework. Dat kan je natuurlijk ook laten, maar dan zal je bijvoorbeeld het lettertype niet kunnen zetten vanuit je eigen framework. (Op zich leent dat probleem zich goed voor een artikel: Hoe kan je classes die anderen creëren soepeltjes in je eigen framework hangen?)

 

De progress class, documentatie en template zijn te vinden op de SDGN Website (en alhier).

 

Ga je ermee aan de slag en slaag je erin nieuwe functionaliteit toe te voegen? Laat het me weten!

 

Home