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

## 33.2 Entwicklung einer 7-Segment-Anzeige

- 33.2 Entwicklung einer 7-Segment-Anzeige
  - 33.2.1 Anforderungen
  - 33.2.2 Bildschirmanzeige
  - 33.2.3 Ereignisbehandlung

### 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:

- Die Anzeige soll in der Größe skalierbar sein und sich zu allen Layoutmanagern kompatibel verhalten.
- Die 7-Segment-Anzeige soll auf Mausklicks reagieren. Nach einem Mausklick soll sie den Fokus erhalten. Nach Drücken der linken Maustaste soll der Anzeigewert herunter- und nach Drücken der rechten Maustaste heraufgezählt werden. Wird während des Mausklicks die `[UMSCHALT]`-Taste gedrückt, so soll lediglich der Fokus zugewiesen werden, der Wert aber unverändert bleiben.
- Der Anzeigewert soll durch Drücken der Tasten `[+]` und `[-]` herauf- bzw. heruntergezählt werden. Wird eine Zifferntaste gedrückt, so soll die Anzeige auf diesen Wert gesetzt werden.
- Eine Anzeige, die den Fokus hat, soll visuell von einer ohne Fokus unterscheidbar sein. Der Fokus soll weiterhin - wie unter Windows üblich - mit Hilfe der Tasten `[TAB]` und `[UMSCHALT]`+`[TAB]` von einem Dialogelement zum nächsten weitergereicht werden können.

### 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 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

### 33.2.3 Ereignisbehandlung

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. |  |
JDK1.1-1.4

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

Listing 33.1: Eine 7-Segment-Anzeige

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de