7.4 Creatie en toepassing van eigen functies


7.4.1. Voorbeelden

VBA kent - zoals elke programmeertaal - een groot aantal standaard- of bibliotheekfuncties.
We illustreren er enkele van in het venster "Foutopsporing".

Een functie is een stukje programma dat een waarde oplevert.
Meestal zal je één of méérdere parameters moeten meegeven bij het aanroepen van een functie. Zo hebben de functies Ucase, Fix en Year één parameter terwijl de functie Mid$ er drie heeft. De parameter(s) is/zijn als het ware de grondstof(fen) waarmee het afgewerkt product moet gemaakt worden.
Naast het aantal is ook het type der parameters van belang. Zo heeft de functie Ucase een parameter van het type string, terwijl de functie Fix er een heeft van het type Numeriek.

Nu kan je naast standaardfuncties ook je eigen functies gebruiken. Je moet ze dan wel eerst maken...

Voorbeeld 1: de functie EersteLetterHoofdletter

Je wil van een woord steeds de eerste letter als hoofdletter en de rest als kleine letter(s) afdrukken.

Je kiest in het Module-ontwerpscherm voor Invoegen,Procedure of drukt de knop en je komt in het volgend dialoogvenster terecht:

Je geeft de functie een naam, klikt het keuzerondje 'Functie' aan en kiest 'OK'. De eerste en laatste regel van de functie verschijnen dan spontaan op het scherm:

Public Function EersteLetterHoofdletter()

End Function

Aan jou om de rest in te vullen:

Deze functie is de eerste in een reeks die we opslaan in de module 'basEigenfuncties'

Tussen de haakjes achter de functienaam declareer je zoveel variabelen als er parameters zijn. (Straks letten we ook op het type.) Bij het oproepen van een functie of een subprocedure wordt/worden (meestal) namelijk één of meerdere waarden (parameters, benoemde argumenten) meegegeven. De in de functie geschreven bewerkingen zullen op of met deze gegevens uitgevoerd worden. Dit veronderstelt echter dat binnen de 'werkruimte' van de functie de doorgegeven waarden kunnen onthouden worden. Dit is de rol van de variabelen tussen de haakjes in de hoofding van de functie. De waarden, meegegeven met de functie-oproep, worden in deze 'parametervariabelen' gecopieerd. (Dit kan op 2 manieren: parameterbinding 'by reference' en 'by value':cfr subprocedures.)
In de laatste regel wordt aan de functie een waarde toegewezen. Deze begint steeds met de naam van de functie gevolgd door het toewijzingsteken (=).
Je kan de functie nu uitvoeren of oproepen als betrof het een standaardfunctie. We doen dit even in het venster 'Foutopsporing':

Bij het declareren van parametervariabelen en/of geheugenvariabelen kan je het TYPE ervan aangeven. Doe je dit niet dan neemt ACCESS het type VARIANT. Variabelen van dit type zijn automatisch converteerbaar naar de meeste andere types , doch nemen meer plaats in het geheugen en worden trager verwerkt.

Wij nemen als richtlijn het type van een geheugenvariabele of een parametervariabele zelf te bepalen!

Je kan ook het type van het resultaat van de functie expliciet opgeven. Je doet dit achter de haakjes in de functiehoofding na het woordje 'As'. Vul je niets in, dan is het type 'Variant'.

Volgende hoofdingen zijn alle correct voor onze functie doch zijn niet identiek:

Public Function EeersteLetterHoofdletter(x)
Public Function EeersteLetterHoofdletter(x As String)
Public Function EeersteLetterHoofdletter(x As String) As String

In de volgende tabel zijn enkel de elementen van de syntax opgenomen die in de voorbeelden aan bod kwamen. Dit zijn de meest voorkomende en daarom de belangrijkste. De volledige syntax van de instructie FUNCTION vind je natuurlijk in de helpschermen.

instructie FUNCTION:

Declareert de naam, argumenten en programmacode
die het hoofddeel van de procedure Function vormen.

Syntaxis :

[Public] Function naam [(argumenten)] [As type]
    [instructies]
    [naam = expressie]
End Function


