10. GUI und Events

 

 

Haben Sie's bermerkt? Bis jetzt konnte die Benutzerin Ihrer Applets nie eingreifen in das Geschehen im Applet. Das soll sich jetzt ändern!

Damit Benutzer etwas bewirken können, muss das Applet Menüs, Tasten, Auswahllisten, Eingabefelder, etc. haben. Das alles nennt man GUI (Graphical User Interface). Sie haben schon mal ein User Interface gehabt, aber das war gar nicht "graphical" - erinnern Sie sich an Console.class? Das war bisher die einzige Möglichkeit für Eingaben in ein Programm. Wie damals versprochen soll das jetzt komfortabler werden.

Für's Erste beschränken wir uns auf eine Taste (= Button), die man anklicken kann. Buttons etc. sind als Klassen im Package awt vorhanden. Wenn wir eine Taste brauchen, machen wir also einfach eine Instanz der Klasse Button. Ich habe vorgesehen, dass Sie die Variable für diese Tasten - Instanz als field der Appletklasse deklarieren, so dass sie im ganzen Applet bekannt ist. Das sieht dann z.B. folgendermassen aus:

  • Erstellen Sie ein neues Basic - Applet - Projekt.
  • Ergänzen Sie das Applet:

    /**
    * @(#)Events.java
    *
    * Applet mit Button
    *
    * @author Sö
    * @version 1.00 04/10/08
    */

    import java.awt.*;
    import java.applet.*;

    public class Events extends Applet {

      Button taste;

      public void init() {
        taste = new Button("drück' mich!");
        add(taste);
      }

      public void paint(Graphics g) {
      }
    }

  • Übersetzen Sie und führen Sie aus und seien Sie nicht erstaunt, wenn beim Drücken der Taste nichts passiert!

Die Methode add() ist eine Methode einer Superklasse von Applet und ermöglicht es, diesem ein grafisches Element zuzufügen - hier eben die Taste.

