Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage |
<< | < | > | >> | API | Kapitel 33 - Eigene Dialogelemente |
In diesem Abschnitt wollen wir uns ein konkretes Beispiel zur Komponentenentwicklung ansehen. Dazu soll eine einstellige 7-Segment-Anzeige entwickelt werden, die in der Lage ist, die Ziffern 0 bis 9 darzustellen. Des weiteren sollen folgende Eigenschaften realisiert werden:
Die Architektur unserer Anzeigekomponente ist denkbar einfach. Wir definieren dazu eine neue Klasse Segment7, die aus Canvas abgeleitet wird. Segment7 besitzt eine Membervariable digit, die den aktuellen Anzeigewert speichert. Dieser kann mit den öffentlichen Methoden getValue und setValue abgefragt bzw. gesetzt werden. Die Klasse bekommt zwei Konstruktoren, die es erlauben, den Anzeigewert wahlweise bei der Instanzierung zu setzen oder die Voreinstellung 0 zu verwenden.
Segment7 überlagert die Methoden getPreferredSize und getMinimumSize der Klasse Component, um den Layoutmanagern die gewünschte Größe mitzuteilen:
public Dimension getPreferredSize() public Dimension getMinimumSize() |
java.awt.Component |
Beide Methoden liefern ein Objekt der Klasse Dimension, also ein rechteckiges Element mit einer Höhe und Breite. getPreferredSize teilt dem Layoutmanager mit, welches die gewünschte Größe der Komponente ist, und getMinimumSize gibt an, welches die kleinste akzeptable Größe ist. Der Layoutmanager FlowLayout beispielsweise verwendet getPreferredSize, um die Größe der Komponente zu bestimmen. GridLayout gibt die Größe selbst vor und paßt sie an die Gitterelemente an. Durch Aufruf von pack kann allerdings auch GridLayout dazu veranlaßt werden, die gewünschte Größe der Komponenten abzufragen und zur Dimensionierung des Fensters (und damit letztlich zur Dimensionierung der Einzelkomponenten) zu verwenden. Seit dem JDK 1.1 gibt es noch eine dritte Methode getMaximumSize, mit der die Komponente ihre maximale Größe mitteilen kann. Sie wird in unserem Beispiel nicht benötigt.
Zur Darstellung der Leuchtdiodenanzeige auf dem Bildschirm wird die Methode paint überlagert; in ihr befindet sich die Logik zur Darstellung der sieben Segmente (siehe Abbildung 33.1). In Segment7 wurden dazu drei Arrays digits, polysx und polysy definiert, die die Belegung der Segmente für jede einzelne Ziffer darstellen und die Eckpunkte jedes einzelnen Segments vorgeben.
paint verwendet das Array digits, um herauszufinden, welche Segmente zur Darstellung der aktuellen Ziffer verwendet werden. Für jedes der beteiligten Segmente wird dann aus den Arrays polysx und polysy das passende Polygon gebildet und mit fillPolygon angezeigt. Als interne Berechnungseinheit werden zwei Parameter dx und dy verwendet, die beim Aufruf von paint aus dem für die Komponente verfügbaren Platz berechnet werden.
Abbildung 33.1: Der Aufbau der 7-Segment-Anzeige
Wie in Kapitel 28 erwähnt, erfolgt die Ereignisbehandlung in selbstdefinierten Komponenten üblicherweise auf der Basis des vierten vorgestellten Architekturmodells. Bei diesem werden die Ereignisse nicht durch registrierte Listener-Klassen bearbeitet, sondern durch Überlagern der Methoden process...Event der Klasse Component.
Damit die Ereignisse tatsächlich an diese Methoden weitergegeben werden, müssen sie zuvor durch Aufruf von enableEvents und Übergabe der zugehörigen Ereignismaske aktiviert werden. Da wir Component-, Focus-, Key- und Mouse-Ereignisse behandeln wollen, rufen wir enableEvents mit den Konstanten AWTEvent.COMPONENT_EVENT_MASK, AWTEvent.FOCUS_EVENT_MASK, AWTEvent.MOUSE_EVENT_MASK und AWTEvent.KEY_EVENT_MASK auf. Beim Überlagern dieser Methoden sollte in jedem Fall der entsprechende Ereignishandler der Superklasse aufgerufen werden, um die korrekte Standard-Ereignisbehandlung sicherzustellen.
Die Reaktion auf Mausklicks wird durch Überlagern der Methode processMouseEvent realisiert. Hier wird zunächst überprüft, ob es sich um ein MOUSE_PRESSED-Ereignis handelt, also ob eine der Maustasten gedrückt wurde. Ist dies der Fall, wird dem Anzeigeelement durch Aufruf von requestFocus der Eingabefokus zugewiesen.
Anschließend wird überprüft, ob die [UMSCHALT]-Taste gedrückt wurde. Ist das der Fall, wird die Ereignisbehandlung beendet, andernfalls wird der Anzeigewert hoch- bzw. heruntergezählt, je nachdem, ob die rechte oder linke Maustaste gedrückt wurde. Am Ende von processMouseEvent wird in jedem Fall super.processMouseEvent aufgerufen, um sicherzustellen, daß die normale Ereignisbehandlung aufgerufen wird.
Ein selbstdefiniertes Dialogelement bekommt nicht automatisch den Fokus zugewiesen, wenn mit der Maus darauf geklickt wird. Statt dessen muß es selbst auf Mausklicks reagieren und sich - wie zuvor beschrieben - durch Aufruf von requestFocus selbst den Fokus zuweisen. Bei jeder Fokusänderung wird ein Focus-Event ausgelöst, das wir durch Überlagern der Methode processFocusEvent bearbeiten. Hier unterscheiden wir zunächst, ob es sich um ein FOCUS_GAINED- oder FOCUS_LOST-Ereignis handelt, und setzen eine interne Statusvariable hasfocus entsprechend. Diese wird nach dem anschließenden Aufruf von repaint verwendet, um in paint durch Modifikation der Anzeigefarbe ein visuelles Feedback zu geben. Hat ein Element den Fokus, so ist die Farbe der Anzeigesegmente gelb, andernfalls rot.
Seit dem JDK 1.1 gibt es einen Mechanismus, der es erlaubt, mit den Tasten [TAB] und [UMSCHALT]+[TAB] zwischen den Eingabefeldern eines Dialogs zu wechseln. Genauer gesagt wird dadurch der Fokus an das nächste Element weiter- bzw. zum vorigen zurückgegeben. Da diese Vorgehensweise nicht bei jedem Dialogelement sinnvoll ist, kann das Dialogelement sie durch Überlagern der Methode isFocusTraversable selbst bestimmen. Liefert isFocusTraversable den Rückgabewert true, so nimmt das Objekt an der [TAB]-Behandlung teil, andernfalls nicht. Die Klasse Segment7 überlagert isFocusTraversable und gibt true zurück. So kann mit [TAB] und [UMSCHALT]+[TAB] wie besprochen zwischen den 7-Segment-Anzeigen gewechselt werden.
Mit der Version 1.4 des JDK wurde isFocusTraversable als deprecated markiert, und sollte durch isFocusable ersetzt werden. Wegen der großen Anzahl vorhandener Bugs und Inkonsistenzen wurde nämlich die Architektur des Fokus-Subsystems vollständig überarbeitet und mit einem teilweise neuen API versehen. Programmcode, der mit der Fokussierung von AWT- oder Swing-Komponenten zu tun hat, muß daher beim Umstieg auf das JDK 1.4 vermutlich überarbeitet werden. |
|
Ein Dialogelement enthält nur dann Tastatureingaben, wenn es den Fokus hat. Durch den zuvor beschriebenen Mechanismus des Aufrufs von requestFocus stellen wir sicher, daß nach einem Mausklick bzw. nach dem Wechsel des Fokus mit [TAB] und [UMSCHALT]+[TAB] Tastaturereignisse an das Element gesendet werden. Diese werden durch Überlagern der Methode processKeyEvent behandelt. Wir überprüfen darin zunächst, ob das Ereignis vom Typ KEY_PRESSED ist, und besorgen dann mit getKeyChar den Wert der gedrückten Taste. Ist er '+', so wird der Anzeigewert um 1 erhöht, bei '-' entsprechend verringert. Wurde eine der Zifferntasten gedrückt, so erhält das Anzeigeelement diesen Wert. Anschließend wird durch Aufruf von repaint die Anzeige neu gezeichnet.
Ein Component-Ereignis brauchen wir in unserem Beispiel nur, damit wir dem Dialogelement unmittelbar nach der Anzeige auf dem Bildschirm den Fokus zuweisen können. Dazu überlagern wir die Methode processComponentEvent und überprüfen, ob das Ereignis vom Typ COMPONENT_SHOWN ist. In diesem Fall wird requestFocus aufgerufen, andernfalls passiert nichts.
Damit ist die Konstruktion der Komponente auch schon abgeschlossen. Durch die Definition von getPreferredSize und getMinimumSize und die automatische Skalierung in der paint-Methode verhält sich unsere neue Komponente so, wie es die Layoutmanager von ihr erwarten. Daher kann sie wie eine vordefinierte Komponente verwendet werden. Hier ist der Quellcode von Segment7:
001 /* Segment7.java */ 002 003 import java.awt.*; 004 import java.awt.event.*; 005 006 class Segment7 007 extends Canvas 008 { 009 private int digit; 010 private boolean hasfocus; 011 private int[][] polysx = { 012 { 1, 2, 8, 9, 8, 2}, //Segment 0 013 { 9,10,10, 9, 8, 8}, //Segment 1 014 { 9,10,10, 9, 8, 8}, //Segment 2 015 { 1, 2, 8, 9, 8, 2}, //Segment 3 016 { 1, 2, 2, 1, 0, 0}, //Segment 4 017 { 1, 2, 2, 1, 0, 0}, //Segment 5 018 { 1, 2, 8, 9, 8, 2}, //Segment 6 019 }; 020 private int[][] polysy = { 021 { 1, 0, 0, 1, 2, 2}, //Segment 0 022 { 1, 2, 8, 9, 8, 2}, //Segment 1 023 { 9,10,16,17,16,10}, //Segment 2 024 {17,16,16,17,18,18}, //Segment 3 025 { 9,10,16,17,16,10}, //Segment 4 026 { 1, 2, 8, 9, 8, 2}, //Segment 5 027 { 9, 8, 8, 9,10,10}, //Segment 6 028 }; 029 private int[][] digits = { 030 {1,1,1,1,1,1,0}, //Ziffer 0 031 {0,1,1,0,0,0,0}, //Ziffer 1 032 {1,1,0,1,1,0,1}, //Ziffer 2 033 {1,1,1,1,0,0,1}, //Ziffer 3 034 {0,1,1,0,0,1,1}, //Ziffer 4 035 {1,0,1,1,0,1,1}, //Ziffer 5 036 {1,0,1,1,1,1,1}, //Ziffer 6 037 {1,1,1,0,0,0,0}, //Ziffer 7 038 {1,1,1,1,1,1,1}, //Ziffer 8 039 {1,1,1,1,0,1,1} //Ziffer 9 040 }; 041 042 public Segment7() 043 { 044 this(0); 045 } 046 047 public Segment7(int digit) 048 { 049 super(); 050 this.digit = digit; 051 this.hasfocus = false; 052 enableEvents(AWTEvent.COMPONENT_EVENT_MASK); 053 enableEvents(AWTEvent.FOCUS_EVENT_MASK); 054 enableEvents(AWTEvent.MOUSE_EVENT_MASK); 055 enableEvents(AWTEvent.KEY_EVENT_MASK); 056 } 057 058 public Dimension getPreferredSize() 059 { 060 return new Dimension(5*10,5*18); 061 } 062 063 public Dimension getMinimumSize() 064 { 065 return new Dimension(1*10,1*18); 066 } 067 068 public boolean isFocusTraversable() 069 { 070 return true; 071 } 072 073 public void paint(Graphics g) 074 { 075 Color darkred = new Color(127,0,0); 076 Color lightred = new Color(255,0,0); 077 Color yellow = new Color(255,255,0); 078 //dx und dy berechnen 079 int dx = getSize().width / 10; 080 int dy = getSize().height / 18; 081 //Hintergrund 082 g.setColor(darkred); 083 g.fillRect(0,0,getSize().width,getSize().height); 084 //Segmente 085 if (hasfocus) { 086 g.setColor(yellow); 087 } else { 088 g.setColor(lightred); 089 } 090 for (int i=0; i < 7; ++i) { //alle Segmente 091 if (digits[digit][i] == 1) { 092 Polygon poly = new Polygon(); 093 for (int j = 0; j < 6; ++j) { //alle Eckpunkte 094 poly.addPoint(dx*polysx[i][j],dy*polysy[i][j]); 095 } 096 g.fillPolygon(poly); 097 } 098 } 099 //Trennlinien 100 g.setColor(darkred); 101 g.drawLine(0,0,dx*10,dy*10); 102 g.drawLine(0,8*dy,10*dx,18*dy); 103 g.drawLine(0,10*dy,10*dx,0); 104 g.drawLine(0,18*dy,10*dx,8*dy); 105 } 106 107 public int getValue() 108 { 109 return digit; 110 } 111 112 public void setValue(int value) 113 { 114 digit = value % 10; 115 } 116 117 protected void processComponentEvent(ComponentEvent event) 118 { 119 if (event.getID() == ComponentEvent.COMPONENT_SHOWN) { 120 requestFocus(); 121 } 122 super.processComponentEvent(event); 123 } 124 125 protected void processFocusEvent(FocusEvent event) 126 { 127 if (event.getID() == FocusEvent.FOCUS_GAINED) { 128 hasfocus = true; 129 repaint(); 130 } else if (event.getID() == FocusEvent.FOCUS_LOST) { 131 hasfocus = false; 132 repaint(); 133 } 134 super.processFocusEvent(event); 135 } 136 137 protected void processMouseEvent(MouseEvent event) 138 { 139 if (event.getID() == MouseEvent.MOUSE_PRESSED) { 140 requestFocus(); 141 if (!event.isShiftDown()) { 142 if (event.isMetaDown()) { 143 setValue(getValue() + 1); //increment by 1 144 } else { 145 setValue(getValue() + 9); //decrement by 1 146 } 147 } 148 repaint(); 149 } 150 super.processMouseEvent(event); 151 } 152 153 protected void processKeyEvent(KeyEvent event) 154 { 155 if (event.getID() == KeyEvent.KEY_PRESSED) { 156 char key = event.getKeyChar(); 157 if (key >= '0' && key <= '9') { 158 setValue(key - '0'); 159 repaint(); 160 } else if (key == '+') { 161 setValue(getValue() + 1); //increment by 1 162 repaint(); 163 } else if (key == '-') { 164 setValue(getValue() + 9); //decrement by 1 165 repaint(); 166 } 167 } 168 super.processKeyEvent(event); 169 } 170 } |
Segment7.java |
Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0.1 |
<< | < | > | >> | API | © 1998-2003 Guido Krüger, http://www.javabuch.de |