Ex-post-Validierung
Zur Ex-post-Validierung wollen wir alle Aktivitäten zählen, die darauf abzielen, die Zulässigkeit der Werte nach der Eingabe sicher zu stellen. Aber wann genau sollen wir das tun und wie stark sollen wir den Benutzer dabei führen?
Varianten der Ex-post-Validierung
Ein extremer Ansatz ist, unmittelbar nach der Eingabe eines einzelnen Zeichens zu überprüfen, ob es zum vorgesehenen Typ passt. Wir können dies die zeichenbezogene Validierung nennen. Bei ganzzahligen Datentypen ist die zeichenbezogene Validierung noch einfach: nur Ziffern und ggfls. ein Vorzeichen am Anfang sind erlaubt. Aber schon bei Double-Zahlen ist sie sehr schwierig. Außerdem lässt diese Art der Überwachung dem Benutzer kaum Luft zum Atmen. Schon bei einem einfachen Tippfehler, den er normalerweise selbst bemerkt und sofort verbessert, wird er sofort zur Ordnung gerufen. Der Benutzer empfindet dies als nervig und verliert die Lust an der Arbeit mit dem Programm.
Das andere Extrem ist die Validierung en bloc. Dabei lassen wir dem Benutzer zunächst völlige Freiheit bei seiner Arbeit im Formular. Erst wenn er dieses mit einem Klick auf die Schaltfläche <Speichern> (oder eine ähnliche) verabschiedet hat, folgt die Überprüfung, die dann im Fehlerfall in einer mehr oder weniger detaillierten Fehlerliste mündet.
Zwischen der zeichenbezogenen Validierung und der Validierung en bloc liegt die feldbezogene Validierung. Dabei validieren wir, sobald der Benutzer ein Eingabefeld verlässt, in das er einen Wert eingegeben hat. Die Reihenfolge der Eingaben kann er selbst bestimmen, und wir können ihm auch die Freiheit lassen, ein Feld zunächst leer zu lassen und erst dann zu bearbeiten, wenn er alle anderen Felder schon ausgefüllt hat.
Der Nachteil der Validierung en bloc gegenüber der feldbezogenen Validierung ist, dass der Benutzer erst sehr spät auf Fehler hingewiesen wird. Oft sind sich Felder in einem Eingabeformular ähnlich, so dass ein früher Fehlerhinweis beim ersten Feld dem Benutzer beim Ausfüllen der weiteren Felder geholfen hätte. Andererseits kann die feldbezogene Validierung die Validierung en bloc nicht vollständig ersetzen. Nur diese kann Abhängigkeiten zwischen den Werten in den einzelnen Feldern berücksichtigen, und sie ist auch besser geeignet, das Ausfüllen von Muss-Feldern sicher zu stellen.
Optimal ist demnach eine Kombination aus feldbezogener Validierung und Validierung en bloc. Ausgenommen sind sehr kleine Formulare mit wenigen Eingabefeldern. Hier kann eine Validierung en bloc ausreichen. Im Grenzfall, wenn ein Formular nur ein Eingabefeld hat, fallen die beiden Validierungsarten ohnehin zusammen.
Beispiel 1: Berechnung einer arithmetischen Reihe
Wir betrachten nun eine einfache Anwendung mit nur einem Eingabefeld. Hier genügt eine „kleine“ Lösung: Feldbezogene Validierung und Validierung en bloc fallen zusammen.
Die Benutzeroberfläche besteht aus dem unten abgebildeten Formular. Der Benutzer kann eine natürliche Zahl n eingeben und bekommt dann die Summe der ersten n natürlichen Zahlen ausgegeben, also 1 + ... + n. Die Eingabe ist auf n > 0 und n ≤ 45000 beschränkt. Die Schaltfläche <Berechnen> löst die Berechnung aus; mit Hilfe der Schaltfläche <Felder leeren> kann der Benutzer den ursprünglichen Zustand wieder herstellen, wenn er ein neues n eingeben will.