Er zijn dus 2 verschilpunten met de syntax van een SUB:

  1. omdat een functie een waarde oplevert, kan je hiervoor ook het type opgeven
  2. aan een functie wordt in de laatste regel een waarde toegewezen

opmerking: in het venster 'Foutopsporing'kan je variabelen onmiddelllijk initialiseren zonder een expliciete declaratie. Het type is steeds Variant.
Is het type van een/de parameter(s) in een functiehoofding bepaald, dan moeten de doorgegeven waarden van exact hetzelfde type zijn behalve als bedoeld type 'Variant' is. Veranderen we bijvoorbeeld de hoofding van onze functie als volgt: EersteLetterHoofdletter(x As String) dan is het niet meer mogelijk de functie te testen met de waarde uit de variabele 'varWoord' daar deze van het type Variant is en de functie een waarde van het type string verwacht!
Het type van een variabele kun je opvragen met de functie Vartype. Voor strings is dit de waarde 8. Doe je dit nu met onze variabele 'varWoord', dan krijg je 8 ook al is 'varWoord' van het type Variant! Dit komt omdat ACCESS een intern type toewijst aan variabelen van het type Variant en dit op grond van de actuele inhoud. Het is deze waarde die weergegeven wordt met de functie Vartype. Deze verandert dus zodra 'varWoord' een inhoud van een ander type krijgt. Bij het doorgeven van de waarde van 'varWoord' geldt echter steeds het type Variant. Zowaar een addertje onder het gras!


Voorbeeld 2 : de functie 'Beoordeling'

Bij het resultaat van een leerling moet een beoordeling afgedrukt worden gaande van 'grootste onderscheiding' tot 'onvoldoende'. De punten en het maximum worden aan de functie doorgegeven.

Public Function Beoordeling(sngPtn, sngMax As Single) As String
Dim sngPercent As Single
sngPercent = Format(ptn / max * 100, "#.00")
Select Case sngPercent
  Case Is >= 90
     Beoordeling = "Grootste onderscheiding"
  Case Is >= 80
    Beoordeling = "Grote onderscheiding"
  Case Is >= 70
    Beoordeling = "Onderscheiding"
  Case Is >= 60
    Beoordeling = "Eervolle vermelding"
  Case Is >= 50
    Beoordeling = "Voldoening"
  Case Else
    Beoordeling = "Onvoldoende"
End Select
End Function


In de hoofding van de functie staat dat 2 waarden van het type 'Single' zullen worden aangeboden wanneer de functie wordt opgeroepen. Uiteraard is de volgorde belangrijk. De functie levert een resultaat op van het type 'string'.


opmerking:

Bij het opgeven van de parameters kunnen de aanduidingen 'Optional', 'ByVal', ByRef' gebruikt worden. Dit is ook het geval bij subprocedures.

7.4.2. Oefeningen

  1. Maak een functie 'MaandDag' om uit een datum het jaartal te verwijderen. Het resultaat is de maand en de dag in cijfervorm vb. 04-15.
  2. Maak een functie 'Leeftijd' om de iemands leeftijd in jaren te berekenen wanneer de geboortedatum gegeven is.
  3. Maak een functie 'Sorteerformaat' om namen in zo'n vorm te schrijven dat ze correct gesorteerd worden.
  4. Maak een functie 'Palindroom' om na te gaan of een woord een palindroom is. Een palindroom is een woord dat je in beide richtingen kunt lezen zoals 'lepel'. De functie geeft als antwoord 'True' of 'False'.
  5. Maak een functie 'KleinsteVan3' om het kleinste van 3 getallen te zoeken.

antwoorden

7.4.3 Wanneer kan je functies gebruiken?

Functies kan je gebruiken overal waar je een expressie gebruikt. Dit is o.a.

Natuurlijk kan je functies ook gebruiken in het venster 'Foutopsporing', doch dit is enkel voor testdoeleinden en werd reeds geïllustreerd in de voorbeelden.

7.4.4 Functies gebruiken in een query

7.4.4.1 Als (deel van) een berekend veld

