SwingWorker

+ andere TechDocs
+ Grafikausgabe
+ JSF
+


Der folgende Text beschreibt eine spezielle Anwendung zum SwingWorker. Weitergehende Informationen finden Sie zum Beispiel unter:



SwingWorker mit Callback

Dieses Programmierbeispiel demonstriert, wie in einer Swing-GUI-Anwendung mit dem SwingWorker zeitaufwändige Aufgaben in einen Background-Thread verlagert werden können, damit das GUI bedienbar bleibt, und wie trotzdem Zwischenergebnisse im GUI angezeigt werden können.

Das Besondere an diesem Programmierbeispiel ist, dass die Swing-GUI-Klasse und die Worker-Klasse, welche die zeitaufwändige Aufgabe ausführt, entkoppelt sind. Normalerweise muss die Worker-Klasse Zwischenergebnisse über die SwingWorker.publish()-Methode übergeben, und ist damit fest mit dem Swing-GUI gekoppelt.

In diesem Beispiel ist die Worker-Klasse eigenständig und unabhängig vom Swing-GUI. Sie kann als Konsolenprogramm oder mit unterschiedlichen GUIs verwendet werden. Trotzdem werden Zwischenergebnisse über einen Callback übermittelt.

  1. Legen Sie ein Projektverzeichnis mit Unterverzeichnissen an, beispielsweise so:

    md \MeinWorkspace\SwingWorkerCallback

    cd \MeinWorkspace\SwingWorkerCallback

    md bin

    md src

  2. Als "Worker-Klasse", welche eine beliebige zeitaufwändige Aufgabe ausführt, soll ein simples Textdateianzeige-Programm dienen. Damit es für das Beispiel tatsächlich zeitaufwändig ist, wird per "Thread.sleep( 10 )" pro Schleifendurchlauf eine Verzögerung simuliert.
    Die Textdatei soll nicht zuerst als Ganzes gelesen und dann angezeigt werden, sondern jede gelesene Zeile soll sofort angezeigt werden und wird deshalb als "Zwischenergebnis" übermittelt. Damit verschiedene GUIs möglich sind, erfolgt die Übermittlung der Meldungen nicht direkt, sondern über einen Callback.
    Diese erste Klasse DateiLeserMitCallback enthält keine Swing-GUI und insbesondere keinerlei Abhängigkeit zu Swing. Es ist als Konsolenprogramm eigenständig ausführbar.
    Legen Sie im src-Unterverzeichnis folgende Klasse an: DateiLeserMitCallback.java

    import java.io.*;
    
    public class DateiLeserMitCallback
    {
       /** Ausfuehrung als Konsolenprogramm (ohne Swing-GUI). */
       public static void main( String[] args )
       {
          String textdatei = ( args != null && args.length > 0 ) ? args[0] : "src/DateiLeserMitCallback.java";
          String charEncod = ( args != null && args.length > 1 ) ? args[1] : "ISO-8859-1";
          liesTextdatei( textdatei, charEncod, new KonsoleMeldungenCallback() );
       }
    
       /** Universelles Interface fuer Callback-Klasse zur Entkopplung der Meldung von Zwischenergebnissen
           waehrend der Verarbeitung, damit beliebige GUIs moeglich sind. */
       public static interface MeldungenCallback<V>
       {
          void ausgabeMeldung( V v );
       }
    
       /** Konkrete Implementierung der Callback-Klasse zur Ausgabe von Zwischenergebnissen auf der Konsole. */
       public static class KonsoleMeldungenCallback implements MeldungenCallback<String>
       {
          @Override public void ausgabeMeldung( String s ) { System.out.println( s ); }
       }
    
       /** "Arbeitsmethode" mit Callback zum Returnieren von Zwischenergebnissen waehrend der Verarbeitung. */
       public static Boolean liesTextdatei( String textdatei, String charEncod, MeldungenCallback<String> meldungenCallback )
       {
          String ausgabeAbgrenzung = "------------------------------------------------------------------";
          meldungenCallback.ausgabeMeldung( "\n" + ausgabeAbgrenzung );
    
          if( textdatei == null || textdatei.trim().length() == 0 ) {
             meldungenCallback.ausgabeMeldung( "Fehler: Dateiname ist leer." );
             return Boolean.FALSE;
          }
          if( !(new File( textdatei )).exists() ) {
             meldungenCallback.ausgabeMeldung( "Fehler: Datei " + textdatei + " existiert nicht." );
             return Boolean.FALSE;
          }
    
          meldungenCallback.ausgabeMeldung( "Textdateianzeige" );
          meldungenCallback.ausgabeMeldung( "Textdatei:        " + textdatei );
          meldungenCallback.ausgabeMeldung( "Zeichenkodierung: " + charEncod );
          meldungenCallback.ausgabeMeldung( ausgabeAbgrenzung );
    
          try( BufferedReader in = new BufferedReader( new InputStreamReader( new FileInputStream( textdatei ), charEncod ) ) ) {
             String line;
             while( (line = in.readLine()) != null ) {
                meldungenCallback.ausgabeMeldung( line );
                // Zur Simulation berechnungsintensiver Aufgaben:
                Thread.sleep( 10 );
             }
          } catch( Exception ex ) {
             meldungenCallback.ausgabeMeldung( "Fehler-Exception: " + ex.getMessage() + "\n" + ex.toString() );
             return Boolean.FALSE;
          }
    
          meldungenCallback.ausgabeMeldung( ausgabeAbgrenzung );
          meldungenCallback.ausgabeMeldung( "Fertig." );
          meldungenCallback.ausgabeMeldung( ausgabeAbgrenzung );
          return Boolean.TRUE;
       }
    }
    
  3. Das folgende Swing-GUI-Programm führt obige Klasse aus und zeigt Zwischenergebnisse im GUI an.
    Legen Sie im src-Unterverzeichnis folgende Klasse an: SwingWorkerCallbackGui.java

    import java.awt.*;
    import java.awt.event.*;
    import java.io.File;
    import java.nio.charset.StandardCharsets;
    import java.util.List;
    import java.util.concurrent.*;
    import javax.swing.*;
    import javax.swing.text.DefaultCaret;
    
    /**
     * Dieses Programmierbeispiel demonstriert, wie in einer Swing-GUI-Anwendung mit dem
     * {@link <a href="http://docs.oracle.com/javase/8/docs/api/javax/swing/SwingWorker.html">SwingWorker</a>}
     * zeitaufwaendige Aufgaben in einen Background-Thread verlagert werden koennen,
     * damit das GUI bedienbar bleibt, und wie trotzdem Zwischenergebnisse im GUI angezeigt werden koennen.
     * </br>
     * Das Besondere an diesem Programmierbeispiel ist, dass die Swing-GUI-Klasse und die Worker-Klasse,
     * welche die zeitaufwaendige Aufgabe ausfuehrt, entkoppelt sind.
     * Normalerweise muss die Worker-Klasse Zwischenergebnisse ueber die SwingWorker.publish()-Methode uebergeben,
     * und ist damit fest mit dem Swing-GUI gekoppelt.
     * In diesem Beispiel ist die Worker-Klasse eigenstaendig und unabhaengig vom Swing-GUI.
     * Sie kann als Konsolenprogramm oder mit unterschiedlichen GUIs verwendet werden.
     * Trotzdem werden Zwischenergebnisse ueber einen Callback uebermittelt.
     */
    public class SwingWorkerCallbackGui
    {
       public static void main( String[] args )
       {
          starteSwingWorkerCallbackGui();
       }
    
       public static void starteSwingWorkerCallbackGui()
       {
          final String titel = "Textdateianzeige";
    
          final JTextField textdatei = new JTextField();
          textdatei.setText( (new File( "src/SwingWorkerCallbackGui.java" )).getAbsolutePath() );
    
          final String[] charsetAnzeigeNamen = new String[] {
                StandardCharsets.ISO_8859_1.name(), StandardCharsets.UTF_8.name(), StandardCharsets.UTF_16.name() };
          final JComboBox<String> charEncod = new JComboBox<String>( charsetAnzeigeNamen );
    
          final JTextArea textArea = new JTextArea();
          textArea.setFont( new Font( "Monospaced", Font.PLAIN, 11 ) );
          textArea.setEditable( false );
          DefaultCaret caret = (DefaultCaret) textArea.getCaret();
          caret.setUpdatePolicy( DefaultCaret.ALWAYS_UPDATE );
          JScrollPane areaScrollPane = new JScrollPane( textArea );
          areaScrollPane.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS );
          areaScrollPane.setPreferredSize( new Dimension( 250, 250 ) );
    
          final JButton button = new JButton( "Textdatei anzeigen" );
          button.addActionListener( new ActionListener() {
             @Override
             public void actionPerformed( ActionEvent ev ) {
                button.setEnabled( false );
                textArea.setBackground( Color.WHITE );
                textArea.setForeground( Color.BLACK );
                // Starte Extra-Thread per SwingWorker, damit der Event Dispatch Thread (EDT) nicht blockiert wird:
                (new TextDateiLesenSwingWorker( textdatei.getText(), charEncod.getSelectedItem().toString(), button, textArea )).execute();
             }
          } );
    
          JPanel panel1 = new JPanel();
          panel1.setBorder( BorderFactory.createEmptyBorder( 30, 30, 5, 30 ) );
          panel1.setLayout( new GridLayout( 1, 1 ) );
          panel1.add( new JLabel( titel ) );
    
          JPanel panel2a = new JPanel();
          panel2a.setBorder( BorderFactory.createEmptyBorder( 5, 30, 5, 0 ) );
          panel2a.setLayout( new GridLayout( 3, 1, 15, 15 ) );
          panel2a.add( new JLabel( "Textdatei" ) );
          panel2a.add( new JLabel( "Zeichenkodierung" ) );
          panel2a.add( new JLabel( "" ) );
    
          JPanel panel2b = new JPanel();
          panel2b.setBorder( BorderFactory.createEmptyBorder( 5, 0, 5, 30 ) );
          panel2b.setLayout( new GridLayout( 3, 1, 15, 15 ) );
          panel2b.add( textdatei );
          panel2b.add( charEncod );
          panel2b.add( button );
    
          JPanel panel3 = new JPanel();
          panel3.setBorder( BorderFactory.createEmptyBorder( 5, 30, 30, 30 ) );
          panel3.setLayout( new GridLayout( 1, 1 ) );
          panel3.add( areaScrollPane );
    
          JFrame frame = new JFrame( titel );
          frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
          frame.setResizable( false );
          frame.setLocation( 100, 100 );
          frame.setLayout( new BorderLayout( 10, 10 ) );
          frame.add( panel1,  BorderLayout.PAGE_START );
          frame.add( panel2a, BorderLayout.WEST );
          frame.add( panel2b, BorderLayout.EAST );
          frame.add( panel3,  BorderLayout.PAGE_END );
          frame.pack();
          frame.setVisible( true );
       }
    
       /** Auf Swing-Komponenten soll nur vom Swing Event Dispatch Thread (EDT) aus zugegriffen werden,
           um Multithreading-Probleme zu vermeiden.
           Andererseits sollen zeitaufwaendige Aufgaben nicht im EDT ausgefuehrt werden,
           damit dieser nicht blockiert wird und das GUI bedienbar bleibt.
           Der "SwingWorker" loest die Aufgabe, indem die zeitaufwaendigen Aufgaben in einen Extra-Background-Thread verlagert werden,
           und ueber einen Kommunikationsmechanismus die Swing-Komponenten Thread-sicher asynchron im EDT manipuliert werden. */
       public static final class TextDateiLesenSwingWorker extends SwingWorker<Boolean,String>
       {
          String    textdatei;
          String    charEncod;
          JButton   button;
          JTextArea textArea;
    
          public TextDateiLesenSwingWorker( String textdatei, String charEncod, JButton button, JTextArea textArea )
          {
             this.textdatei = textdatei;
             this.charEncod = charEncod;
             this.button    = button;
             this.textArea  = textArea;
          }
    
          /** Die "doInBackground()"-Methode wird in einem eigenen Background-Thread ausgefuehrt.
              Sie darf nicht direkt Swing-Komponenten manipulieren. */
          @Override
          protected Boolean doInBackground() throws Exception
          {
             final class SwingMeldungenCallback implements DateiLeserMitCallback.MeldungenCallback<String>
             {
                /** Die "publish()"-Methode sendet Zwischenergebnis-Objekte an die "process()"-Methode,
                    in welcher Swing-Aktionen Thread-sicher asynchron im EDT ausgefuehrt werden. */
                @SuppressWarnings("synthetic-access")
                @Override public void ausgabeMeldung( String s ) { publish( s ); }
             }
    
             /** Aufruf des eigentlichen Jobs. Zwischenergebnisse werden per Callback returniert.
                 Das finale Return-Ergebnis kann in der "done()"-Methode per "get()" abgefragt werden. */ 
             return DateiLeserMitCallback.liesTextdatei( textdatei, charEncod, new SwingMeldungenCallback() );
          }
    
          /** Die "process()"-Methode empfaengt die ueber "publish()" uebergebenen Objekte.
              Sie laeuft im EDT und kann asynchron Swing-Komponenten manipulieren. */
          @Override
          protected void process( List<String> chunks )
          {
             if( chunks != null && textArea != null ) {
                for( String s : chunks ) {
                   textArea.append( s + "\n" );
                }
             }
          }
    
          /** Die "done()"-Methode wird nach Beendigung der "doInBackground()"-Methode aufgerufen.
              Sie laeuft im EDT und kann Swing-Komponenten manipulieren. */
          @Override
          protected void done()
          {
             Boolean ret = Boolean.FALSE;
             try {
                /** Abfrage der Ergebnisses der "doInBackground()"-Methode: */
                ret = get();
             } catch( ExecutionException | InterruptedException | CancellationException ex ) { /* ok */ }
             if( textArea != null ) {
                if( ret != null && ret.booleanValue() ) {
                   textArea.setForeground( new Color( 0x008800 ) );
                } else {
                   textArea.setBackground( new Color( 0xFFFFEE ) );
                   textArea.setForeground( Color.RED );
                }
             }
             if( button != null ) { button.setEnabled( true ); }
          }
       }
    }
    
  4. Legen Sie im SwingWorkerCallback-Projektverzeichnis eine Batchdatei zum Starten des Konsolenprogramms an: run-Konsole.bat

    @echo.
    @if not exist bin md bin
    javac -d bin src/DateiLeserMitCallback.java
    java -cp bin DateiLeserMitCallback
    pause
    

    Führen Sie das Konsolenprogramm aus:

    cd \MeinWorkspace\SwingWorkerCallback

    run-Konsole.bat

  5. Legen Sie im SwingWorkerCallback-Projektverzeichnis eine Batchdatei zum Starten des Swing-GUI-Programms an: run-SwingGUI.bat

    @echo.
    @if not exist bin md bin
    javac -d bin -cp bin src/*.java
    start javaw -cp bin SwingWorkerCallbackGui
    

    Führen Sie das Swing-GUI-Programm aus:

    cd \MeinWorkspace\SwingWorkerCallback

    run-SwingGUI.bat

    SwingWorkerCallback



Weitere Themen: andere TechDocs | Grafikausgabe mit Java (AWT, Java 2D, JIMI, JAI) | JSF (JavaServer Faces) | JSP
© 2015 Torsten Horn, Aachen