Wenngleich die Aufgabenstellung alles andere als umfangreich ist, wurde das Programm mit einer zweistufigen Schichtenarchitektur (nächstes Bild) realisiert. Dadurch wird der Kern der Verarbeitung (die sog. Programmlogik), so weit wie möglich von der Benutzeroberfläche entkoppelt (s. Artikel Architektur).
Die obere Schicht bildet das Formular Reihensumme mit seinem Modul. Dieses Modul führt den Benutzer durch das Programm und steuert auch die Validierung. Die darunter liegende Schicht enthält zwei Moduln. Im Modul FolgenUndReihen (rechts) liegt der inhaltliche Kern der Verarbeitung. Das Modul ValHlp enthält Funktionen, die im Verlauf der Eingabevalidierung aufgerufen werden.

Das nächste Bild zeigt die Reaktion des Programms, wenn der Benutzer einen unzulässigen Wert in das Feld n eingibt. Es erscheint eine MessageBox mit einem Hinweis auf die zulässigen Werte. Nachdem der Benutzer mit eine Klick auf <OK> bestätigt hat, dass er die Nachricht zur Kenntnis genommen hat, verschwindet die Meldung wieder, und er kann nun seine Eingabe korrigieren.

Wir betrachten nun den Code des Formularmoduls ReihensummeForm. Die Eingabevalidierung wird in der Prozedur BerechnenBtn_Click durch den Aufruf der Funktion ValidierungOK angestoßen. Diese Funktion befindet sich ebenfalls im Formularmodul. Sie delegiert die Überprüfung der Eingabe auf eine Funktion istImGanzzBereich im Modul ValHlp.
'Aktionen nach Anklicken des Buttons <Berechnen>
'===============================================
Private Sub BerechnenBtn_Click()
If ValidierungOK Then
Me.SummeTBx.Text = CStr(FolgenUndReihen.SuAR1BisN(CLng(Me.nTBx.Text)))
Else
Me.SummeTBx.Text = "Fehler"
End If
End Sub
'Aktionen nach Anklicken des Buttons <Felder leeren>
'===================================================
Private Sub FelderLeerenBtn_Click()
Me.nTBx.Text = ""
Me.SummeTBx.Text = ""
Me.nTBx.SetFocus
End Sub
'Überprüfung der Eingabe
'=======================
Private Function ValidierungOK() As Boolean
If ValHlp.istImGanzzBereich(Me.nTBx.Text, 0, 45000) Then
ValidierungOK = True
Else
ValidierungOK = False
MsgBox "nur ganze Zahlen von 1 bis 45000 eingeben!"
Me.nTBx.SetFocus
End If
End Function
Das Modul ValHlp enthält eine Reihe von Funktionen, die zur Überprüfung von Eingabewerten herangezogen warden können. Da solche Überprüfungen bei jeder Validierung vorkommen, lohnt es sich, diese Funktionen in einem eigenen Modul zu bündeln. Sie können dann auch in anderen Programmen verwendet werden.
Aus dem umfangreicheren Code dieses Moduls sollen hier nur die beiden Funktionen gebraucht werden, die innerhalb unserer Anwendung gebraucht werden. Es handelt sich um die Funktion istImGanzzBereich, welche aus dem Formularmodul aufgerufen wird, und um die Funktion istGanzzahl, welche ihrerseits von istImGanzzBereich aufgerufen wird.
'ermittelt für den Wert z, ob er einer Ganzzahl entspricht;
'z kann auch ein String sein
'==========================================================
Public Function istGanzzahl(ByVal z As Variant) As Boolean
If Not IsNumeric(z) Then
istGanzzahl = False
Else
If CLng(z) - Int(z) = 0 Then
istGanzzahl = True
Else
istGanzzahl = False
End If
End If
End Function
'ermittelt für den Wert z, ob er einer Ganzzahl entspricht und
'im Bereich [min, max] liegt; z kann auch ein String sein
'==========================================================
Public Function istImGanzzBereich(ByVal z As Variant, _
ByVal min As Long, _
ByVal max As Long) As Boolean
If Not istGanzzahl(z) Then
istImGanzzBereich = False
Else
If CLng(z) >= min And CLng(z) <= max Then
istImGanzzBereich = True
Else
istImGanzzBereich = False
End If
End If
End Function
Beispiel 2: Zuschussberechnung
In diesem Beispiel wird eine Kombination aus feldbezogener Validierung und Validierung en bloc angewandt. Dabei weist die feldbezogene Validierung nur auf Fehler hin und erzwingt nicht deren Korrektur. Die abschließende Validierung en bloc prüft noch einmal alle Felder und stellt auch sicher, dass alle Mussfelder ausgefüllt sind.
Das folgende Bild zeigt das einzige Formular des Programms. Es gibt drei Eingabefelder und ein Ausgabefeld (Zuschuss). Bezüglich der Eingaben gilt:
- beim Nachnamen wird ein String erwartet, der mit einem Buchstaben beginnen muss und außer Buchstaben nur wenige andere Zeichen enthalten darf (Leerzeichen, Bindestrich, Punkt, Apostroph)
- die Kinderzahl soll eine Ganzzahl im Bereich [0, 20] sein
- als Einkommen wird eine Kommazahl im Bereich [0, 100000] erwartet.
Das Eurozeichen und die Formatierungspunkte in den Feldern Einkommen und Zuschuss werden den Zahlen vom Programm hinzugefügt.

