Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 15 Komponenten, Container und Ereignisse
gp 15.1 Es tut sich was - Ereignisse beim AWT
gp 15.1.1 Was ist ein Ereignis?
gp 15.1.2 Die Klasse AWTEvent
gp 15.1.3 Events auf verschiedenen Ebenen
gp 15.1.4 Ereignisquellen, -senken und Horcher (Listener)
gp 15.1.5 Listener implementieren
gp 15.1.6 Listener bei Ereignisauslöser anmelden
gp 15.2 Varianten, das Fenster zu schließen
gp 15.2.1 Eine Klasse implementiert die Schnittstelle WindowListener
gp 15.2.2 Adapterklassen nutzen
gp 15.2.3 Innere Mitgliedsklassen und innere anonyme Klassen
gp 15.2.4 Generic Listener
gp 15.3 Komponenten im AWT und in Swing
gp 15.3.1 Peer-Klassen und Lightweight-Komponenten
gp 15.3.2 Die Basis aller Komponenten: Component und JComponent
gp 15.3.3 Proportionales Vergrößern eines Fensters
gp 15.3.4 Dynamisches Layout während einer Größenänderung
gp 15.3.5 Hinzufügen von Komponenten
gp 15.4 Das Swing-Fenster JFrame
gp 15.4.1 Kinder auf einem Swing-Fenster
gp 15.4.2 Schließen eines Swing-Fensters
gp 15.5 Ein Informationstext über die Klasse JLabel
gp 15.5.1 Mehrzeiliger Text
gp 15.6 Die Klasse ImageIcon
gp 15.6.1 Die Schnittstelle Icon
gp 15.6.2 Was Icon und Image verbindet
gp 15.7 Eine Schaltfläche (JButton)
gp 15.7.1 Der aufmerksame ActionListener
gp 15.7.2 Generic Listener für Schaltflächen-Ereignisse verwenden
gp 15.7.3 AbstractButton
gp 15.7.4 JToggleButton
gp 15.8 Tooltips
gp 15.9 Horizontale und vertikale Schieberegler
gp 15.9.1 Der AdjustmentListener, der auf Änderungen hört
gp 15.10 JSlider
gp 15.11 Ein Auswahlmenü - Choice, JComboBox
gp 15.11.1 ItemListener
gp 15.11.2 Zuordnung einer Taste mit einem Eintrag
gp 15.12 Eines aus vielen - Kontrollfelder (JCheckBox)
gp 15.12.1 Ereignisse über ItemListener
gp 15.13 Kontrollfeldgruppen, Optionsfelder und JRadioButton
gp 15.14 Der Fortschrittsbalken JProgressBar
gp 15.15 Rahmen (Borders)
gp 15.16 Symbolleisten alias Toolbars
gp 15.17 Menüs
gp 15.17.1 Die Menüleisten und die Einträge
gp 15.17.2 Menüeinträge definieren
gp 15.17.3 Mnemonics und Shortcuts (Accelerator)
gp 15.17.4 Beispiel für ein Programm mit Menüleisten
gp 15.18 Popup-Menüs
gp 15.19 Alles Auslegungssache: die Layoutmanager
gp 15.19.1 Null-Layout
gp 15.19.2 FlowLayout
gp 15.19.3 BorderLayout
gp 15.19.4 GridLayout
gp 15.19.5 Der GridBagLayout-Manager
gp 15.19.6 Weitere Layoutmanager
gp 15.20 Der Inhalt einer Zeichenfläche: JPanel
gp 15.21 Das Konzept des Model-View-Controllers
gp 15.22 List-Boxen
gp 15.23 JSpinner
gp 15.24 Texteingabefelder
gp 15.24.1 Text in einer Eingabezeile
gp 15.24.2 Die Oberklasse der JText-Komponenten: JTextComponent
gp 15.24.3 JPasswordField
gp 15.24.4 Validierende Eingabefelder
gp 15.24.5 Mehrzeilige Textfelder
gp 15.24.6 Die Editor-Klasse JEditorPane
gp 15.25 Bäume mit JTree-Objekten
gp 15.25.1 Selektionen bemerken
gp 15.26 Tabellen mit JTable
gp 15.26.1 Ein eigenes Modell
gp 15.26.2 AbstractTableModel
gp 15.26.3 DefaultTableModel
gp 15.26.4 Ein eigener Renderer für Tabellen
gp 15.26.5 Spalteninformationen
gp 15.26.6 Tabellenkopf von Swing-Tabellen
gp 15.26.7 Selektionen einer Tabelle
gp 15.27 JRootPane und JLayeredPane
gp 15.28 Dialoge
gp 15.28.1 Der Farbauswahldialog JColorChooser
gp 15.28.2 Der Dateiauswahldialog
gp 15.29 Das Java-Look&Feel
gp 15.30 Swing-Beschriftungen einer anderen Sprache geben
gp 15.31 Die Zwischenablage (Clipboard)
gp 15.32 Undo durchführen
gp 15.33 Ereignisverarbeitung auf unterster Ebene
gp 15.34 AWT, Swing und die Threads
gp 15.34.1 Warum Swing nicht Thread-sicher ist
gp 15.34.2 Swing-Elemente bedienen mit invokeLater() und invokeAndWait()
gp 15.35 Selbst definierte Cursor
gp 15.35.1 Flackern des Mauszeigers bei Animationen vermeiden
gp 15.36 Mausrad-Unterstützung
gp 15.37 Benutzerinteraktionen automatisieren
gp 15.37.1 Automatisch in die Tasten hauen
gp 15.37.2 Mausoperationen
gp 15.37.3 Methoden zur Zeitsteuerung
gp 15.37.4 Screenshots
gp 15.37.5 Funktionsweise und Beschränkungen


