Tit   Inh   Ind   1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   <<   <   >   >> 

23.2 Entwicklung einer 7-Segment-Anzeige



23.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:

23.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 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 23.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 23.1: Der Aufbau der 7-Segment-Anzeige

23.2.3 Ereignisbehandlung

Wie in Kapitel 18 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 oben 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.

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 Type 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
Listing 23.1: Eine 7-Segment-Anzeige


 Tit   Inh   Ind   1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   <<   <   >   >> 
Go To Java 2, Addison Wesley, Version 1.0.2, © 1999 Guido Krüger, http://www.gkrueger.com