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

 << zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5
Java ist auch eine Insel

Java ist auch eine Insel
5., akt. und erw. Auflage
1454 S., mit CD, 49,90 Euro
Galileo Computing
ISBN 3-89842-747-1
gp Kapitel 15 Komponenten, Container und Ereignisse
  gp 15.1 Es tut sich was – Ereignisse beim AWT
    gp 15.1.1 Die Klasse AWTEvent
    gp 15.1.2 Events auf verschiedenen Ebenen
    gp 15.1.3 Ereignisquellen und Horcher (Listener)
    gp 15.1.4 Listener implementieren
    gp 15.1.5 Listener bei Ereignisauslöser anmelden/abmelden
    gp 15.1.6 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 Swing-Fenster JFrame, JDialog, JWindows
    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 Mit BorderLayout in allen Himmelsrichtungen
    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 Rollbalken
    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, 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 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.27 Dialoge
    gp 15.27.1 Der Farbauswahldialog JColorChooser
    gp 15.27.2 Der Dateiauswahldialog
  gp 15.28 Flexibles Java-Look&Feel
    gp 15.28.1 UIManager
    gp 15.28.2 Ändern des Aussehens Laufzeit
    gp 15.28.3 Verbessern des Aussehens unter Windows mit WinLAF
  gp 15.29 Swing-Beschriftungen einer anderen Sprache geben
  gp 15.30 Die Zwischenablage (Clipboard)
  gp 15.31 Undo durchführen
  gp 15.32 Ereignisverarbeitung auf unterster Ebene
    gp 15.32.1 Eigene Ereignisse in die Queue setzen
    gp 15.32.2 Auf alle Ereignisse hören
  gp 15.33 AWT, Swing und die Threads
    gp 15.33.1 Swing ist nicht Thread-sicher
    gp 15.33.2 Swing-Elemente bedienen mit invokeLater(), invokeAndWait()
  gp 15.34 Selbst definierte Cursor
    gp 15.34.1 Flackern des Mauszeigers bei Animationen vermeiden
  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 MouseInfo und PointerInfo
  gp 15.36 Zeitliches Ausführen mit dem javax.swing.Timer
  gp 15.37 Alternativen zu AWT und Swing
    gp 15.37.1 XML-Beschreibungen der Oberfläche: Swixml, XUL/Luxor
    gp 15.37.2 SWT


Galileo Computing

15.33 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 namens 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 lässt sich nicht fortsetzen, weil der AWT-Thread blockiert ist. Bei einer Aktion in einem Event-Handler sollten wir einen separaten 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 dies jedoch etwas anders, wie wir im nächsten Abschnitt sehen werden.


Galileo Computing

15.33.1 Swing ist nicht Thread-sicher  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.
gp  Der Verzicht auf Synchronisation bringt einen Gewinn an Ausführungsgeschwindigkeit

Beispiel   Swing weiß mit konkurrierenden Zugriffen nicht allzu viel anzufangen.

Listing 15.48   SwingNoSyncDemo.java

import javax.swing.*;
public class SwingNoSyncDemo
{
  public static void main( String[] args )
  {
    final DefaultListModel model = new DefaultListModel();
    JFrame frame = new JFrame();
    frame.add( new JList( model ) );
    frame.setSize( 200100 );
    frame.setVisible( true );
    new Thread() {
      @Override public void run() {
        setPriority( Thread.MIN_PRIORITY );
        while ( true )
          model.addElement( "Dumm gelaufen" );
      }
    }.start();
    new Thread() {
      @Override 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 in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 14 >= 14
  at java.util.Vector.elementAt(Vector.java:432)
  at javax.swing.DefaultListModel.getElementAt(DefaultListModel.java:70)
    ...
  at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:149)
  at java.awt.EventDispatchThread.run(EventDispatchThread.java:110)
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 0
  at javax.swing.plaf.basic.BasicListUI.updateLayoutState(BasicListUI.java:1155)
  at javax.swing.plaf.basic.BasicListUI.maybeUpdateLayoutState(BasicListUI.
  java:1098)
  at javax.swing.plaf.basic.BasicListUI.paint(BasicListUI.java:234)
  at javax.swing.plaf.ComponentUI.update(ComponentUI.java:142)
    ...

Obwohl als unterliegende Datenstruktur der Vektor vorhanden ist, der, wie wir wissen, nur synchronisierte Methoden besitzt, ist nicht er 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 Model informiert also das Darstellungsobjekt darüber, 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 jedoch mindestens ein Wert. Daher folgt eine ArrayIndexOutOfBoundsException in der Methode elementAt() vom Vektor. Die Visualisierung fragt mit einem Index im Vektor nach, doch der Vektor hat vom Lösch-Thread schon ein Element abgeben müssen. Daher ist die interne Größe des Vektors kleiner als der von Swing erfragte Index.

Lösung für Swing

Einige der Methoden, die dennoch synchronisiert sind, tragen Listener ein, so etwa bei JComponent addPropertyChangeListener(), removePropertyChangeListener() und add-VetoableChangeListener(), 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.33.2 Swing-Elemente bedienen mit invokeLater(), 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 gibt es 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 invoke-AndWait() durchlaufen werden. Den Methoden wird ein Runnable-Objekt übergeben. Die beiden 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 ist in der Regel wenig sinnvoll, die Methode so lange stehen zu lassen, bis die Anzeige auch wirklich gezeichnet wurde.

Ein Problem stellt für sehr viele Applikationen leider die Tatsache dar, 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; wir wollen von der Implementierung nur wenige Zeilen betrachten. Im Prinzip leistet die Methode das Gleiche wie invokeLater(), auch sie muss das InvocationEvent in die Warteschlange legen. Hinzu kommt jedoch, dass invokeAndWait() auf das Ende des Threads warten muss:

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

Das konstruierte InvocationEvent bekommt als Argument wieder 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 invoke-AndWait() aus dem synchronized-Block aus, und es geht weiter.

 << zurück




Copyright © Galileo Press GmbH 2005
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, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de