Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage
 <<    <     >    >>   API  Kapitel 33 - Eigene Dialogelemente

33.2 Entwicklung einer 7-Segment-Anzeige



33.2.1 Anforderungen

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:

33.2.2 Bildschirmanzeige

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 passt sie an die Gitterelemente an. Durch Aufruf von pack kann allerdings auch GridLayout dazu veranlasst werden, die gewünschte Größe der Komponenten abzufragen und zur Dimensionierung des Fensters (und damit letztlich zur Dimensionierung der Einzelkomponenten) zu verwenden. Daneben 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

33.2.3 Ereignisbehandlung

Wie in Abschnitt 28.2.4 erwähnt, erfolgt die Ereignisbehandlung in selbst definierten 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 requestFocusInWindow 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, dass die normale Ereignisbehandlung aufgerufen wird.

Ein selbst definiertes Dialogelement bekommt nicht automatisch den Fokus zugewiesen, wenn mit der Maus darauf geklickt wird. Stattdessen muss es selbst auf Mausklicks reagieren und sich - wie zuvor beschrieben - durch Aufruf von requestFocusInWindow 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.

Im JDK 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 isFocusable selbst bestimmen. Liefert isFocusable den Rückgabewert true, so nimmt das Objekt an der [TAB]-Behandlung teil, andernfalls nicht. Die Klasse Segment7 überlagert isFocusable und gibt true zurück. So kann mit [TAB] und [UMSCHALT]+[TAB] wie besprochen zwischen den 7-Segment-Anzeigen gewechselt werden.

Ein Dialogelement enthält nur dann Tastatureingaben, wenn es den Fokus hat. Durch den zuvor beschriebenen Mechanismus des Aufrufs von requestFocusInWindow stellen wir sicher, dass 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 requestFocusInWindow 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 isFocusable()
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       requestFocusInWindow();
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       requestFocusInWindow();
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
Listing 33.1: Eine 7-Segment-Anzeige


 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0
 <<    <     >    >>   API  © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de