Haben Sie übrigens bemerkt, dass wir nicht wie üblich die Methode paint(), sondern init() programmieren. Init() ist eine Methode der Klasse Applet. Sie wird aufgerufen, wenn das Applet gestartet wird. Unser Applet "Events" ist ja abgeleitet von der Klasse Applet ("extends!) und erbt damit alle Methoden der Klasse Applet. Wenn Sie wissen wollen, was die Klasse Applet sonst noch für Methoden hat: Klicken Sie auf das Wort Applet, drücken Sie die Tasten <Ctrl>+<F1> und suchen Sie den Abschnitt "Method Detail". Geerbte Methoden können wir (meist) neu definieren oder überschreiben, wie man sagt. Genau das haben Sie soeben gemacht: Die Methode init() von Applet wird zwar immer beim Start aufgerufen, sie macht aber gar nichts. Wenn also beim Start des Applet etwas geschehen soll, so müssen wir die Methode init() überschreiben.

Die Methode paint(), die wir früher verwendet (überschrieben) haben, stammt nicht von Applet, sondern von einer ihrer Superklassen. Sie wird immer dann aufgerufen, wenn der Grafikbereich - in unserem Fall das Applet - neu dargestellt werden muss. Das braucht nicht nur der Start zu sein, es ist auch denkbar, dass das Applet verdeckt war und nun wieder sichtbar wird, dass die Benutzerin es vergrössert oder verkleinert und, für uns sehr wichtig: die Methode repaint() erzwingt, dass die Methode paint() aufgerufen wird. Dazu später mehr.


Events

Wieder ein Beispiel aus dem Alltag: Wenn es knallt, dann schaue ich herum um zu sehen, was passiert ist. Das Knallen ist ein Ereignis (= event), ich bin eine Instanz einer Klasse (das ist zwar etwas despektierlich, aber da ich mich selber als Beispiel nehme, werden Sie mich gewähren lassen). Ein Objekt kann also auf Events reagieren. Kann, denn wenn ich keine Ohren habe, dann geht's nicht. Wir müssen auch in Java "Ohren" machen, die Events wahrnehmen können. Sie heissen Listener. Es gibt verschiedenste Listener, so wie wir auch verschiedene Sinne haben, mit denen wir auf "Events" reagieren können.

Listener sind wiederum Klassen (um genau zu sein: Es sind nicht Klassen, sondern Interfaces, aber dazu später mehr). Sie befinden sich im Package java.awt.event - also müssen wir dieses importieren. Die Standard - Listener tun nichts, also müssen wir von diesen eigene Klassen ableiten. Wir könnten also neben unserer Klasse Events eine Listener - Klasse erstellen, so wie Sie früher die Klasse MyColor erstellt haben, und dann davon eine Instanz machen. Das wäre aber wie mit Kanonen auf Spatzen geschossen: Wir brauchen diese kleine Listener - Klasse nur um den Event abzufangen. Seit dem JDK 1.1 gibt es innere Klassen (inner classes). Das sind Klassen, welche in eine andere Klasse verpackt sind. Und diese dürfen sogar anonym, also namenlos, sein. Genau solche anonymous classes verwendet man meist um Listener zu realisieren: sie werden direkt in den Methode verpackt, die einem GUI Element einen Listener zufügt. In unserem Falle heisst diese Methode addActionListener() und wir übergeben ihr unsere anonyme Klasseninstanz als Parameter in der runden Klammer:

  • Importieren Sie das Package java.awt.event und ergänzen Sie die Methode init() nach add() folgendermassen und testen Sie (beachten Sie das schwarze Fenster, wenn Sie die Taste drücken!):

taste.addActionListener(new ActionListener() {
   public void actionPerformed (ActionEvent e)
   {
      System.out.println("Die Taste wurde gedrückt!");
   }
  }
);

Dieser ganze Quelltextausschnitt ist eigentlich nur der Aufruf der Methode addActionListener() ! Aber in der (runden) Parameter - Klammer, die in der ersten Zeile beginnt und in der letzten endet, geschieht einiges: mit "new" wird eine Instanz einer anonymen Klasse vom Typ ActionListener erzeugt, die dann auch gleich nach der ersten geschweiften Klammer ausprogrammiert wird. Sie überschreibt die Methode actionPerformed() der Klasse ActionListener. Diese wird aufgerufen, wenn der Event stattgefunden hat. Der Event selber ist wieder eine Klasse und heisst in unserem Falle ActionEvent. Sie wird der Methode als Parameter übergeben und wir könnten sie in der Methode weiter verwenden - dazu wieder mal später!

 

 
 
  • Nun soll der Tastenklick etwas schlaueres bewirken: Die Hintergrundfarbe soll von rot auf grün wechseln. Deklarieren Sie dazu ein Klassenfeld vom Typ Color, machen Sie die rote Instanz in der Methode init(), setzen Sie sie in der Methode actionPerformed() auf grün, erzwingen Sie danach ein Neuzeichnen des Applet mit repaint(); und ergänzen Sie die Methode paint() so, dass die Hintergrundfarbe auf Ihre Instanz gesetzt wird.
  • Ihr Applet kann also nur einmal von rot auf grün wechseln. Schaffen Sie's, dass jeder Tastenklick die Farbe wechselt? Hinweis: Eine Variable vom Typ boolean könnte sich merken ob die Farbe rot oder grün ist. Eine boolean Variable kann die Werte true oder false annehmen.

Damit haben Sie eine GUI Komponente, den Button, etwas kennengelernt. Es gibt eine ganze Reihe weiterer Komponenten und auch Elemente, die Komponenten in sich aufnehmen und damit gruppieren können (Container). Es hat keinen Sinn, dass wir nun in aller Ausführlichkeit Element um Element besprechen. Die nachfolgende Tabelle soll Ihnen einen Überblick über die Möglichkeiten geben. Wenn Sie ein Element verwenden wollen, schreiben Sie den Klassennamen in den Editor, klicken Sie drauf, drücken Sie <F1> und Sie sehen alle Details zum Element. Am Ende des Kapitels schnuppern Sie dann noch etwas im GUI und den Events herum.

Die GUI Elemente, welche hier vorgestellt werden, gehören übrigens zum "klassischen" Teil des JDK. Mit Java 2 wurde eine Oberflächengestaltung namens Swing geschaffen, die viel mehr Möglichkeiten bietet, entsprechend aber auch viel komplexer zu handhaben ist. Swing werden wir in einem speziellen Kapitel behandeln.

Die GUI - Elemente auf einen Blick:

Klasse Beschreibung
Komponenten:  
Button Taste, die man anklicken kann
Canvas Blanker Bereich, auf den man zeichnen kann
Checkbox Wähl- oder abwählbares "Gutzeichen"
CheckboxGroup Checkboxes werden zusammengefasst, so dass nur eine wählbar ist  (= "Radiobuttons")
Choice Ausklappbare Liste mit einzeiligen Texten zur Auswahl
Component Mutterklasse aller Komponentern
FileDialog Dialogfenster zur Auswahl von Dateien
Label Einzeiliger Text, vom Benutzer nicht veränderbar
List Liste mit einzeiligen Texten zur Auswahl
Scrollbar Schieberegler zum Scrollen
TextArea Mehrzeiliger Bereich für die Ausgabe und Eingabe von Text
TextField Einzeiliger Bereich für die Ausgabe und Eingabe von Text
Menükomponenten:  
CheckboxMenuItem Menüpunkt mit Checkbox
Menu Ausklappbarer Menübereich
MenuBar Menübalken, der die ausklappbaren Menübereiche enthält
MenuComponent Mutterklasse aller Menükomponenten
MenuItem Menüpunkt 
PopupMenu Mit der Maus hervorholbarer Menübereich
Container: (enthalten weitere Container oder Komponenten) 
Container Mutterklasse aller Container
Dialog Einfaches Fenster ohne Menübalken
Frame Vollständiges Fenster mit Rahmen, kann Menübalken aufnehmen
Panel Container, der selber in einem Container ist, geeignet zum Gruppieren von Komponenten
ScrollPane Container, scrollbar, mit Scrollbalken 
Window Leeres Fenster ohne Rahmen und Menübalken, geeignet für PopupMenu

Und nun noch eine Spielerei mit Events. Bisher haben Sie ja nur den ActionEvent, der von einem Tastenklick produziert wird, kennengelernt. Der folgende Quelltext soll Sie in einen andern Event einführen. Er stellt das einfachste Zeichnungsprogramm aller Zeiten dar, der Embryo von Corel Draw sozusagen!

  • Erstellen Sie ein neues Basic - Applet - Projekt und ändern Sie ab und testen Sie:

    **
    * @(#)Zeichnen.java
    *
    * Einfachstes Zeichnungsprogramm
    * aller Zeiten!
    *
    * @author Sö
    * @version 1.00 04/10/08
    */

    import java.awt.*;
    import java.awt.event.*;
    import java.applet.*;

    public class Zeichnen extends Applet implements MouseMotionListener{

      public void init() {
        addMouseMotionListener(this);
      
    }

      public void mouseDragged(MouseEvent e) {
        Graphics g= this.getGraphics();
        int x = e.getX();
        int y = e.getY();
        g.drawLine(x,y,x,y);
      }

      public void mouseMoved(MouseEvent e) {
      }
    }

Schon im Header der Klasse Zeichnen gibt's was Neues: "implements MouseMotionListener". Ich habe schon gesagt, dass Listener keine Klassen sind, sondern Interfaces. Ein Interface ist fast wie eine Klasse, mit dem (grossen) Unterschied aber, dass man von ihm keine Instanzen machen und auch nicht mit "extends" Subklassen ableiten kann. Und doch kann man von einem Interface alle seine Eigenschaften erben, wenn man es implementiert (= implements). Man kann beliebig viele Interfaces implementieren und kann damit von vielen Interfaces erben. Damit ist die multiple inheritance, die es bei Java nicht gibt, doch durch ein Hintertürchen wieder da! Eine salomonische Lösung haben da die Entwickler von Java gefunden.

Beim ActionEvent hätten wir das auch so machen können. Sie sehen hier eine zweite Art, wie man Listener verwenden kann. Da "Zeichnen" das Interface MouseMotionListener implementiert, kann sie all deren Methoden direkt verwenden. Und sie muss sogar all ihre Methoden überschreiben, auch wenn sie gar nicht gebraucht werden. Hier sind es zum Glück nur deren zwei: mouseMoved() wird aufgerufen, wenn die Maus bewegt wird und mouseDragged() wird aufgerufen, wenn die Maus bei gedrückter Taste bewegt wird. Es gibt übrigens auch den MouseListener, der reagiert, wenn eine Maustates gdrückt oder losgelassen wird, etc. Beachten Sie bitte, dass Sie alle Methoden dieser Listener überschreiben müssen, auch wenn Ihre Methode leer ist. Beim MouseListener sind dies die fünf Methoden mouseClicked(), mouseEntered(), mouseExited(), mousePressed() und mouseReleased().Sie schreiben also public void mouseClicked(MouseEvent e) { } und analog für alle andern Events, die nichts bewirken sollen.

Vor der Methode addMouse... steht kein Name einer Klasseninstanz wie bei addActionListener. Wenn nichts vor einer Methode steht, dann wird die Methode aus der aktuellen Klasse aufgerufen, also Zeichnen: Also wird der MouseMotionListener dem Applet selber zugefügt. Sie könnten auch schreiben this.addMouse..., denn das Schlüsselwort this steht für die aktuelle Klasse. Es steht übrigens auch in der Parameterklammer. Hier muss es stehen und meint diesmal Zeichnen nicht als Applet, sondern als Listenerklasse.

Da wir die Methode paint() des Applet nicht brauchen, müssen wir den Grafik - Kontext mit der Methode getGraphics() explizit verfügbar machen um dann darauf zeichnen zu können mit drawLine().

Und diesmal werten wir den Event auch aus: Er kann uns nämlich sagen, wo die Maus war, als sie "gedragged" wurde (schönes Deutsch, nicht wahr?): Die Methoden getX() und getY() des Events liefern uns diese Koordinate - und dahin zeichnen wir dann auch!

 

 

 

Dieses Einfachst - Zeichnungsprogramm kann jetzt natürlich verbessert werden, solange, bis es ein ausgewachsenes Corel Draw ist. Meine Vorschläge für Ihre ersten Schirtte auf diesem weiten Weg:

  • Die Punkte sollen durch Strecken verbunden werden, so dass die gezeichnete Linie keine Lücken hat.

  • Vor dem Drücken der Taste soll es keine Strecke geben. Tip: Verwenden Sie mousePressed() von MouseListener.

  • Man soll auf Tastendruck die Zeichnung löschen können. Tip: Ein fillRect() in der Grösse des Applet und eine Taste "Löschen".

  • Man soll die Farbe, in der gezeichnet wird, wählen können:
    Tips:

    - Mit Label titel = new Label("Zeichnen mit Farbe"); und add(titel); haben Sie die Beschriftung. Die GUI Komponenten werden eine neben der andern angeordnet und wenn die Zeile voll ist, beginnt eine neue. 
    - Erstellen Sie eine CheckboxGroup mit "CheckboxGroup cbg = new CheckboxGroup();".
    - Nun erstellen Sie vier Checkboxes, z.B. mit "= new Checkbox("schwarz",true,cbg);". So ist am Anfang "schwarz" gewählt. Bei "rot" etc. sollten Sie entsprechend "false" angeben.
    - Die Klasse Checkbox hat eine Methode getState(), die true liefert, wenn die Box gewählt ist und false andernfalls.
    - Die Anordnung der GUI Elemente lässt sich steuern mit sogenannten Layout Managern. Wieder einmal: Das kommt später, wir können nicht alles aufs Mal machen.