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);
        }
    }