Kurs:Java – ein schneller Einstieg/Auf Ereignisse reagieren

Auf Ereignisse reagieren Bearbeiten

Ereignisse haben die "Eigenschaft" unverhofft aufzutreten. Programme, die nach viel Mühe endlich arbeiten, zeigen sich besonders überrascht, wenn sie beendet werden. So stellt das Ende einer Berechnung und die Anzeige des Ergebnisses keinesfalls auch immer ein Ende des Programms dar. Im vorliegenden Fall gehört die GUI zum Programm. Wird das Programm beendet, verschwindet die GUI und damit natürlich auch die Anzeige. Die Anzeige des Ergebnisses ist nicht wahrzunehmen, denn unmittelbar nach der Anzeige verschwindet sie mitsamt den Eingaben.

Besser ist es also, alles so zu lassen wie es ist und ein bestimmtes Ereignis zu benutzen, um das Programm zu beenden. Hier ist dieses Ereignis ein Klick auf den Button zum Schließen des Fensters. Das Verhalten entspricht zwar dem Erwarteten, aber Java denkt nicht daran das Programm selbst zu beenden. Eine entsprechende Anweisung wurde nicht programmiert.

Anwendung verlassen Bearbeiten

Normalerweise verabschiedet sich eine fensterbasierende Anwendung, wenn das Fenster geschlossen wird. Ein Klick auf die entsprechende Schaltfläche und die Anwendung ist weg. Klicks werden in Java als Ereignisse (events) geführt. Events sind in der Vorstellungswelt der herkömmlicher Programmierung Ausnahmen. Die lenken die Aufmerksamkeit auf sich, jedoch ist das nicht immer erwünscht. In Java wird ein Event als "Dazwischenreden" eingestuft, womit die Möglichkeit besteht, zuzuhören oder nicht. Nun ist Zuhören nicht jedermanns Sache, weshalb in Java eine spezielle Klasse existiert, deren Objekte nichts anderes tun, als zuzuhören. Damit diese nicht völlig überfordert sind, hat praktisch jeder "Dazwischenredner" einen eigenen spezialisierten Zuhörer. Es ist nun Aufgabe der Programmierung, Zuhörer dabeizuhaben oder nicht. Bezogen auf den Klick der "Fensterschließen"-Schaltfläche, bleibt dieser bisher ein "ungehörter Rufer in der Wüste".

Erster Kontakt zu EventListenern Bearbeiten

Die "Window-close"-Schaltfläche ist zweifellos Bestandteil des Fensters. Es liegt demnach nahe, das Fenster selbst mit einem Listener (Zuhörer) auszustatten. Die Methode Ausstatten kann auch als hinzufügen interpretiert werden, was die fragmentarische Methodenbezeichnung add... nahelegt. Die Methode addWindowListener(...) ist denn auch die richtige, wie es die API-Dokumentation zeigt. Was soll der Listener tun, wenn er den Ruf nach "Feierabend" von der Schaltfläche vernimmt? Doch nur das Programm zu beenden und das System zu verlassen. Alle anderen Rufe sollen ungehört verhallen.

Wird ein Listener verlangt, der nur eine begrenzte Anzahl von Events (in diesem Fall nur ein einziges) verarbeiten soll, so ist ein Adapter erforderlich. Dieser gestattet den Aufbau funktionsfähiger Listener ohne alle im Interface vorgeschriebenen Methoden zu definieren (Interfaces werden noch detailliert besprochen). Der Name des Adapters für einen WindowListener lautet WindowsAdapter. Das betroffene Fensterobjekt frame erhält also über