Stel dat we in onze database 'Boeken.mdb' een tabel met o.a. de namen en geboortedata van leerlingen bijhouden. We willen een lijst met de naam, de geboortedatum en de leeftijd van alle leerlingen. We maakten vroeger reeds de functie 'Leeftijd'. Deze staat in de algemene module 'basEigenFuncties'. Dit brengt mee dat deze functie in alle query's, formulieren en rapporten van de database kan gebruikt worden. Je maakt de query 'qryLeeftijd':

Een berekend veld geef je best een naam, anders wordt de naam 'Exprnnn' toegekend. Hier werd 'lt' (afkorting van leeftijd) gebruikt.

7.4.4.2 Als (deel van een) criterium

We willen een lijst van de leerlingen uit punt 7.4.4.1 die vandaag jarig zijn. We maakten vroeger de functie 'MaandDag'. Deze levert ons de maand en de dag van een datum. Indien dit voor de geboortedatum én voor vandaag dezelfde waarde oplevert is de betrokken leerling jarig. Je maakt de query 'qryVerjaardag':

7.4.4.3 Oefeningen: gebruik tblLeerlingen als basis voor onderstaande oefeningen
  1. Maak een query om voor iedereen het aantal dagen tot zijn/haar volgende verjaardag te berekenen. Gebruik hierin een berekend veld met de zelfgedefinieerde functie 'AantalDagenTotVerjaardag'.
  2. Maak een query met de naam en de leeftijd van alle leerlingen in de volgende vorm: 'Janssens is 25 jaar.' Gebruik de functie 'Leeftijd' uit de vorige reeks oefeningen.
  3. Maak een lijst van de jarige leerlingen in de volgende vorm: 'Janssens wordt vandaag 25 jaar.'. Gebruik een zelfgedefinieerde functie 'Jarig' met een Booleaanse teruggavewaarde.
  4. Maak een lijst van de leerlingen die in de loop van de volgende week jarig zijn. Gebruik een zelfgedefinieerde functie 'VolgendeMaandag' om het begin van de volgende week te bepalen vanaf een bepaalde datum.
  5. Maak een lijst van de leerlingen die tussen 15 en 17 jaar zijn. Sorteer deze op de leeftijd en op de naam. Gebruik de functies 'Leeftijd' en 'Sorteerformaat' uit de vorige reeks oefeningen.

antwoorden

7.4.5 Functies gebruiken in een formulier of rapport

7.4.5.1. Als waarde voor de inhoud van een besturingselement

De gegevens in een formulier hoeven niet per se uit een tabel of query te komen. Ze heten dan 'niet-afhankelijk'. Onderstaand voorbeeld toont hoe we de gemiddelde snelheid berekenen wanneer afgelegde afstand en benodigde tijd worden ingevoerd.

Het tekstvak 'Gemiddelde snelheid in km/u' heeft als besturingselementbron de zelfgedefinieerde functie GemiddeldeSnelheid(). Deze heeft 4 parameters: afstand, uren, minuten en seconden.

De functie ziet er zo uit:

Public Function GemiddeldeSnelheid(strA, strU, strM, strS As String) As Single
Dim dblSeconden As Double
dblSeconden = CDbl(strU) * 3600 + CDbl(strM) * 60 + CDbl(strS)
If dblSeconden > 0 Then 'deling door nul kan niet
    GemiddeldeSnelheid = CDbl(strA) * CDbl(3600) / dblSeconden
Else
    GemiddeldeSnelheid = 0
End If
End Function

In ons voorbeeld komen de parameterwaarden uit 4 tekstvakken van een formulier en zijn dus van het type 'string'. Ook al gebeuren een aantal typeconversies in ACCESS automatisch, toch wordt de conversie van 'string' naar getal IN de functie expliciet aangegeven (CDbl). Zo hebben we een duidelijker beeld van de gegeventypes waarmee we werken.

We voegen een paar elementen aan ons voorbeeld toe:

Hiervoor gebruiken we 2 nieuwe functies:MijnTijd (de functie 'Tijd' bestaat reeds in ACCESS; deze naam mag daarom niet gekozen worden) en TussenGrenzen. De eerste functie vormt met de uren, minuten en seconden één waarde van het type 'Date' terwijl de tweede nagaat of een getal tussen 2 opgegeven grenzen ligt.