Galileo Computing

15.34 AWT, Swing und die Threadsdowntop

Beim AWT und bei Swing gibt es einen Thread, der für die Oberflächenelemente verantwortlich ist: den AWT-Thread. Er läuft parallel zum Hauptprogramm (ein Thread mit dem Namen main) und führt den Programmcode in den Listenern aus. Aus diesem Grund ist es auch ungünstig, in einen Event-Handler langen Programmcode zu legen, denn dann steht die grafische Applikation und kann nicht weitermachen, da der AWT-Thread blockiert ist. Wenn wir eine Aktion in einem Event-Handler machen müssen, dann sollten wir einen extra Thread starten, damit die grafische Oberfläche sofort wieder reaktionsfähig ist.


Beispiel Wenn eine Schaltfläche gedrückt wird, soll ein langer Text in den Puffer eingelesen werden. Das lässt sich schön mit zwei inneren Klassen realisieren.
ActionListener al = new ActionListener() {
 public void actionPerformed( ActionEvent e ) {
  New Thread( new ReaderThread(e.getActionCommand()) ).start();
  System.exit( 0 );
 }
};

In einer externen Klasse lesen wir den Text:

class ReaderThread implements Runnable
{
  ReaderThread( String actionCommand )
  {
    // ...
  }
  public void run() {
    // ...
  }
}

Unter dem AWT ist es kein Problem, wenn zwei Threads auf ein und dasselbe Oberflächenelement zugreifen. Bei Swing ist das jedoch etwas anders, wie wir im nächsten Abschnitt sehen werden.


Galileo Computing

15.34.1 Warum Swing nicht Thread-sicher istdowntop

Die Tatsache, dass das Swing-Toolkit nicht Thread-sicher ist, erstaunt vielleicht auf den ersten Blick. Das AWT ist Thread-sicher, da AWT auf Plattform-Peer-Elemente vertraut. In einer List-Box unter dem AWT ist es problemlos möglich, ein Element einzufügen und parallel zu löschen. Doch auf die Synchronisation bei Swing wurde aus zwei Gründen verzichtet:

gp Untersuchungen mit anderen grafischen Bibliotheken haben ergeben, dass Operationen in Threads zu ärgerlichen Deadlock-Situationen führen können. Es ist eine zusätzliche Last, die auf dem Programmieren grafischer Oberflächen lastet, Monitore korrekt einzusetzen.
gp Als zweiter, sicherlich in Zukunft weniger wichtiger Punkt ist der Gewinn von Ausführungsgeschwindigkeit zu nennen. Das Swing-Toolkit kostet ohnehin viel Zeit, so dass auf die zusätzliche Synchronisation gut verzichtet werden kann.

Beispiel Swing kann mit konkurrierenden Zugriffen nicht viel anfangen.

Listing 15.46 SwingNoSyncDemo.java

