Eingabeprüfungen in Java

### Formatierungen von Textfeldern mit Java Swing

Damit der Benutzer einer Dialoganwendung gar nicht erst falsche Werte eingeben kann, gibt es bei der Dialogerstellung mit Java Swing verschiedene Möglichkeiten die Textfelder zu formatieren.
Falsche Werte können auf unterschiedliche Weise falsch sein:

Lexikalisch
  Unerlaubte Zeichen
  Prüfung: während der Eingabe
  Beispiel: Ein Buchstabe in einem Zahlenfeld
Syntatktisch
  Das Muster der Eingabe ist falsch
  Prüfung: nach der Eingabe
  Beispiel: Datum 1.1.2.04
Semantisch
  Der Wert ist nicht plausibel
  Prüfung: bei der Verarbeitung
  Beispiel: Geburtsdatum vor 1900
Pragmatisch
  Die Eingabe passt nicht zur Umgebung
  Prüfung: vor, oder bei der Verarbeitung
  Beispiel: Zu einem Projekt was in 2005 durchgeführt wird, gibt ein Benutzer folgendes Datum ein: 1.2.2004

#### javax.swing.JFormattedTextField

Neben dem Standard Texteingabefeld javax.swing.JTextField, gibt es auch das vorformatierte Textfeld JFormattedTextField, welches von ersterem abgeleitet ist. Dem JFormattedTextField kann man Vorlagen oder Schablonen mitgeben, die mit der Klasse javax.swing.text.MaskFormatter.MaskFormatter erstellt wurden:

##### Code:

    javax.swing.JFormattedTextField myJFTextField = new javax.swing.JFormattedTextField();
    javax.swing.text.MaskFormatter mf;
    try {
            mf = new javax.swing.text.MaskFormatter ("##,##");
    }
    catch (java.text.ParseException e) {}
    javax.swing.text.DefaultFormatterFactory dff = new  javax.swing.text.DefaultFormatterFactory(mf);
    myJFTextField.setFormatterFactory(dff);

##### Zeichen für die Schablone:

* - beliebiges Zeichen
? - alphabetisches Zeichen (Prüfung isLetter()=TRUE)
# - nummerisches Zeichen (Prüfung isDigit()=TRUE)
A - alphanummerisches Zeichen (Prüfung isLetterOrDigit()=TRUE)
U - wie "?" jedoch immer groß
L - wie "?" jedoch immer klein
H - hexadezimales Zeichen
' _ (Apostroph) als Kennzeichen, wenn ein Platzhalter, als Buchstabe verwendet wird
Es gibt auch einige voreingestellte Formate, wie z. B. java.text.DecimalFormat für Zahlen, Dezimalzahlen, Daten.

##### Code:

    java.text.NumberFormat nummerformat = java.text.NumberFormat.getNumberInstance();
    nummerformat.setMaximumFractionDigits(0); //keine Nachkommastellen
    nummerformat.setGroupingUsed(false); //Keine gruppierung in 100er Schritten

    java.text.DecimalFormat myDF = new java.text.DecimalFormat();
    myDF.setMaximumFractionDigits(2);   //Nachkommastellen einstellen
    myDF.setMinimumFractionDigits(2);

    java.text.DateFormat df = java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT, Locale.German);
    // SHORT nummerisch, wie 12.13.52 oder 3:30pm
    // MEDIUM länger Jan 12, 1952
    // LONG noch länger January 12, 1952 oder 3:30:32pm
    // FULL komplettes Datum Tuesday, April 12, 1952 AD oder 3:30:42pm PST.

    //javax.swing.JFormattedTextFields mit den Formaten erstellen
    javax.swing.jFTF_zahl = new javax.swing.JFormattedTextField(nummerformat);
    javax.swing.jFTF_dezimal = new javax.swing.JFormattedTextField(myDF);
    javax.swing.jFTF_date = new javax.swing.JFormattedTextField(df);

Bei der Verarbeitung im Programm benötigt man zum einen die Methode parse(String wert), um an der Eingabe zu sehen, ob sie gültig ist, wenn nicht wirft sie eine java.text.ParseExcepition. Zum anderen benötigt man die Methode format(double wert), um aus der errechneten Zahl, wieder einen String zu bilden, der beispielsweise ins Ausgabetextfeld geschrieben werden kann.

#### Normales JTextField mit javax.swing.InputVerifier