frame.addWindowsListener( new WindowsAdapter () ...

Einen Listener für spezielle Events des Klasse WindowEvent. Nur das Event WINDOW_CLOSING ist von Belang, denn jetzt soll das Programm beendet und das System verlassen werden. Das Programm wird von Java automatisch beendet, wenn der Befehl System.exit( ...) ausgeführt wird. Es genügt in diesem Fall also, nur System.exit ( 0) aufzurufen. Im WindowListener ist dafür eine eigene Methode vorgesehen, die nun noch geschrieben werden muss, denn der WindowListener ist ein Interface. Das reale Objekt wird ja erst durch die Instanzierung über den WindowAdapter erzeugt. Irgendwo in der Klassendefinition des neuen WindowAdapters muss diese Methode angesiedelt werden. Am einfachsten werden Probleme dadurch gelöst, die erforderlichen Dinge immer erst dann bereit zu haben, wenn sie benötigt werden. Hier also unmittelbar nach dem Befehl eine neue Instanz des WindowAdapters zu bilden.

frame.addWindowListener                                 // WindowListener hinzufügen
       (                                                 // Argument: WindowsListener
        new WindowAdapter() {                            // Instanzierung der Klasse
             public void windowClosing( WindowEvent e) { // Methode definieren
              System.exit( 0);                           // Methodenfunktionalität
             }                                           // Ende Methodendefinition
            }                                            // Ende Klassendefinition
       );                                                // Ende Argumentliste

In Java ist also die Instanzierung von Objekten durch unmittelbar anschließende Klassendefinition sogar innerhalb von Argumentlisten möglich. Diese Vorgehensweise ist normalerweise nicht üblich, wird aber für einfache Testzwecke oft eingesetzt. In der Praxis ist eigentlich immer eine separate Klasse vorhanden.

Aufgabe:

Ein Klick auf das "close-icon" ohne den beschriebenen Adapter bewirkt:

  1. Entfernen der Fensterinstanz.
  2. Fenster wird auf "nicht sichtbar" gesetzt.
  3. Programm geht in "sleep"-Modus.

Die Klasse WindowAdapter als "inner class" einsetzen Bearbeiten

Dieses Fragment kann nun in den Quelltext eingefügt werden und es ergibt sich ein erstes einsetzbares Programm mit GUI. Natürlich noch funktionalen Inhalt. Außerdem kann ein Anwender bisher nicht einmal ahnen was die Felder bedeuten, aber diese "Nebensächlichkeiten" werden nach und nach beseitigt.

class MyFrame {
 public static void main( String[] args) {
 javax.swing.JFrame frame = new javax.swing.JFrame();
 javax.swing.JPanel panel = new javax.swing.JPanel();
 javax.swing.JTextField betragFeld  = new javax.swing.JTextField( 7);
 javax.swing.JTextField rabattFeld  = new javax.swing.JTextField( 7);
 javax.swing.JTextField ausgabeFeld = new javax.swing.JTextField( 7);
  frame.addWindowListener( new WindowAdapter() {
                                public void windowClosing( WindowEvent e) {
                                 System.exit( 0);
                                }
                               }
                         );
  panel.setLayout( new java.awt.BorderLayout());
  panel.add( betragFeld,  java.awt.BorderLayout.NORTH);
  panel.add( rabattFeld,  java.awt.BorderLayout.CENTER);
  panel.add( ausgabeFeld, java.awt.BorderLayout.SOUTH);
  frame.setTitle( "Rabattberechnung");
  frame.setContentPane( panel);
  frame.pack();
  frame.setVisible( true);
 }
}

Berechnungen einfügen Bearbeiten

Endlich kann auch die eigentliche Aufgabe – dunkle Fetzen der Erinnerung an prozentuale Berechnungen – in die Anwendung einbezogen werden. Praktisch kann die gesamte funktionale Sequenz des Programms "Rabatt" übernommen werden, wobei die Zeilen der Anzeige einfach "auskommentiert" werden. Diese Anzeige verlangte als Argument einen String und genau diese Klasse ist ja wohl für JTextFields geradezu prädestiniert. Ohne viel in der API-Dokumentation zu suchen wird eine Methode getText(...) bei den Objekten der JTextFields als vorhanden angenommen und belegt.

class MyFrame {
 public static void main( String[] args) {
 javax.swing.JFrame frame = new javax.swing.JFrame();
 javax.swing.JPanel panel = new javax.swing.JPanel();
 javax.swing.JTextField betragFeld  = new javax.swing.JTextField( 7);
 javax.swing.JTextField rabattFeld  = new javax.swing.JTextField( 7);
 javax.swing.JTextField ausgabeFeld = new javax.swing.JTextField( 7);

 String betragText = "1234.56";
 String rabattText = "7";
 Float betragZahl = new Float( betragText);
 Float rabattZahl = new Float( rabattText);
 float betragWert = betragZahl.floatValue();
 float rabattWert = rabattZahl.floatValue();
 float rabattFaktor = rabattWert / 100;
 float betragDifferenz = betragWert * rabattFaktor;
 float endBetrag = betragWert - betragDifferenz;
/*
  System.out.println( rabattWert+"% von "+betragWert+" sind: "+betragDifferenz);
  System.out.println( "Der Endbetrag ist:    "+endBetrag);
*/
  betragFeld.setText( betragText);
  rabattFeld.setText( rabattText);
  ausgabeFeld.setText( ""+endBetrag);
  frame.addWindowListener( new WindowAdapter() {
                                public void windowClosing( WindowEvent e) {
                                 System.exit( 0);
                                }
                               }
                         );
  panel.setLayout( new java.awt.BorderLayout());
  panel.add( betragFeld,  java.awt.BorderLayout.NORTH);
  panel.add( rabattFeld,  java.awt.BorderLayout.CENTER);
  panel.add( ausgabeFeld, java.awt.BorderLayout.SOUTH);
  frame.setTitle( "Rabattberechnung");
  frame.setContentPane( panel);
  frame.pack();
  frame.setVisible( true);
 }
}

Auch das funktioniert wunderbar. Nachteilig ist jetzt eigentlich nur noch, dass sich die Anwendung von Eingaben des Anwenders völlig unbeeindruckt zeigt. Überhaupt wird die ganze Sache etwas unübersichtlich, denn hier setzt sich eine einzige Methode mit allen benötigten Objekten aller Klassen auseinander und hat auch noch mehr als 20 Zeilen. Die Einstellung zu alten Adelsgeschlechtern ist zweifellos geteilt, trotzdem war es einer derer von Hohenzollern, mit der Erkenntnis:

divide et impera! (teile und herrsche)

Aufgabe:

Befolgen des Ratschlags.

  • Welche Teile (Abschnitte) des Programms könnten unter einem sinnvollen Namen zusammengefasst werden?
  • Können Teile (Abschnitte) des Programms in eigenen Behältern (JContainer) zusammengefasst werden?