Public Function MijnTijd(strU, strMin, strSec As String) As Date
MijnTijd = CDate(strU + ":" + strMin + ":" + strSec)
MijnTijd = Format(MijnTijd, "hh:mm:ss")
End Function



De functie bepaalt de waarde van het tekstvak txtTijd. Bij de eigenschap 'Besturingselementbron' is dus ingevuld: =MijnTijd([txtUren];[txtMinuten];[txtSeconden])

De functie 'TussenGrenzen' wordt gebruikt om na te gaan of de ingevoerde waarde bij de tekstvakken txtAfstand, txtUren, txtMinuten en txtSeconden tussen bepaalde grenzen ligt. De ingevoerde waarde en de 2 grenswaarden worden als parameterwaarden doorgegeven.

Public Function TussenGrenzen(sngWaarde, sngMin, sngMax As Single) As Integer
Select Case sngWaarde
  Case Is     TussenGrenzen = 1 'getal is te klein
  Case Is > sngMax
    TussenGrenzen = 2 ' getal is te groot
  Case Else
    TussenGrenzen = 0 ' getal ligt tussen de grenzen
End Select
End Function


De functie TussenGrenzen heeft 3 mogelijke teruggavewaarden.
De invoercontrole wordt uitgevoerd vòòrdat de waarde van het ingevulde tekstvak wordt bijgewerkt. We wijzen daarom een sub toe aan de eigenschap 'Voor Bijwerken' van de 4 tekstvakken waarin een waarde moet ingevuld worden. De 4 subs zijn analoog. Je vindt er hier één:

Private Sub txtMinuten_BeforeUpdate(Cancel As Integer)
Dim intOK As Integer
intOK = TussenGrenzen(CSng(txtMinuten), 0, 59)
Select Case intOK
  Case 0 'Getal is OK.
  Case 1
    MsgBox ("Het getal is te klein!")
    Cancel = True
  Case 2
    MsgBox ("Het getal is te groot!")
    Cancel = True
End Select
End Sub


Bespreking:

Private Sub txtMinuten_BeforeUpdate(Cancel As Integer)

De sub heeft een parameter 'Cancel'. Hiermee kan je de gebeurtenis die aanleiding geeft tot het uitvoeren van de sub annuleren. Dit doe je wanneer een foutieve waarde werd ingevuld. Deze moet m.a.w. verbeterd worden. De parameter Cancel krijgt de waarde 'True' om de betrokken gebeurtenis te annuleren.

intOK = TussenGrenzen(CSng(txtMinuten), 0, 59)

Je roept de functie 'Tussengrenzen' op met de inhoud van het actieve tekstvak. Omdat de door te geven waarde numeriek moet zijn wordt deze eerst geconverteerd (CSng).De 2 andere parameters zijn de onder- en de bovengrens.

Je kan de grenzen toch opgeven bij 'Validatieregel'? Ja, maar er zijn gevallen waar dit ontoereikend is zoals:

In zo'n gevallen brengt een sub bij de gebeurteniseigenschap 'Vòòr Bijwerken' de oplossing.

Er zijn nog meer cobtroles mogelijk bij de in te vullen tekstvakken: is er iets ingevuld?, is de invoer numeriek?,...). Dit kan gecontroleerd worden via 'Validatieregel', een sub bij 'Vòòr bijwerken' of een combinatie van de 2. Dit brengt ons bij het volgende punt.

7.4.5.2 Als waarde voor enkele andere eigenschappen van besturingselementen

Dit zijn de eigenschappen 'Invoermasker', 'Standaardwaarde' en 'Validatieregel'. In het ontwerpscherm van een tabel of formulier tref je bij de eigenschappen van een veld of een besturingselement de opbouwknop aan. Klik je hier dan kan je een expressie en dus ook een functie invullen.