Eine andere Möglichkeit ist die Nutzung des normalen JTextFields mit InputVerifier. Dabei schreibt man sich eine eigene Klasse, die von der abstrakten Klasse javax.swing.InputVerifier abgeleitet ist. Dem JTextField hängt man dann mit der Methode setInputVerifier(javax.swing.InputVerifier iV) den Verifier an.
Wenn man einen Wert in ein Textfeld geschrieben hat und dieser korrekt ist erwartet man normalerweise, dass man ins nächste Textfeld springen kann. Mit dem InputVerifier kann man den Focus bei falscher Eingabe aber auf dem Textfeld belassen, so dass der Benutzer nicht ins nächste Feld kommt, solange die Eingabe falsch ist.
In der eigenen Klasse überschreibt man folgende Methoden:

public boolean verify (JComponent input)
  sollte keine Einflüsse auf die Komponente haben und TRUE zurückgeben, wenn das JTextField die Bedingung zur Weitergabe des Focus erfüllt und FALSE, wenn nicht
public boolean shouldYieldFocus (JComponent input)
  darf die Komponente beeinflussen und ruft verify(JComponent input) auf

##### Beispiel für einen InputVerifier: SpecialDateInputVerifier

    public class SpecialDateInputVerifier extends javax.swing.InputVerifier {
        public SpecialDateInputVerifier() {
            super();
        }

        public boolean verify(javax.swing.JComponent input) {
            javax.swing.JTextField jTF = (javax.swing.JTextField) input;
            String sInput = jTF.getText();
            //DateChecker ist eine Klasse, welche Daten auf Korrektheit prüft
            return myutil.DateChecker.checkDate (sInput);
        }

        public boolean shouldYieldFocus(javax.swing.JComponent input) {
            if (!verify(input)) {
                //Textfeld Vordergrund rot färben
                input.setForeground(java.awt.Color.RED);
                return false;
            }
            else {
                input.setForeground(java.awt.Color.BLACK);
                return true;
            }
        }
    }

Damit eine Komponente, also ein Textfeld überhaupt den Verifier aufruft muss ihre Eigenschaft verifyInputWhenFocusTarget auf TRUE gesetzt werden (Standardeinstellung).
Ein InputVerifier darf keine Meldung mit JOptionPane anzeigen, denn würde er es tun durchliefe er eine Endlosschleife.

#### Formatierung mit Hilfe einer Dokument-Klasse

Die Inhalte von Textfeldern werden in Java-Swing durch Objekte dargestellt, welche die Schnittstelle java.text.Document implementieren. Standardmäßig ist das ein Objekt der Klasse javax.swing.text.PlainDocument. Dieses allgemeine Document lässt alle Tastaturzeichen zu.
Will man nur bestimmte Zeichen zulassen, schreibt man sich eine eigene Klasse, die von javax.swing.text.PlainDocument abgeleitet ist und überschreibt die Methode insertString() und eventuell removeString(), dahingehend, dass nur die gewünschten Änderungen in das Dokument aufgenommen werden.

##### Beispiel für eine Dokumentenklasse für Dezimalzahlen

    public class DecimalDocument extends javax.swing.text.PlainDocument{

        public DecimalDocument() {  }

        public void insertString(int offset, String str, javax.swing.text.AttributeSet a)
        throws javax.swing.text.BadLocationException {
            //Dezimaltrenner, je nach Land abfragen und einsetzen
            char decimalSeparator = (new java.text.DecimalFormatSymbols()).getDecimalSeparator();
            //Zeichenkette mit den gültigen Zeichen
            String valid = "+-.0123456789" + decimalSeparator;
            for (int i=0; i<str.length();i++) {
                if (valid.indexOf(str.charAt(i)) == -1) {
                    //Beep
                    System.out.println("Falsches Zeichen");
                    java.awt.Toolkit.getDefaultToolkit().beep();
                    return;
                }
                //Wichtig Aufruf der übergeordneten Methode
                super.insertString(offset, str, a);
            }
        }
    }

##### Beispiel für eine insertString-Methode für Namen

     public void insertString(int offset, String str, javax.swing.text.AttributeSet a)
        throws javax.swing.text.BadLocationException{
            //Nur Zeichenketten zulassen, die nur aus Buchstaben bestehen und deren
            //1. Buchstabe ein Großbuchstabe ist und deren andere Buchstaben Klein sind

            if ((Character.isLowerCase(str.charAt(0))) & (offset ==0)) {
                System.out.println("Erster Buchstabe muss Groß sein, bsp: 'Valk, van der'");
                System.out.println("Offset: "+offset);
                return;
            }
            String disallowed = "+-:;!\"§$%&/()=?1234567890*/#~<>|";
            for (int i=0; i<str.length(); i++){
                if (disallowed.indexOf(str.charAt(i)) >= 0){
                    System.out.println("Falsches Zeichen im Namen");
                    return;
                }
                super.insertString(offset,str, a);
            }
        }