Das Programm besitzt eine zweistufige Schichtenarchitektur (nächstes Bild). Die obere Schicht bildet das Formular Validierungsbeispiel mit seinem Modul. Dieses Modul führt den Benutzer durch das Programm und steuert auch die Validierung. Die darunter liegende Schicht ist zweigeteilt. Im Modul Zuschussberechnung (rechts) liegt der inhaltliche Kern der Verarbeitung (sog. Programmlogik). Der Rest dieser Schicht besteht aus zwei Moduln, die bei der Validierung herangezogen werden. Diese beiden Moduln bilden selbst wieder eine kleine Hierarchie. ValForm enthält Funktionen, die bei der Validierung von Formular-Eingabefeldern herangezogen werden können; ValHlp enthält Funktionen, die rein wertbezogene Überprüfungen vornehmen.

Wir wollen nun das Verhalten des Programms bei Eingabefehlern studieren. Das nächste Bild zeigt die Reaktion des Programms, wenn der Benutzer einen unzulässigen Wert in das Feld Monatseinkommen eingibt. Hier greift die feldbezogene Validierung ein: Das Feld wird rot gefärbt, der bereits vorher vorhandene Tooltiptext wird um einen Fehlerhinweis ergänzt. Der falsche Eingabewert bleibt zunächst erhalten und muss auch vom Benutzer nicht sofort korrigiert werden. Er kann seine Eingaben an beliebiger Stelle in der Maske fortsetzen.

Hat der Benutzer die Schaltfläche <Zuschuss errechnen> gedrückt, so greift die Validierung en bloc. Sie überprüft zunächst, ob alle Mussfelder ausgefüllt sind. Ist dies nicht der Fall, so erfolgt eine entsprechende Meldung (nächstes Bild).

Da die feldbezogene Validierung die Korrektur einzelner fehlerhafter Felder nicht erzwingt, prüft die Validierung en bloc auch nochmals alle Eingaben. Finden sich noch unzulässige Eingaben, so wird eine Korrekturaufforderung mit einer ausführlichen Fehlerliste ausgegeben (nächstes Bild). Nach dem Bestätigen der Korrekturaufforderung liegt der Fokus der Eingabe auf dem ersten fehlerhaften Feld.