Stel dat je in het formulier 'frmGemiddeldesnelheid(2)' een verband wil instellen tussen de afstand en de uren. Je kan dan een functie schrijven om bij het invoeren van de uren rekening te houden met de reeds ingevulde afstand. In ons voorbeeld is op deze wijze een minimumsnelheid ingesteld bij de eigenschap 'Validatieregel'. De ingevulde grenzen voor de uren zijn nul en het resultaat van de functie 'ControleUren'. Deze heeft als parameter de ingevoerde afstand en als resultaat het maximum aantal uren dat voor deze afstand wordt toegestaan.

Public Function ControleUren(strX As String) As Integer
Select Case CDbl(strX)
  Case Is  ControleUren = 1
  Case Is   ControleUren = 2
  Case Is >= 30
     ControleUren = 4
  Case Else
     ControleUren = 99
End Select
End Function

7.4.6 Functies en gebeurteniseigenschappen

7.4.6.1 Gebeurteniseigenschappen

Gebeurtenissen worden door 'ACCESS' gekoppeld aan gebeurteniseigenschappen. Bij het zich voordoen van een gebeurtenis kunnen we

  1. een macro laten uitvoeren
  2. een gebeurtenisprocedure laten uitvoeren
  3. een functie laten uitvoeren

Je ziet hieronder mogelijkheid 2 en 3.

Voorbeeld:

Je maakt of gebruikt een formulier op basis van de tabel tblLeerlingen. Zet er de naam en de onderwijsvorm in.
De naam van een leerling uit het TSO moet cursief en niet vet, deze van een BSO-leerling rechtop en vet worden getoond.

De gebeurtenis waarop moet gereageerd worden heet 'Current' of 'Aanwijzen'. Deze doet zich voor iedere keer dat er van record gewisseld wordt.

Je kan de gebeurtenisprocedure 'Bij aanwijzen' laten uitvoeren.

Private Sub Form_Current()
txtNaam.SetFocus
txtNaam.SelStart = Len(txtNaam)
Select Case [txtOnderwijsvorm].Value
  Case "TSO"
    txtNaam.FontItalic = True
    txtNaam.FontBold = False
  Case "BSO"
    txtNaam.FontItalic = False
    txtNaam.FontBold = True
End Select
End Sub
Je kan eveneens de functie NaamOpmaak laten uitvoeren. NaamOpmaak is een zelfgekozen naam terwijl de naam 'Form_Current' automatisch wordt aangemaakt.

Let op het verschil in syntax op het eigenschappenblad!

Private Function NaamOpmaak()
txtNaam.SetFocus
txtNaam.SelStart = Len(txtNaam)
Select Case [txtOnderwijsvorm].Value
  Case "TSO"
    txtNaam.FontItalic = True
    txtNaam.FontBold = False
  Case "BSO"
    txtNaam.FontItalic = False
    txtNaam.FontBold = True
End Select
End Function


opmerkingen:

Het effect van beide werkwijzen is identiek. Het gebruik van een functie doet een beetje raar aan omdat er geen teruggavewaarde is op de klassieke manier. Velen geven daarom de voorkeur aan het gebruik van een gebeurtenisprocedure in dit verband. Je vindt de volledige lijst der mogelijke gebeurtenissen en der gekoppelde eigenschappen in het helpscherm 'Gebeurtenissen en gebeurteniseigenschappen, naslag'.

7.4.6.2 Oefeningen

  1. In het formulier frmUitgeverijen mag het tekstvak met het faxnummer en het bijhorend label enkel getoond worden als er een faxnummer is ingevuld.
  2. Maak of gebruik het formulier frmLeerling. Zorg ervoor dat de hoofding van het formulier de graad (1/2/3) aangeeft waarin de leerling zit en dat de kleur van de sectie detail naargelang de graad rood, groen of blauw is.

antwoorden

7.4.7 Functies gebruiken in een macro

In een macro kan je VBA-code laten uitvoeren via de opdracht 'ProcedureUitvoeren'. Bij deze actie wordt je naar de functie gevraagd met de uit te voeren code. Het is niet mogelijk de naam van een 'SubProcedure' op te geven, enkel een 'Functieprocedure' wordt aanvaard.

De opdracht 'ProcedureUitvoeren' kan o.a. worden gebruikt bij het maken van een eigen menubalk. Dit valt evenwel niet binnen het bestek van dit hoofdstuk.