D) Text in JTextField vorblenden
Sie kennen diese Funktionalität aus vielen Bereichen: Sie tippen etwas in ein Eingabefeld und das Programm schlägt Ihnen automatisch einen Text vor. Bspw. bei der Autovervollständigung in der Browser-Adressleiste oder in vielen Textverarbeitungsprogrammen bei der Eingabe des aktuellen Datums. Leider gibt es eine solche Eingabemöglichkeit nicht im Standard-Java. Sie müssen selbst Hand anlegen.
Unser Ziel in diesem Kapitel ist es, ein javax.swing.JTextField
so zu manipulieren, dass bei der Eingabe eines Textes vordefinierte Vorschläge unterbreitet werden.
JTextField
erbt. Nennen wir sie JSuggestionTextField
. In dieser Klasse wird ein Set
angelegt, in welchem die möglichen Vorschläge gespeichert werden. Wir verwenden in diesem Fall ein Set
, da es keinen Sinn macht, mehrere identische Vorschläge bereit zu halten. Gleichzeitig werden noch Methoden zur Manipulation dieser Vorschläge benötigt. Um eine möglichst hohe Flexibilität zu gewährleisten, erwartet diese jedoch eine Collection
als Parameter.
public class JSuggestionTextField extends JTextField { private static final long serialVersionUID = -7240368338330757960L; private Set<String> data = null; public JSuggestionTextField() { this.data = new TreeSet<String>(); addKeyListener(this); } public void setData(Collection<String> data) { this.data.clear(); for (String str : data) { addData(str); } } public void addData(String str) { if (str == null) { throw new NullPointerException("Can't add a null value"); } this.data.add(str); } public void deleteData(String str) { this.data.remove(str); } }
Dies ist soweit noch kein Hexenwerk. Auch der nächste Schritt sollte für Sie nachvollziehbar sein. Sie benötigen eine Methode, die nach jeder Texteingabe aufgerufen wird, und überprüft, ob der Inhalt des Textfeldes dem Anfang einer Zeichenkette Ihrer Vorschläge entspricht. Falls dies zutrifft, wird der Textinhalt mit dem Vorschlag ersetzt, und der „Rest“ des Vorschlags zum Überschreiben markiert.
private void quickSearch(String start) { for (String str : this.data) { if (str.startsWith(start)) { setText(str); select(start.length(), str.length()); break; } } }
Damit Sie überhaupt auf Texteingaben des Users reagieren können, müssen Sie an Ihrem JSuggestionTextField
einen java.awt.event.KeyListener
registrieren und die entsprechenden Methoden überschreiben. Zusätzlich legen Sie noch eine handleKey(char key)
Methode an, die sich um die Verarbeitung der gedrückten Taste kümmert.
public class JSuggestionTextField extends JTextField implements KeyListener { // vorhergehende Implementationen private void handleKey(char key) {} public void keyPressed(KeyEvent key) { handleKey(key.getKeyChar()); } public void keyTyped(KeyEvent key) {} public void keyReleased(KeyEvent key) {} }
Wenden wir uns der Implementierung der handleKey(char key)
Methode zu. Zuerst sollten Sie sich den momentan selektierten Text holen.
String selection = getSelectedText();
Auch ist es nur sinnvoll dem User einen Text vorzuschlagen, wenn er entweder gerade etwas tippt, der Cursor sich also an der letzten Position des Textes befindet, oder bereits vom Programm etwas vorgeschlagen wurde. Der Einfachheit halber gehen wir davon aus, dass dies immer dann zutrifft, wenn die Selektion den letzten Buchstaben im JSuggestionTextField
einschließt. Dies lässt sich mit einer einzigen Codezeile überprüfen:
if (getSelectionEnd() == getText().length()) {
Nun gibt es drei Möglichkeiten, die Sie in dieser If-Abfrage berücksichtigen müssen.
1.) Entspricht der gedrückte Buchstabe dem nächsten Zeichen der Selektion? In diesem Fall wird der Start der Selektion einfach um eine Position nach hinten verschoben.
if (selection != null && selection.charAt(0) == key) { setSelectionStart(getSelectionStart() + 1); }
2.) Falls der gedrückte Buchstabe nicht dem nächsten Zeichen entspricht, aber generell etwas selektiert ist, wird die Selektion gelöscht und gleichzeitig eine Suche angestoßen, ob für diese neue Eingabe ebenfalls ein Vorschlag unterbreitet werden kann. Hierzu wird die Methode quickSearch(String start)
mit dem nicht selektierten Text im JSuggestionTextField
+ der gerade gedrückten Taste aufgerufen.
else if (selection != null) { String nonSelected = getText().substring(0, getText().length() - selection.length()) + key; quickSearch(nonSelected); }
3.) Falls das alles nicht zutrifft, konnte zur bisherigen Eingabe kein Vorschlag unterbreitet werden. In diesem Fall wird quickSearch
mit der aktuellen Eingabe aufgerufen. Evtl. kann ja nun ein Text vorgeschlagen werden.
else { quickSearch(getText() + key); }
Beim Testen der Klasse, fällt noch ein Fehler auf: Nachdem die Selektion aktualisiert wurde, wird selbige einfach überschrieben. Dies liegt daran, dass nach einem keyPressed(KeyEvent key)
Aufruf noch keyTyped(KeyEvent key)
aufgerufen wird, wodurch der Inhalt des eigentlichen JTextFields
angepasst wird. Um dies zu unterbinden, benötigen wir ein weiteres Attribut. Dieses Attribut wird auf true
gesetzt, wenn die gedrückte Taste dem nächsten Zeichen der Selektion entspricht, oder ein neuer Vorschlag unterbreitet werden konnte. In der keyTyped(KeyEvent key)
Methode muss in diesem Fall die weitere Verarbeitung unterbunden werden.
public class JSuggestionTextField extends JTextField implements KeyListener { private boolean nextCharIsSuggestionChar = false; // restliche Implementierungen private void quickSearch(String start) { for (String str : this.data) { if (str.startsWith(start)) { setText(str); select(start.length(), str.length()); this.nextCharIsSuggestionChar = true; break; } } } private void handleKey(char key) { String selection = getSelectedText(); if (getSelectionEnd() == getText().length()) { if (selection != null && selection.charAt(0) == key) { this.nextCharIsSuggestionChar = true; setSelectionStart(getSelectionStart() + 1); } else if (selection != null) { String nonSelected = getText().substring(0, getText().length() - selection.length()) + key; quickSearch(nonSelected); } else { quickSearch(getText() + key); } } } public void keyTyped(KeyEvent key) { if (this.nextCharIsSuggestionChar) { key.consume(); this.nextCharIsSuggestionChar = false; } } }
Die komplette Klasse sieht nun wie folgt aus:
package de.jbb.tools; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.Collection; import java.util.Set; import java.util.TreeSet; import javax.swing.JTextField; public class JSuggestionTextField extends JTextField implements KeyListener { private static final long serialVersionUID = -7240368338330757960L; private Set<String> data = null; private boolean nextCharIsSuggestionChar = false; public JSuggestionTextField() { this.data = new TreeSet<String>(); addKeyListener(this); } public void setData(Collection<String> data) { this.data.clear(); for (String str : data) { addData(str); } } public void addData(String str) { if (str == null) { throw new NullPointerException("Can't add a null value"); } this.data.add(str); } public void deleteData(String str) { this.data.remove(str); } private void quickSearch(String start) { for (String str : this.data) { if (str.startsWith(start)) { setText(str); select(start.length(), str.length()); this.nextCharIsSuggestionChar = true; break; } } } private void handleKey(char key) { String selection = getSelectedText(); if (getSelectionEnd() == getText().length()) { if (selection != null && selection.charAt(0) == key) { this.nextCharIsSuggestionChar = true; setSelectionStart(getSelectionStart() + 1); } else if (selection != null) { String nonSelected = getText().substring(0, getText().length() - selection.length()) + key; quickSearch(nonSelected); } else { quickSearch(getText() + key); } } } public void keyTyped(KeyEvent key) { if (this.nextCharIsSuggestionChar) { key.consume(); this.nextCharIsSuggestionChar = false; } } public void keyPressed(KeyEvent key) { handleKey(key.getKeyChar()); } public void keyReleased(KeyEvent key) {} }
Um das JSuggestionTextField
zu testen, kann folgender Code verwendet werden:
JFrame frame = new JFrame(); JSuggestionTextField stf = new JSuggestionTextField(); stf.addData("Test Eintrag"); stf.addData("Noch ein Test Eintrag"); stf.addData("Test Eintrag 2"); stf.addData("Juhuuu"); stf.addData("Juhuuu"); stf.addData("Das ist nur ein Vorschlag"); frame.add(stf); frame.setSize(200, 50); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true);
Ein weiteres Beispiel zu diesem Thema finden Sie im Wiki auf Byte-Welt:
JTextField mit Prompt oder Eingabehinweis
Wieso reicht if (getSelectionEnd() == getText().length()) aus als Abfrage?
Am Anfang wenn wir den ersten Buchstaben eintippen ist doch garnichts makiert. Es wird doch erst ab dann relevant wenn etwas gefunden worden ist und vorgeschlagen wurde.
Also müsste doch bei der ersten Eingabe if (getSelectionEnd() == getText().length()) false sein und nichts machen. Oder ist von vornerein immer der erste Buchstabe makiert?
Hallo Marcel,
wenn noch nichts im Textfeld eingetragen wurde, folglich nichts selektiert ist und der Cursor an der „0ten“ Stelle im Textfeld blinkt, ist auch die Text-Länge 0 sowie das Ende der nicht vorhandenen Selektion.
Grüße
Stefan