import javax.swing.*;
public class SwingNoSyncDemo
{
  public static void main( String args[] )
  {
    final DefaultListModel model = new DefaultListModel();
    JList list = new JList( model );
    JFrame frame = new JFrame();
    frame.getContentPane().add( list );
    frame.setSize( 200, 100 );
    frame.show();
    new Thread(){ public void run() {
        setPriority( Thread.MIN_PRIORITY );
        while ( true )
          model.addElement( "Dumm gelaufen" );
      }
    }.start();
    new Thread(){ public void run() {
        setPriority( Thread.MIN_PRIORITY );
        while ( true )
          model.removeElement( "Dumm gelaufen" );
      }
    }.start();
  }
}

Werfen wir einen Blick auf die Ausgabe, die erscheint, wenn das Programm nur kurz läuft:

Exception occurred during event dispatching:
java.lang.ArrayIndexOutOfBoundsException: 4145 >= 4145
  at java.util.Vector.elementAt(Vector.java:417)
  at javax.swing.DefaultListModel.getElementAt(DefaultListModel.java:70)
...
  at java.awt.Component.dispatchEvent(Component.java:2499)
  at java.awt.EventQueue.dispatchEvent(EventQueue.java:319)
  at java.awt.EventDispatchThread.pumpOneEvent(EventDispatchThread.java:103)
  at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
  at java.awt.EventDispatchThread.run(EventDispatchThread.java:84)
Exception occurred during event dispatching:
java.lang.ArrayIndexOutOfBoundsException: 4288 >= 4288
  at java.util.Vector.elementAt(Vector.java:417)
  at javax.swing.DefaultListModel.getElementAt(DefaultListModel.java:70)
...

Obwohl als unterliegende Datenstruktur der Vektor vorhanden ist, der, wie wir wissen, nur synchronisierte Methoden besitzt, ist er nicht direkt der Übeltäter. Es liegt an Swing, wie mit den Daten umgegangen wird. Wenn der erste Thread Daten in das Modell einfügt, muss die Visualisierung aktualisiert werden. Als Datenstruktur nimmt das Standardmodell einen Vektor, der die Daten aufnimmt. Das Modell informiert also das Darstellungsobjekt, dass es den Inhalt neu zeichnen muss. Merken wir uns die Stelle. Das Darstellungsobjekt wird sich nun vom Modell die Daten besorgen. Bis dahin läuft alles ganz gut. Doch der zweite Thread löscht parallel die Daten aus dem Modell. Springen wir jetzt zur Markierung zurück. Irgendwann passiert es, dass zwischen der Benachrichtigung der Darstellungskomponenten und dem wirklichen Zeichnen etwas gelöscht wird. Die Visualisierung weiß aber davon nichts und versucht alle Werte zu zeichnen; es fehlt aber mindestens ein Wert. Daher folgt eine ArrayIndexOutOfBoundsException in der Methode elementAt() vom Vektor:

java.lang.ArrayIndexOutOfBoundsException: 4145 >= 4145
  at java.util.Vector.elementAt(Vector.java:417)

Werfen wir einen Blick in die Implementierung, dann erkennen wir das Problem:

public synchronized Object elementAt( int index ) {
  if (index >= elementCount) {
    throw new ArrayIndexOutOfBoundsException(
                 index + " >= " + elementCount);
  }
  ...
}

Die Visualisierung fragt mit dem Index 4145 im Vektor nach, doch der Vektor hat vom Lösch-Thread schon ein Element abgeben müssen. Daher ist die interne Größe elementCount auch kleiner als der Index.

Dass das Programm dennoch eine Zeit lang läuft, hängt damit zusammen, dass der Hinzufüge-Thread aufgeholt haben kann und noch viele Elemente im Vektor auf Vorrat liegen. Nehmen wir setPriority(Thread.MIN_PRIORITY) aus dem Einfüge-Thread heraus, dann gibt es kein Problem.

Lösung für Swing

Einige der Methoden, die dennoch synchronisiert sind, tragen Listener ein, so etwa bei JComponent addPropertyChangeListener(), removePropertyChangeListener() und addVetoableChangeListener(), removeVetoableChangeListener(). Bei JCheckBoxMenuItem ist es dann die einsame Methode setState(boolean), die synchronisiert ist. Es findet sich intern mal hier mal da ein synchronisierter Block. Ansonsten ist jedoch nicht viel dabei, und wir müssen unsere Teile synchronisiert ausführen.

Um Programmstücke konform ausführen zu lassen, definiert Swing einige Methoden und Klassen. Dazu gehören:

gp invokeLater(Runnable)
gp invokeAndWait(Runnable)
gp JProgressBar
gp ProgressMonitor
gp ProgressMonitorInputStream
gp SwingWorker

