Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
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/abmelden
    gp 15.1.7 Aufrufen der Listener
  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.4.3 JWindow und JDialog
  gp 15.5 Informationstext über die Klasse JLabel
    gp 15.5.1 Mehrzeiliger Text, HTML in der Darstellung
  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 Der Container JPanel
  gp 15.10 Alles Auslegungssache: die Layoutmanager
    gp 15.10.1 FlowLayout
    gp 15.10.2 BorderLayout
    gp 15.10.3 GridLayout
    gp 15.10.4 Der GridBagLayout-Manager
    gp 15.10.5 Null-Layout
    gp 15.10.6 Weitere Layoutmanager
  gp 15.11 Horizontale und vertikale Schieberegler
    gp 15.11.1 Der AdjustmentListener, der auf Änderungen hört
  gp 15.12 JSlider
  gp 15.13 Ein Auswahlmenü – Choice, JComboBox
    gp 15.13.1 ItemListener
    gp 15.13.2 Zuordnung einer Taste mit einem Eintrag
    gp 15.13.3 DateComboBox
  gp 15.14 Eines aus vielen – Kontrollfelder (JCheckBox)
    gp 15.14.1 Ereignisse über ItemListener
  gp 15.15 Kontrollfeldgruppen, Optionsfelder und JRadioButton
  gp 15.16 Der Fortschrittsbalken JProgressBar
  gp 15.17 Rahmen (Borders)
  gp 15.18 Symbolleisten alias Toolbars
  gp 15.19 Menüs
    gp 15.19.1 Die Menüleisten und die Einträge
    gp 15.19.2 Menüeinträge definieren
    gp 15.19.3 Mnemonics und Shortcuts (Accelerator)
    gp 15.19.4 Beispiel für ein Programm mit Menüleisten
    gp 15.19.5 Popup-Menüs
  gp 15.20 Das Konzept des Model-View-Controllers
  gp 15.21 List-Boxen
  gp 15.22 JSpinner
  gp 15.23 Texteingabefelder
    gp 15.23.1 Text in einer Eingabezeile
    gp 15.23.2 Die Oberklasse der JText-Komponenten: JTextComponent
    gp 15.23.3 JPasswordField
    gp 15.23.4 Validierende Eingabefelder
    gp 15.23.5 Mehrzeilige Textfelder
    gp 15.23.6 Die Editor-Klasse JEditorPane
  gp 15.24 Bäume mit JTree-Objekten
    gp 15.24.1 Selektionen bemerken
  gp 15.25 Tabellen mit JTable
    gp 15.25.1 Ein eigenes Tabellen-Model
    gp 15.25.2 AbstractTableModel
    gp 15.25.3 DefaultTableModel
    gp 15.25.4 Ein eigener Renderer für Tabellen
    gp 15.25.5 Zell-Editoren
    gp 15.25.6 Größe und Umrandung der Zellen
    gp 15.25.7 Spalteninformationen
    gp 15.25.8 Tabellenkopf von Swing-Tabellen
    gp 15.25.9 Selektionen einer Tabelle
    gp 15.25.10 Ein professionelles Tabellenlayout mit JGrid
  gp 15.26 JRootPane, JLayeredPane und JDesktopPane
    gp 15.26.1 JRootPane und JLayeredPane
    gp 15.26.2 JDesktopPane und die Kinder JInternalFrame
    gp 15.26.3 Der Farbauswahldialog JColorChooser
    gp 15.26.4 Der Dateiauswahldialog
  gp 15.27 Flexibles Java-Look&Feel
  gp 15.28 Swing-Beschriftungen einer anderen Sprache geben
  gp 15.29 Die Zwischenablage (Clipboard)
  gp 15.30 Undo durchführen
  gp 15.31 Ereignisverarbeitung auf unterster Ebene
  gp 15.32 AWT, Swing und die Threads
    gp 15.32.1 Warum Swing nicht Thread-sicher ist
    gp 15.32.2 Swing-Elemente bedienen mit invokeLater() und invokeAndWait()
  gp 15.33 Selbst definierte Cursor
    gp 15.33.1 Flackern des Mauszeigers bei Animationen vermeiden
  gp 15.34 Mausrad-Unterstützung
  gp 15.35 Benutzerinteraktionen automatisieren
    gp 15.35.1 Automatisch in die Tasten hauen
    gp 15.35.2 Mausoperationen
    gp 15.35.3 Methoden zur Zeitsteuerung
    gp 15.35.4 Screenshots
    gp 15.35.5 Funktionsweise und Beschränkungen
    gp 15.35.6 Zeitliches Ausführen mit dem javax.swing.Timer
    gp 15.35.7 MouseInfo und PointerInfo
  gp 15.36 Alternativen zu AWT und Swing
    gp 15.36.1 XML-Beschreibungen der Oberfläche: Swixml, XUL/Luxor
    gp 15.36.2 SWT


Galileo Computing

15.32 AWT, Swing und die Threaddowntop

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();
  }
};

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.32.1 Warum Swing nicht Thread-sicher ist  downtop

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.48   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.setVisible( true );

    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 Model einfügt, muss die Visualisierung aktualisiert werden. Als Datenstruktur nimmt das Standardmodel einen java.util.Vector, 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 Model die Daten besorgen. Bis dahin läuft alles ganz gut. Doch der zweite Thread löscht parallel die Daten aus dem Model. 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.

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.32.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 Argument 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 © 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