Autor: 'Kriz'
Wichtiger Hinweis!!!
Die Grundlagen über die Java-Programmierung ist nicht Bestandteil der Tutorialreihe! Zum Lernen von Java empfehle ich das hervorragende Online-Tutorial von Guido Krüger, welches man sich kostenlos von www.javabuch.de runterladen kann.
In diesem Tutorial geht es ein paar kleine, effektive Modifikationen an einer TextField Komponente des AWTs. Ich benötigte sie um ein wirklich kleines, hunzeliges Progrämmchen mit AWT zu schreiben. Im Großen und Ganzen war ich auf der Suche nach der Möglichkeit einer Zeichenbegrenzung und einer dynamischen Eingabekontrolle. Wer sich evtl. in C++ mit der Windowsprogrammierung unter MFC ein wenig auskennt, der weiß daß es für das dortige Textfeld CEdit ein paar schnuckelige Eventnachrichten gibt, die z.B. der Kontrolle während und direkt nach der Eingabe von Text dienen uvm. Da sich das AWT Eventsystem auf sehr einfache Eventnachrichten beschränkt (Swing kennt ein paar Events mehr), sind einige nützliche Events wie die angesprochenen Kontrollmechanismen entweder nur sehr spartanisch oder garnicht vorhanden. Aus diesem Grund musste ich mir mit ein wenig Hilfe aus dem Internet und Usegroups diese Dinge selber programmieren.
Folgende Modifikationen möchte ich euch hier erklären:
Die AWT Komponente TextField kann sich bei folgenden Eventhandlern registrieren, die nicht direkt aus dem Zusammenhang der Basisklasse Component entstammen: ActionListener und TextListener. Die (wichtigsten) Eventhandler direkt aus Component geerbt sind: KeyListener, MouseListener, MouseMotionListener, ComponentListener und FocusListener.
Beim ActionListener wird nur dann von einem registrierten TextField ein ActionEvent erzeugt, wenn der Benutzer innerhalb des TextFields die RETURN Taste drückt. Der TextListener dagegen reagiert bei jeder Veränderung des Textinhalts eines TextFields. Der KeyListener reagiert bei jedem Tastendruck während der Eingabe in einem TextField. Die restlichen Listener sind für dieses Tutorial irrelevant.
Ein großes Problem bei der Verwendung des TextListeners ist der sogenannte callback loop Effekt (der aber auch bei falscher Verwendung in allen anderen Listenern vorkommen kann!), bei dem aus Versehen infolge einer unglücklichen Programmsequenz immer und immer wieder ein TextEvent geworfen wird und somit quasi das gesamte Eventsystem sich extrem von der Eingabeinteraktivität des Programms abkoppelt: Andere Events werden fast nicht mehr behandelt, das Programm kann beim Klicken auf Schließen/Beenden fast 20 Sekunden oder mehr benötigen um sich zu beenden usw. Alles wird auf einmal sehr langsam…
Callback loops sind kritisch, denn sie deuten auf einen Designfehler hin, den der Programmierer selbst zu verantworten hat. Im Falle von TextListener passiert das immer dann, wenn man innerhalb der Eventmethode textValueChanged() aus dem Interface TextListener die regiszrierte Textkomponente abfängt und dann versucht einen neuen Text zu setzen. In jenem Moment wirft die Textkomponente wieder ein TextEvent, welches man abfängt und wo man wieder den Text ändert. Und sofort geht das Spiel wieder von vorne los! Folgender Code demonstriert so einen Fehler:
public void textValueChanged(TextEvent event) { // myTextField wurde mittels addTextListener() vorher registriert if((TextField)event.getSource() == myTextField) { myTextField.setText("Ooops o_O"); } }
Daher darf man niemals innerhalb einer Eventmethode etwas mit einer auf diesem Event registrierten Komponente machen, was unweigerlich dasselbe Event hervorrufen würde, sobald man die Komponente abfängt!
Soviel erstmal zum Verständnis über das Thema Events und callback loops. Kommen wir nun zur Vorstellung der ersten Modifikation.
Wenn man Text in ein TextField eingibt, dann wird die Anzahl der maximal eingebbaren Zeichen nicht vom AWT in irgendeiner Form limitiert, d.h. man kann quasi unbegrenzt viele Zeichen eingeben. Bei gewissen Anlässen ist das aber extrem uncool, z.B. wenn man ein TextField haben möchte, welches einen Kreiswinkel aufnehmen soll (das war z.B. mein Anliegen). Ein Kreiswinkel in Ganzzahlen ausgedrückt geht von 0° bis 360°, hat also maximal 3 Stellen. Mein Ziel war es die Eingabe beim Erreichen des dritten Zeichens zu unterdrücken.
Das Schlüsselevent hierzu war der KeyListener. Man registriert das TextField mittels addKeyListener() an ein vorhandeses KeyEvent-System (meistens das vom Elternfenster). Aus dem Interface KeyListener heraus muß man nun für ein funktionierendes KeyEvent-System drei Methoden konkretisieren:
Beim Druck auf eine (Zeichen-)Taste innerhalb des registrierten TextFields werden nun diese drei Methoden durch die Messagepump von Java in folgender Reihenfolge bedient: Beim Runterdrücken der Taste wird keyPressed() aufgerufen, beim Loslassen der Taste keyReleased() und exakt danach sofort keyTyped(). Weitere Details dazu sollte der Leser bitte dem Java-Tutorial von Guido Krüger (Link siehe am Anfang) entnehmen.
Nun gut, wie aber kann man nun Java dazu bringen, nur maximal 3 Zeichen bei der Eingabe zuzulassen? Dazu gibt es in der Klasse KeyEvent eine sehr nützliche Methode namens consume(). consume() „frisst“ einfach die aktuell eingegebene Taste, ehe Java sie in das TextField schreibt. Dadurch hat man die Möglichkeit, Zeichenausgaben auf dem TextField zu unterdrücken. Sowas muß bereits beim Drücken einer Taste passieren, daher kommt nur die Eventmethode keyPressed() in Frage! Lange Rede kurzer Sinn, hier ist ein erster Versuch:
public void keyPressed(KeyEvent e) { Object o = e.getSource(); if(o instanceof TextField) { TextField tf = (TextField)o; if(tf == myTextField) { if(tf.getText().length() >= 3) e.consume(); } } }
Anhand der Stringlänge des TextFields kann man nun abfragen, ob die Länge größer-gleich 3 Zeichen ist. Falls ja, so wird mittels consume() das aktuell eingegebene Zeichen „gefressen“ und es passiert weiter nichts mehr im TextField.
Leider hat dieser Versuch einen kleinen Schönheitsfehler: Sobald man drei Zeichen eingegeben hat, reagiert unser TextField sinngemäß auf keine Taste mehr, darunter auch solche Editiertasten wie BACKSPACE, DELETE, Links-Rechts-Cursortasten oder RETURN. Wir müssen also nochmals ran und die Methode etwas abändern:
public void keyPressed(KeyEvent e) { Object o = e.getSource(); if(o instanceof TextField) { TextField tf = (TextField)o; if(tf == myTextField) { int keycode = e.getKeyCode(); if(tf.getText().length() >= 3) { switch (keycode) { case KeyEvent.VK_ENTER: case KeyEvent.VK_BACK_SPACE: case KeyEvent.VK_DELETE: case KeyEvent.VK_LEFT: case KeyEvent.VK_RIGHT: return; default: break; } e.consume(); } } } }
Nun reagiert unser TextField auch jenseits der 3-Zeichen-Grenze auf die nötigen Editiertasten und führt sich auch aus. Alles andere dagegen wird nachwievor geschluckt und nicht mehr im TextField angezeigt. Dafür holen wir uns mittels der KeyEvent-Methode getKeyCode() den Zeichencode der gedrückten Taste (der Zeichencode ist nicht der ASCII- oder UNICODE-Code, sondern ein virtueller Tastencode). In einer switch-Abfrage testen wir nun auf die Editiertasten über ihre in KeyEvent fest definierten VK_ Codes (VK = virtual key). Wird einer der Codes gefunden, bricht die Methode mittels return einfach ab, d.h. die Taste wird ausgeführt. Alles andere wird nach dem switch-Konstrukt konsumiert. Man beachte, daß die RETURN Taste als VK_ENTER hinterlegt ist und nicht als VK_RETURN!
Das war's schon =)
Der zweite Teil befasst sich mit den Textinhalten eines TextFields und wie man diese dynamisch kontrollieren kann. Die Suche nach dieser Möglichkeit beruhte auf meinem Drang hin, das weiter oben angesprochene Kreiswinkel-TextField zu realisieren. Ich wollte, daß mein spezielles TextField folgendes tut:
Der erste Ansatz drängt nach dem TextListener, ist aber wegen dem bereits weiter oben erklärten Callback Loop Phänomen nicht realisierbar, da ich direkt in der Behandlungsmethode auch sofort den Textinhalt dynamisch ändern muß! Was also nun? Was bleibt übrig? Nun, wir bedienen uns anstatt des TextListeners wieder dem guten, alten KeyListeners. Diesmal beschäftigen wir uns hier mit der zweiten Evenmethode keyReleased(), die nach dem Loslassen einer Taste ausgelöst wird. Man wandelt den Zahlenstring um in einen Integerwert und prüft, ob dieser größer-gleich 360 ist. Falls ja, so wird der Modulo-360 Algorithmus auf den Wert ausgeführt und dieser dann wieder zurück als String in das TextField geschrieben. Als kosmetische Nachbehandlung setzen wir dann dynamisch das Caret (das ist der Zeichencursor) direkt hinter das letzte Zeichen im TextField. Damit hätten wir Punkt 1 und 2 abgehakt. Bleiben nur noch Punkt 3 und 4 übrig. Dazu bedienen wir uns dem Limitierungsalgorithmus aus dem ersten Tutorialteil und zwingen Java, nur noch Ziffernzeichen zu akzeptieren und das maximal dreimal hintereinander. Minus & Co. werden einfach „gefressen“ :)
Und so sieht das ganze dann aus:
public void keyPressed(KeyEvent e) { Object o = e.getSource(); if(o instanceof TextField) { TextField tf = (TextField)o; if(tf == myTextField) { int keycode = e.getKeyCode(); if(tf.getText().length() >= 3) { switch (keycode) { case KeyEvent.VK_ENTER: case KeyEvent.VK_BACK_SPACE: case KeyEvent.VK_DELETE: case KeyEvent.VK_LEFT: case KeyEvent.VK_RIGHT: return; default: break; } e.consume(); } else { switch (keycode) { case KeyEvent.VK_ENTER: case KeyEvent.VK_BACK_SPACE: case KeyEvent.VK_DELETE: case KeyEvent.VK_LEFT: case KeyEvent.VK_RIGHT: case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: return; default: break; } e.consume(); } } } } public void keyReleased(KeyEvent e) { Object o = e.getSource(); if(o instanceof TextField) { TextField tf = (TextField)o; if(tf == myTextField) { try { int degree = Integer.parseInt(tf.getText()); if(degree >= 360) { degree %= 360; tf.setText(String.valueOf(degree)); tf.setCaretPosition(tf.getText().length()); } } catch(NumberFormatException exc) { } } } }
Und das war's auch schon hier wieder :)
Hier ist nun ein Textprogramm für unser (bzw. mein *hust*) Kreiswinkel-TextField:
// Kreiswinkel-TextField Testprogramm import java.awt.*; import java.awt.event.*; public class Testprogramm extends Frame implements KeyListener { private TextField myTextField = null; public static void main(String[] args) { new Testprogramm(); } public Testprogramm() { super("Testprogramm"); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { quit(); } }); myTextField = new TextField(); myTextField.addKeyListener(this); add(myTextField); pack(); setLocationRelativeTo(null); setVisible(true); } public void keyPressed(KeyEvent e) { Object o = e.getSource(); if(o instanceof TextField) { TextField tf = (TextField)o; if(tf == myTextField) { int keycode = e.getKeyCode(); if(tf.getText().length() >= 3) { switch (keycode) { case KeyEvent.VK_ENTER: case KeyEvent.VK_BACK_SPACE: case KeyEvent.VK_DELETE: case KeyEvent.VK_LEFT: case KeyEvent.VK_RIGHT: return; default: break; } e.consume(); } else { switch (keycode) { case KeyEvent.VK_ENTER: case KeyEvent.VK_BACK_SPACE: case KeyEvent.VK_DELETE: case KeyEvent.VK_LEFT: case KeyEvent.VK_RIGHT: case KeyEvent.VK_0: case KeyEvent.VK_1: case KeyEvent.VK_2: case KeyEvent.VK_3: case KeyEvent.VK_4: case KeyEvent.VK_5: case KeyEvent.VK_6: case KeyEvent.VK_7: case KeyEvent.VK_8: case KeyEvent.VK_9: return; default: break; } e.consume(); } } } } public void keyReleased(KeyEvent e) { Object o = e.getSource(); if(o instanceof TextField) { TextField tf = (TextField)o; if(tf == myTextField) { try { int degree = Integer.parseInt(tf.getText()); if(degree >= 360) { degree %= 360; tf.setText(String.valueOf(degree)); tf.setCaretPosition(tf.getText().length()); } } catch(NumberFormatException exc) { } } } } public void keyTyped(KeyEvent e) { // Wird nicht gebraucht } private void quit() { setVisible(false); dispose(); System.exit(0); } }
Es gibt eine Unmenge an virtuellen Tastencodes in Java. Viele Bereiche kann man gebündelt abfragen, so z.B. A-Z oder 0-9, indem man mit einer if-Abfrage arbeitet. Bei der Beschränkung von Tastencodes sollte man äußerst vorsichtig vorgehen, sonst könnte die Textkomponente nicht mehr richtig reagieren. Einige Tastencodes sind außerdem von den Locale-Einstellungen abhängig, d.h. je nach Region sind bestimmte Tastencodes nicht verfügbar (z.B. japanische Sondertasten usw.). Ähnlich verhält es sich mit plattformspezifischen Tastencodes (z.B. SUN-/Macintosh-Tasten usw.).
Modifizierte TextField-Komponenten sollte man als eigene Klasse direkt von TextField ableiten und dort die Änderungen vor Ort vornehmen. Das erspart viel Zeit und außerdem entspricht das der OOP-Philosophie ;)
Dieses Tutorial stammt aus der ehemaligen Sammlung des resourcecode.de und konnte dank der freundlichen Zustimmung des Autors in das thewall-Wiki übertragen werden.