Galileo Computing

15.34.2 Swing-Elemente bedienen mit invokeLater() und invokeAndWait()toptop

Da Swing nicht Thread-sicher ist, ist die einzige Möglichkeit zur Manipulation von Oberflächenelementen der AWT-Thread. Wenn wir es schaffen, dort die Aufträge einzureihen, dann wird nichts schief gehen. Genau für diese Aufgabe existieren in der Klasse EventQueue zwei Methoden: invokeLater() und invokeAndWait(). Damit lassen sich beliebige Programmstücke in die Warteschlange einführen. In der Warteschlange für das AWT liegen Aufträge und Ereignisse, die an die Oberflächenelemente verteilt werden. Alles spielt sich dabei neben dem Haupt-Thread ab, so dass Parallelität herrscht. Hat die Warteschlange alle Ereignisbehandler aufgerufen, kann der Programmcode von invokeLater() und invokeAndWait() durchlaufen werden. Den Methoden wird ein Runnable-Objekt übergeben. Die zwei Methoden erfüllen unterschiedliche Bedürfnisse:

gp invokeLater() legt einen Thread in die Warteschlage und kehrt sofort zurück. Die Methode ist somit asynchron. Der Aufrufer weiß nicht, wann der Programmcode abgearbeitet wird.
gp invokeAndWait() legt ebenfalls den Thread in die Warteschlange, verharrt aber so lange in der Methode, bis der Programmcode in run() aufgerufen wurde. Die Methode ist also synchron.

Mit diesen Methoden lassen sich jetzt alle Manipulationen an der Oberfläche durchführen.


Beispiel Ein Fortschrittsbalken JProgressBar mit dem Namen bar soll in einer Schleife einer Berechnung angepasst werden.
EventQueue.invokeLater( new Runnable()
{
  public void run() {
    bar.setValue( i );
  }
} );

Bei der Auswahl der beiden Funktionen haben wir uns für den Fortschrittsbalken für invokeLater() entschieden. Es macht in der Regel wenig Sinn, die Methode so lange stehen zu lassen, bis die Anzeige auch wirklich gezeichnet wurde.

Ein Problem ist leider für sehr viele Applikationen, dass das Objekt zur Manipulation immer irgendwie sichtbar sein muss. Hier soll bar einfach direkt für die innere Klasse sichtbar sein.

Die Funktionen invokeLater() und invokeAndWait() befinden sich nicht nur in der Klasse EventQueue, sondern sind noch einmal in der Klasse SwingUtilities untergebracht. Daher ist es gleichgültig, ob wir EventQueue.invokeXXX() oder SwingUtilities.invokeXXX() schreiben. SwingUtilities hat vielleicht den Vorteil, dass das Paket java.awt für die EventQueue nicht importiert werden muss, sonst gibt es aber keinen Unterschied.

Implementierung

Genehmigen wir uns abschließend noch einen kurzen Blick auf die Implementierung. Es lässt sich schon erahnen, dass invokeLater() einfacher ist:

public static void invokeLater( Runnable runnable )
{
  Toolkit.getEventQueue().postEvent(
    new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
}

Das Ereignis, welches in die Event-Queue kommt, ist vom Typ InvocationEvent und damit ein AWTEvent. Wir übergeben unser Runnable-Objekt, damit der AWT-Thread später die run()-Methode aufrufen kann.

Die Methode invokeAndWait() ist etwas komplizierter, und wir wollen von der Implementierung nur wenige Zeilen betrachten. Im Prinzip macht die Methode das Gleiche wie invokeLater(), sie muss ebenfalls das InvocationEvent in die Warteschlange legen. Doch hinzu kommt, dass invokeAndWait() auf das Ende des Threads warten muss:

InvocationEvent event = new InvocationEvent(
  Toolkit.getDefaultToolkit(), runnable, lock, true);
synchronized (lock) {
  Toolkit.getEventQueue().postEvent(event);
  lock.wait();
}

Das konstruierte InvocationEvent bekommt wieder als Parameter das runnable. Jetzt erhält es aber zusätzlich ein Lock-Objekt. Wenn der AWT-Thread durch die Ereignis-Warteschlange geht und das InvocationEvent sieht, führt er wieder die run()-Methode aus. Anschließend informiert er über notify() das wartende Objekt. Dann steigt invokeAndWait() aus dem sychronized-Block aus, und es geht weiter.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de