ViaThinkSoft CodeLib
This article is in:
CodeLib → Programming aids → Delphi
Es wird folgendes Problem behandelt: http://stackoverflow.com/questions/35032857/combining-onvalidate-and-onbeforepost
Als Beispiel haben wir ein Form mit einem DBNavigator and ein paar DBEdits.
Wir möchten folgendes Erzielen:
- Es soll verhindert werden, dass ungültige Daten in die Datenbank gespeichert werden.
- Wenn der Benutzer etwas falsches in ein DBEdit eingibt und zu einem anderen Control wechseln möchte, soll eine Warnung angezeigt werden, aber er soll in der Lage sein, dies zu ignorieren und weiter zuarbeiten. Im idealen Falle wird der Benutzer einmal (!) gewarnt und zum Control zurückgeführt.
- Wenn der Benutzer das Dataset speichern möchte, soll dies verhindert werden, und die Warnung soll erneut angezeigt werden. Im idealen Falle wird der Benutzer zum Control geführt.
Das Hauptproblem ist, dass OnValidate nur ein TFieldNotifyEvent ist und es daher dem Dataset nicht mitteilen kann, ob die Eingabe gültig ist. Somit gibt es keine Möglichkeit um zu prüfen, ob alles OK ist, bevor man abspeichert.
Ich habe folgendes probiert:
Versuch 1: OnBeforePost prüft den Wert wirft eine Exception.
Pro:
- Ungültige Daten können nicht abgespeichert werden.
- Der Benutzer kann mit der Arbeit fortfahren und die Änderungen über den DBNavigator zurücksetzen/abbrechen, wenn er nicht in der Lage ist, das Formular korrekt auszufüllen.
Contra:
- Es wird keine Warnung angezeigt, wenn der Benutzer das DBEdit verlässt.
- Geringe Priorität: Alle validierungen müssen in einer einzigen Prozedur stattfinden. Bessere Objektorientierte Programmierung würde die Validierung direkt in einem Ereignis des TField machen.
Versuch 2: Eine Exception im OnValidate werfen.
Pro:
- Ungültige Daten können nicht abgespeichert werden.
Contra:
- Der Benutzer wird in eine Endlosschleife geraten, wenn er nicht in der Lage ist, etwas gültiges in das DBEdit einzutragen (z.B. wenn er nicht weiß, was er eingeben soll), und er kann nichtmal den "Abbrechen" Button im DBNavigotr klicken. In diesem Fall muss er die Anwendung durch den Task-Manager abbrechen, was ein Worse-Case-Szenario in bezug auf Benutzerfreundlichkeit wäre.
Versuch 3: Zeige eine Warnung (keine Exception) im OnValidate und validiere erneut in OnBeforePost, und werfe dort eine Exception.
Pro:
- Ungültige Daten können nicht abgespeichert werden.
- Eine Warnung ist möglich
Contra:
- Die Prüfung muss zweifach implementiert werden (selbst wenn Sie diese Prüfung in eine Funktion auslagern), und das Risiko ist groß, dass ein Programmierer einen der zwei Prüfungen vergisst. Außerdem wird diese Lösung bei großen Formularen mit vielen Feldern schnell unübersichtlich.
- Wenn der Benutzer den Speichern-Button im DBNavigator klickt, während er im DBEdit ist, erhält er die Nachricht doppelt (eine Warnung beim Verlassen des DBEdit, und eine Exception weil der Post fehlschlägt).
- Für den Fall dass der Wert bereits in der Datenbank falsch ist (z.B. weil eine alte Programmversion die Prüfungen nicht durchgeführt hatte), wird keine Warnung beim verlassen des DBEdits angezeigt, wenn der Benutzer das DBEdit nur verlassen hat, ohne etwas dort zu ändern.
Gibt es einen besseren Weg?
Lösung:
Um einige Probleme zu vermeiden, sollen Werte im OnValidate durchgeführt werden. Wird dort eine Exception geworfen, dann wird der Wert nicht akzeptiert. Allerdings entstehen dadurch neue Probleme.
Problem 1: Dies hat allerdings das Problem, das man in eine Endlosschleife gerät, wenn man keinen gültigen Wert eintragen kann, und auch nicht den "Cancel" Button in DBNavigator klicken kann!
Lösung: Um dies zu verhindern, wird folgende Strategie angewendet:
Im OnValidate des jeweiligen Felds der Tabelle wird folgender Code eingefügt:
Im OnValidate wird folgender Code eingefügt:
Bei einer Validierung, die aufgrund eines Eingabefehlers stattfand, wird nur gewarnt.
Aber der Wert wird nun trotzdem in den Puffer geschrieben (also Table1XYZ).
Erst beim posten müssen wir nocheinmal genauer prüfen und das endgültige Speichern verhindern.
Daher wird dann folgender Code ausgeführt:
Problem 2: Im OnValidate können wir zwar ein SetFocus durchführen, um auf das Feld zurückzuspringen, allerdings muss man Edit-OnEnter (wegen grafischer Markierung des Felds) manuell aufrufen, um mit einem Tag verhindern, dass das Edit-OnExit nach dem Edit-OnEnter aufgerufen wird.
Lösung: siehe Code unten.
Als Beispiel haben wir ein Form mit einem DBNavigator and ein paar DBEdits.
Wir möchten folgendes Erzielen:
- Es soll verhindert werden, dass ungültige Daten in die Datenbank gespeichert werden.
- Wenn der Benutzer etwas falsches in ein DBEdit eingibt und zu einem anderen Control wechseln möchte, soll eine Warnung angezeigt werden, aber er soll in der Lage sein, dies zu ignorieren und weiter zuarbeiten. Im idealen Falle wird der Benutzer einmal (!) gewarnt und zum Control zurückgeführt.
- Wenn der Benutzer das Dataset speichern möchte, soll dies verhindert werden, und die Warnung soll erneut angezeigt werden. Im idealen Falle wird der Benutzer zum Control geführt.
Das Hauptproblem ist, dass OnValidate nur ein TFieldNotifyEvent ist und es daher dem Dataset nicht mitteilen kann, ob die Eingabe gültig ist. Somit gibt es keine Möglichkeit um zu prüfen, ob alles OK ist, bevor man abspeichert.
Ich habe folgendes probiert:
Versuch 1: OnBeforePost prüft den Wert wirft eine Exception.
Pro:
- Ungültige Daten können nicht abgespeichert werden.
- Der Benutzer kann mit der Arbeit fortfahren und die Änderungen über den DBNavigator zurücksetzen/abbrechen, wenn er nicht in der Lage ist, das Formular korrekt auszufüllen.
Contra:
- Es wird keine Warnung angezeigt, wenn der Benutzer das DBEdit verlässt.
- Geringe Priorität: Alle validierungen müssen in einer einzigen Prozedur stattfinden. Bessere Objektorientierte Programmierung würde die Validierung direkt in einem Ereignis des TField machen.
Versuch 2: Eine Exception im OnValidate werfen.
Pro:
- Ungültige Daten können nicht abgespeichert werden.
Contra:
- Der Benutzer wird in eine Endlosschleife geraten, wenn er nicht in der Lage ist, etwas gültiges in das DBEdit einzutragen (z.B. wenn er nicht weiß, was er eingeben soll), und er kann nichtmal den "Abbrechen" Button im DBNavigotr klicken. In diesem Fall muss er die Anwendung durch den Task-Manager abbrechen, was ein Worse-Case-Szenario in bezug auf Benutzerfreundlichkeit wäre.
Versuch 3: Zeige eine Warnung (keine Exception) im OnValidate und validiere erneut in OnBeforePost, und werfe dort eine Exception.
Pro:
- Ungültige Daten können nicht abgespeichert werden.
- Eine Warnung ist möglich
Contra:
- Die Prüfung muss zweifach implementiert werden (selbst wenn Sie diese Prüfung in eine Funktion auslagern), und das Risiko ist groß, dass ein Programmierer einen der zwei Prüfungen vergisst. Außerdem wird diese Lösung bei großen Formularen mit vielen Feldern schnell unübersichtlich.
- Wenn der Benutzer den Speichern-Button im DBNavigator klickt, während er im DBEdit ist, erhält er die Nachricht doppelt (eine Warnung beim Verlassen des DBEdit, und eine Exception weil der Post fehlschlägt).
- Für den Fall dass der Wert bereits in der Datenbank falsch ist (z.B. weil eine alte Programmversion die Prüfungen nicht durchgeführt hatte), wird keine Warnung beim verlassen des DBEdits angezeigt, wenn der Benutzer das DBEdit nur verlassen hat, ohne etwas dort zu ändern.
Gibt es einen besseren Weg?
Lösung:
Um einige Probleme zu vermeiden, sollen Werte im OnValidate durchgeführt werden. Wird dort eine Exception geworfen, dann wird der Wert nicht akzeptiert. Allerdings entstehen dadurch neue Probleme.
Problem 1: Dies hat allerdings das Problem, das man in eine Endlosschleife gerät, wenn man keinen gültigen Wert eintragen kann, und auch nicht den "Cancel" Button in DBNavigator klicken kann!
Lösung: Um dies zu verhindern, wird folgende Strategie angewendet:
Im OnValidate des jeweiligen Felds der Tabelle wird folgender Code eingefügt:
Im OnValidate wird folgender Code eingefügt:
if Sender = nil then
Exception.Create('Fehlertext ...')
else
ShowMessage('Fehlertext ...');
Bei einer Validierung, die aufgrund eines Eingabefehlers stattfand, wird nur gewarnt.
Aber der Wert wird nun trotzdem in den Puffer geschrieben (also Table1XYZ).
Erst beim posten müssen wir nocheinmal genauer prüfen und das endgültige Speichern verhindern.
Daher wird dann folgender Code ausgeführt:
var
f: TField;
begin
for f in Table1.Fields do
begin
if Assigned(f.OnValidate) then
begin
f.OnValidate(nil); // "nil" sorgt dafür, dass eine Exception anstelle einer normalen Warnung geworfen wird
end;
end;
Problem 2: Im OnValidate können wir zwar ein SetFocus durchführen, um auf das Feld zurückzuspringen, allerdings muss man Edit-OnEnter (wegen grafischer Markierung des Felds) manuell aufrufen, um mit einem Tag verhindern, dass das Edit-OnExit nach dem Edit-OnEnter aufgerufen wird.
Lösung: siehe Code unten.
// -- Problem 1 --
procedure TForm1.Table1Edit1Validate(Sender: TField);
begin
if IsXYZValid(Table1XYZ.AsString) then
begin
SpecialSetFocus(Edit1, Sender=nil);
if Sender = nil then
Exception.Create('Fehlertext ...')
else
ShowMessage('Fehlertext ...');
end;
end;
procedure TForm1.Table1BeforePost(DataSet: TDataSet);
var
f: TField;
begin
for f in Table1.Fields do
begin
if Assigned(f.OnValidate) then
begin
f.OnValidate(nil); // "nil" sorgt dafür, dass eine Exception anstelle einer normalen Warnung geworfen wird
end;
end;
end;
// -- Problem 2 (kosmetisch) --
procedure TForm1.SpecialSetFocus(x: TWinControl; saveMode: boolean);
begin
if not x.Focused then
begin
x.SetFocus;
if not saveMode then
begin
x.Tag := 1; // Workaround damit EditEnter funktioniert, also nur kosmetisch
EditEnter(x);
end;
end;
end;
procedure TForm1.Edit1Exit(Sender: TObject);
begin
if TComponent(Sender).Tag = 0 then
begin
// Hier der reguläre Code
TEdit(Sender).Color := clWindowText;
end;
TComponent(Sender).Tag := 0;
end;
procedure TForm1.Edit1Enter(Sender: TObject);
begin
if TComponent(Sender).Tag = 0 then
begin
// Hier der reguläre Code
TEdit(Sender).Color := clRed;
end;
TComponent(Sender).Tag := 0;
end;
Daniel Marschall
ViaThinkSoft Co-Founder
ViaThinkSoft Co-Founder