Wir betrachten nun das Formularmodul Validierungsbeispiel. Die drei Ereignisprozeduren, welche die feldbezogene Validierung anstoßen, befinden sich am Schluss. Jede der drei Prozeduren ist nur eine Zeile lang und besteht aus dem Aufruf einer auf den betreffenden Datentyp zugeschnittenen Validierungsfunktion im Modul ValForm. Beachten Sie, dass diesen Funktionen nicht nur der zu überprüfende Wert übergeben wird, sondern das gesamte Steuerelement, in das der Wert eingegeben wurde. Auf diese Weise können die Funktionen in ValForm voll über diese Steuerelemente verfügen und damit auch deren Hintergrundfarbe ändern und den Tooltiptext um die Fehlerhinweise ergänzen. Beachten Sie auch, dass diese Funktionen hier wie Prozeduren behandelt werden. Ihr Rückgabewert wird nicht aufgefangen, weil ihre relevante Aktion aus den Änderungen beim jeweiligen Steuerelement besteht.
Die Validierung en bloc geschieht innerhalb der Ereignisprozedur ErrechnenBtn_Click; sie nimmt den größten Teil dieser Prozedur ein. Zunächst wird die Vollständigkeit der Eingaben überprüft. Dann werden die einzelnen Eingabefelder validiert, wobei dieselben Funktionen des Moduls ValForm aufgerufen werden wie bei der feldbezogenen Validierung. Im Gegensatz zur feldbezogenen Validierung werden aber die Rückmeldungen der Funktionen aufgefangen und verwertet. Diese Rückmeldungen beschränken sich nicht auf die Funktionswerte (Eingabe korrekt oder nicht), sondern umfassen auch auf die Parameter, die ByRef übergeben wurden. Dabei handelt es sich um die bereinigten Eingabewerte und, im Fehlerfall, einen Fehlerhinweis, der dann in die Liste der Fehlerhinweise aufgenommen wird.
Option Explicit
'leert die Eingabefelder
'=======================
Private Sub LeerenBtn_Click()
Me.NameTBx.Text = ""
Me.KinderTBx.Text = ""
Me.EinkommenTBx.Text = ""
Me.NameTBx.BackColor = vbWindowBackground
Me.KinderTBx.BackColor = vbWindowBackground
Me.EinkommenTBx.BackColor = vbWindowBackground
Me.ZuschussTBx.Text = ""
Me.NameTBx.SetFocus
End Sub
'Aktionen nach Drücken von <Zuschuss errechnen>
'==============================================
Private Sub ErrechnenBtn_Click()
'Prüfung, ob obligatorische Felder ausgefüllt
If Me.NameTBx.Text = "" Or Me.KinderTBx.Text = "" Or _
Me.EinkommenTBx.Text = "" Then
MsgBox "Sie müssen alle Eingabefelder ausfüllen"
Exit Sub
End If
'Hilfsvariablen zur Fehlerbehandlung
Dim Err As String
Dim allErrors As String
allErrors = ""
Dim focusSet As Boolean
'für Endform der Eingaben
Dim Kinder As Long
Dim Einkommen As Double
Dim Name As String
'Prüfung aller Eingabefelder
If Not ValForm.checkName(Me.NameTBx, Name, Err) Then
Me.NameTBx.SetFocus
focusSet = True
allErrors = allErrors & "Nachname: " & Err & vbLf
End If
If Not ValForm.checkLng(Me.KinderTBx, 0, 20, Kinder, Err) Then
allErrors = allErrors & "Anzahl Kinder: " & Err & vbLf
If Not focusSet Then
Me.KinderTBx.SetFocus
focusSet = True
End If
End If
If Not ValForm.checkDbl(Me.EinkommenTBx, 0, 100000, Einkommen, Err) Then
allErrors = allErrors & "Einkommen: " & Err & vbLf
If Not focusSet Then
Me.EinkommenTBx.SetFocus
focusSet = True
End If
End If
'Verarbeitung nach Prüfung
If allErrors <> "" Then 'wenn Eingaben fehlerhaft
MsgBox "Bitte korrigieren Sie die Eingabefehler:" & _
vbLf & allErrors, vbOKOnly
Else 'wenn fehlerlos
Dim zuschuss As Double
zuschuss = Zuschussberechnung.zuschuss(Einkommen, Kinder)
Me.EinkommenTBx.Text = FormatCurrency(Einkommen)
Me.ZuschussTBx.Text = FormatCurrency(zuschuss)
End If
End Sub
'Prüfung der Eingabefelder unmittelbar nach Update
'==================================================
Private Sub KinderTBx_AfterUpdate()
ValForm.checkLng con:=Me.KinderTBx, min:=0, max:=20
End Sub
Private Sub EinkommenTBx_AfterUpdate()
ValForm.checkDbl con:=Me.EinkommenTBx, min:=0, max:=100000
End Sub
Private Sub NameTBx_AfterUpdate()
ValForm.checkName con:=Me.NameTBx
End Sub
Das Modul ValForm enthält Validierungsfunktionen, deren Anwendung nicht auf dieses Programm beschränkt ist. Die Funktionen können in jedem Programm verwendet werden, das Validierungen von Formulareingaben vornehmen muss.
Die drei Funktionen des Moduls sind hinsichtlich der zu überprüfenden Datentypen spezialisiert. Die Funktion checkDbl prüft Double-Werte, die Funktion checkLng prüft ganzzahlige Werte und die Funktion checkName ist für die Prüfung von Strings anwendbar, welche Namen repräsentieren.
Eine Besonderheit der drei Funktionen sind ihre Nebeneffekte. Sie nehmen einige Parameter ByRef entgegen und verändern sie im Lauf der Validierung.
Option Explicit
'prüft, ob der in con enthaltene Wert einer Doublezahl im Bereich
'[min, max] entspricht;
'================================================================
Public Function checkDbl(ByRef con As MSForms.Control, _
ByVal min As Double, _
ByVal max As Double, _
Optional ByRef num As Double, _
Optional ByRef errorstr As String) _
As Boolean
Const ErrMsg As String = ". Fehler: "
Dim ErrPos As Long
ErrPos = InStr(1, con.ControlTipText, ErrMsg)
If ErrPos > 0 Then con.ControlTipText = Left(con.ControlTipText, ErrPos - 1)
If con.Text = "" Then
num = 0
errorstr = ""
checkDbl = True
con.BackColor = vbWindowBackground
Else
If ValHlp.istImDblBereich(con.Text, min, max) Then
num = CDbl(con.Text)
errorstr = ""
checkDbl = True
con.BackColor = vbWindowBackground
Else
checkDbl = False
num = 0
errorstr = "Eingabe nicht im zulässigen Bereich"
con.BackColor = vbRed
con.ControlTipText = con.ControlTipText & ErrMsg & errorstr
End If
End If
End Function
'prüft, ob der in con enthaltene Wert einer Long-Zahl im Bereich
'[min, max] entspricht;
'================================================================
Public Function checkLng(ByRef con As MSForms.Control, _
ByVal min As Long, _
ByVal max As Long, _
Optional ByRef num As Long, _
Optional ByRef errorstr As String) _
As Boolean
Const ErrMsg As String = ". Fehler: "
Dim ErrPos As Long
ErrPos = InStr(1, con.ControlTipText, ErrMsg)
If ErrPos > 0 Then con.ControlTipText = Left(con.ControlTipText, ErrPos - 1)
If con.Text = "" Then
num = 0
errorstr = ""
checkLng = True
con.BackColor = vbWindowBackground
Else
If ValHlp.istImGanzzBereich(con.Text, min, max) Then
num = CLng(con.Text)
errorstr = ""
checkLng = True
con.BackColor = vbWindowBackground
Else
checkLng = False
num = 0
errorstr = "Eingabe nicht im zulässigen Bereich"
con.BackColor = vbRed
con.ControlTipText = con.ControlTipText & ErrMsg & errorstr
End If
End If
End Function
'prüft, ob der in con enthaltene Wert einem Namen entspricht
'================================================================
Public Function checkName(ByRef con As MSForms.Control, _
Optional ByRef Name As String, _
Optional ByRef errorstr As String) _
As Boolean
Const ErrMsg As String = ". Fehler: "
Dim ErrPos As Long
ErrPos = InStr(1, con.ControlTipText, ErrMsg)
If ErrPos > 0 Then con.ControlTipText = Left(con.ControlTipText, ErrPos - 1)
con.Text = Trim(con.Text)
If con.Text = "" Then
errorstr = ""
checkName = True
con.BackColor = vbWindowBackground
Else
If ValHlp.istName(con.Text) Then
Name = con.Text
errorstr = ""
checkName = True
con.BackColor = vbWindowBackground
Else
checkName = False
errorstr = "Eingabe nicht im zulässigen Bereich"
con.BackColor = vbRed
con.ControlTipText = con.ControlTipText & ErrMsg & errorstr
End If
End If
End Function
Die Überprüfung der Eingabewerte führen die Funktionen von ValForm nicht selbst durch, sondern delegieren sie an Funktionen des Moduls ValHlp. Dieses Modul stellt Funktionen zur Werteprüfung bereit, die nicht an Formularsteuerelemente gebunden sind.
Option Explicit
'prüft, ob der Wert z einer Doublezahl im Bereich
'[min, max] entspricht
'=================================================
Public Function istImDblBereich(ByVal z As Variant, _
ByVal min As Double, _
ByVal max As Double) As Boolean
istImDblBereich = False
If IsNumeric(z) Then
If CDbl(z) >= min And CDbl(z) <= max Then
istImDblBereich = True
End If
End If
End Function
'ermittelt für den Wert z, ob er einer Ganzzahl entspricht;
'z kann auch ein String sein
'==========================================================
Public Function istGanzzahl(ByVal z As Variant) As Boolean
If Not IsNumeric(z) Then
istGanzzahl = False
Else
If CLng(z) - Int(z) = 0 Then
istGanzzahl = True
Else
istGanzzahl = False
End If
End If
End Function
'ermittelt für den Wert z, ob er einer Ganzzahl entspricht und
'im Bereich [min, max] liegt; z kann auch ein String sein
'==========================================================
Public Function istImGanzzBereich(ByVal z As Variant, _
ByVal min As Long, _
ByVal max As Long) As Boolean
If Not istGanzzahl(z) Then
istImGanzzBereich = False
Else
If CLng(z) >= min And CLng(z) <= max Then
istImGanzzBereich = True
Else
istImGanzzBereich = False
End If
End If
End Function
'ermittelt für den Wert s, ob er den Anforderungen für einen
'Namen genügt; Achtung: fängt nicht alle Fehler ab!
'==========================================================
Public Function istName(ByVal s As String) As Boolean
istName = True
If Not istBuchstabe(Mid(s, 1, 1)) Then
istName = False
Else
Dim i As Integer
For i = 1 To Len(s)
If Not (istBuchstabe(Mid(s, i, 1)) Or Mid(s, i, 1) = " " _
Or Mid(s, i, 1) = "-" Or Mid(s, i, 1) = "." Or _
Mid(s, i, 1) = "'") Then
istName = False
End If
Next
End If
End Function
'prüft, ob das übergebene Zeichen ein Buchstabe ist; bezieht
'dabei auch Umlaute mit ein
'============================================================
Public Function istBuchstabe(ByVal c As String) As Boolean
c = Mid(c, 1, 1)
If c >= "A" And c <= "Z" Or _
c >= "a" And c <= "z" Or _
c = "Ä" Or c = "ä" Or c = "Ü" Or _
c = "ü" Or c = "Ö" Or c = "ö" Then
istBuchstabe = True
Else
istBuchstabe = False
End If
End Function
Sie ist zwar im Hinblick auf die Validierung nicht relevant, aber zum Programm gehört sie trotzdem: die Berechnung des Zuschusses. Deshalb der Vollständigkeit halber noch das Modul Zuschussberechnung:
Option Explicit
'ermittelt den Kinderzuschuss aus Einkommen und Kinderzahl
'==========================================================
Public Function zuschuss(ByVal eink As Double, ByVal kids As Byte) As Double
Dim anrechEink As Double
anrechEink = IIf(eink - 450 > 0, eink - 450, 0)
zuschuss = kids * 180 - anrechEink
zuschuss = IIf(zuschuss > 0, zuschuss, 0)
End Function
Abschließende Bemerkungen zur Ex-post-Validierung
- Für die Ausarbeitung dieses Beispiels habe ich Anregungen aus dem ausgezeichneten Buch von Bovey, Wallentin, Bullen und Green aufgenommen (s. Literaturliste zu Excel-VBA).
- Die in den Modulen ValForm und ValHlp enthaltenen Funktionen decken nicht alle Eingabetypen ab; sie müssen bei Bedarf durch weitere Funktionen ergänzt werden.
- Manche Datentypen lassen sich mit den gezeigten primitiven Mitteln nur schwer überprüfen. Die im obigen Beispiel verwendete Funktion checkName versagt leider bei vielen Fehleingaben. So bleibt z.B. unbemerkt, wenn der Benutzer in einem Doppelnamen zu viele Bindestriche einträgt („Streit- - -Hammel“ statt „Streit-Hammel“). Um hier alle möglichen Fehler auszuschließen, muss man schwerere Geschütze auffahren, wie z.B. eine Prüfung anhand regulärer Ausdrücke.
- In manchen Fällen, in denen Ex-post-Validierung schwierig ist, hilft Ex-ante-Validierung. Dazu gehören insbesondere Datumseingaben.
- Validierung kann die Eingabe zulässiger Werte erzwingen, nicht aber die Eingabe richtiger Werte.