Java/JDK 6/Swing Worker
Swing Worker from JDK 6 SE
<source lang="java">
import java.awt.LayoutManager; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingWorker; public class SwingWorkerDemo {
public static void main(String[] args) { JTextArea textArea = new JTextArea(10, 20); final JProgressBar progressBar = new JProgressBar(0, 10); final CounterTask task = new CounterTask(); task.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if ("progress".equals(evt.getPropertyName())) { progressBar.setValue((Integer) evt.getNewValue()); } } }); JButton startButton = new JButton("Start"); startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { task.execute(); } }); JButton cancelButton = new JButton("Cancel"); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { task.cancel(true); } }); JPanel buttonPanel = new JPanel(); buttonPanel.add(startButton); buttonPanel.add(cancelButton); JPanel cp = new JPanel(); LayoutManager layout = new BoxLayout(cp, BoxLayout.Y_AXIS); cp.setLayout(layout); cp.add(buttonPanel); cp.add(new JScrollPane(textArea)); cp.add(progressBar); JFrame frame = new JFrame("SwingWorker Demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(cp); frame.pack(); frame.setVisible(true); }
} class CounterTask extends SwingWorker<Integer, Integer> {
private static final int DELAY = 1000; public CounterTask() { } @Override protected Integer doInBackground() throws Exception { int i = 0; int count = 10; while (!isCancelled() && i < count) { i++; publish(new Integer[] { i }); setProgress(count * i / count); Thread.sleep(DELAY); } return count; } protected void process(List<Integer> chunks) { for (int i : chunks) System.out.println(i); } @Override protected void done() { if (isCancelled()) System.out.println("Cancelled !"); else System.out.println("Done !"); }
}
</source>
Swing worker from org.jdesktop.swingworker
<source lang="java">
/*
* $Id: AccumulativeRunnable.java,v 1.2 2006/09/28 20:20:28 idk Exp $ * * Copyright ?2005 Sun Microsystems, Inc. All rights * reserved. Use is subject to license terms. */
package org.jdesktop.swingworker; import java.util.*; import javax.swing.SwingUtilities; /**
* An abstract class to be used in the cases where we need {@code Runnable} * to perform some actions on an appendable set of data. * The set of data might be appended after the {@code Runnable} is * sent for the execution. Usually such {@code Runnables} are sent to * the EDT. **
* Usage example: * * <p> * Say we want to implement JLabel.setText(String text) which sends * {@code text} string to the JLabel.setTextImpl(String text) on the EDT. * In the event JLabel.setText is called rapidly many times off the EDT * we will get many updates on the EDT but only the last one is important. * (Every next updates overrides the previous one.) * We might want to implement this {@code setText} in a way that only * the last update is delivered. * <p> * Here is how one can do this using {@code AccumulativeRunnable}: *
* AccumulativeRunnable<String> doSetTextImpl = * new AccumulativeRunnable<String>() { * @Override * protected void run(List<String> args) { * //set to the last string being passed * setTextImpl(args.get(args.size() - 1); * } * } * void setText(String text) { * //add text and send for the execution if needed. * doSetTextImpl.add(text); * } *
* * <p> * Say we want want to implement addDirtyRegion(Rectangle rect) * which sends this region to the * handleDirtyRegions(List<Rect> regions) on the EDT. * addDirtyRegions better be accumulated before handling on the EDT. * * <p> * Here is how it can be implemented using AccumulativeRunnable:*
* AccumulativeRunnable<Rectangle> doHandleDirtyRegions = * new AccumulativeRunnable<Rectangle>() { * @Override * protected void run(List<Rectangle> args) { * handleDirtyRegions(args); * } * }; * void addDirtyRegion(Rectangle rect) { * doHandleDirtyRegions.add(rect); * } *
* * @author Igor Kushnirskiy * @version $Revision: 1.2 $ $Date: 2006/09/28 20:20:28 $ * * @param <T> the type this {@code Runnable} accumulates * */
abstract class AccumulativeRunnable<T> implements Runnable {
private List<T> arguments = null; /** * Equivalent to {@code Runnable.run} method with the * accumulated arguments to process. * * @param args accumulated arguments to process. */ protected abstract void run(List<T> args); /** * {@inheritDoc} * * <p> * This implementation calls {@code run(List<T> args)} method * with the list of accumulated arguments. */ public final void run() { run(flush()); } /** * appends arguments and sends this {@code Runnable} for the * execution if needed. * <p> * This implementation uses {@see #submit} to send this * {@code Runnable} for execution. * @param args the arguments to accumulate */ public final synchronized void add(T... args) { boolean isSubmitted = true; if (arguments == null) { isSubmitted = false; arguments = new ArrayList<T>(); } Collections.addAll(arguments, args); if (!isSubmitted) { submit(); } } /** * Sends this {@code Runnable} for the execution * * <p> * This method is to be executed only from {@code add} method. * * <p> * This implementation uses {@code SwingWorker.invokeLater}. */ protected void submit() { SwingUtilities.invokeLater(this); } /** * Returns accumulated arguments and flashes the arguments storage. * * @return accumulated arguments */ private final synchronized List<T> flush() { List<T> list = arguments; arguments = null; return list; }
} /////////////////////////////////////////////////////////// /*
* $Id: SwingPropertyChangeSupport.java,v 1.1 2005/06/18 21:27:14 idk Exp $ * * Copyright ?2005 Sun Microsystems, Inc. All rights * reserved. Use is subject to license terms. */
package org.jdesktop.swingworker; import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeEvent; import javax.swing.SwingUtilities; /**
* This subclass of {@code java.beans.PropertyChangeSupport} is almost * identical in functionality. The only difference is if constructed with * {@code SwingPropertyChangeSupport(sourceBean, true)} it ensures * listeners are only ever notified on the Event Dispatch Thread. * * @author Igor Kushnirskiy * @version $Revision: 1.1 $ $Date: 2005/06/18 21:27:14 $ */
public final class SwingPropertyChangeSupport extends PropertyChangeSupport {
/** * Constructs a SwingPropertyChangeSupport object. * * @param sourceBean The bean to be given as the source for any * events. * @throws NullPointerException if {@code sourceBean} is * {@code null} */ public SwingPropertyChangeSupport(Object sourceBean) { this(sourceBean, false); } /** * Constructs a SwingPropertyChangeSupport object. * * @param sourceBean the bean to be given as the source for any events * @param notifyOnEDT whether to notify listeners on the Event * Dispatch Thread only * * @throws NullPointerException if {@code sourceBean} is * {@code null} * @since 1.6 */ public SwingPropertyChangeSupport(Object sourceBean, boolean notifyOnEDT) { super(sourceBean); this.notifyOnEDT = notifyOnEDT; } /** * {@inheritDoc} * * <p> * If {@see #isNotifyOnEDT} is {@code true} and called off the * Event Dispatch Thread this implementation uses * {@code SwingUtilities.invokeLater} to send out the notification * on the Event Dispatch Thread. This ensures listeners * are only ever notified on the Event Dispatch Thread. * * @throws NullPointerException if {@code evt} is * {@code null} * @since 1.6 */ public void firePropertyChange(final PropertyChangeEvent evt) { if (evt == null) { throw new NullPointerException(); } if (! isNotifyOnEDT() || SwingUtilities.isEventDispatchThread()) { super.firePropertyChange(evt); } else { SwingUtilities.invokeLater( new Runnable() { public void run() { firePropertyChange(evt); } }); } } /** * Returns {@code notifyOnEDT} property. * * @return {@code notifyOnEDT} property * @see #SwingPropertyChangeSupport(Object sourceBean, boolean notifyOnEDT) * @since 1.6 */ public final boolean isNotifyOnEDT() { return notifyOnEDT; } // Serialization version ID static final long serialVersionUID = 7162625831330845068L; /** * whether to notify listeners on EDT * * @serial * @since 1.6 */ private final boolean notifyOnEDT;
}
/////////////////////////////////////////////////////////// /*
* $Id: SwingWorker.java,v 1.5 2007/03/01 19:55:54 idk Exp $ * * Copyright ?2005 Sun Microsystems, Inc. All rights * reserved. Use is subject to license terms. */
package org.jdesktop.swingworker; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeEvent; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.*; import java.awt.event.*; import javax.swing.SwingUtilities; import javax.swing.Timer; /**
* An abstract class to perform lengthy GUI-interacting tasks in a * dedicated thread. * * <p> * When writing a multi-threaded application using Swing, there are * two constraints to keep in mind: * (refer to *
Swing Worker Image Loader
<source lang="java">
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.List; import java.util.ArrayList; import java.io.File; import java.io.IOException; import java.awt.Image; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextArea; import javax.imageio.ImageIO; import org.jdesktop.swingworker.SwingWorker; public class ImageLoader extends JFrame {
private JTextArea log; private JPanel viewer; public ImageLoader() { super("Image Loader"); this.log = new JTextArea(4, 4); this.viewer = new JPanel(); JButton start = new JButton("Start"); start.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String[] files = new String[] { "Bodie_small.png", "Carmela_small.png", "Death Valley_small.png", "Lake_small.png" }; new ImageLoadingWorker(log, viewer, files).execute(); } }); add(new JScrollPane(log), BorderLayout.NORTH); add(new JScrollPane(viewer), BorderLayout.CENTER); add(start, BorderLayout.SOUTH); setSize(360, 280); } public static void main(String... args) { SwingUtilities.invokeLater(new Runnable() { public void run() { new ImageLoader().setVisible(true); } }); }
}
// Final result is a list of Image // Intermediate result is a message as a String class ImageLoadingWorker extends SwingWorker<List<Image>, String> {
private JTextArea log; private JPanel viewer; private String[] filenames; public ImageLoadingWorker(JTextArea log, JPanel viewer, String... filenames) { this.log = log; this.viewer = viewer; this.filenames = filenames; } // In the EDT @Override protected void done() { try { for (Image image : get()) { viewer.add(new JLabel(new ImageIcon(image))); viewer.revalidate(); } } catch (Exception e) { } } // In the EDT @Override protected void process(String... messages) { for (String message : messages) { log.append(message); log.append("\n"); } } // In a thread @Override public List<Image> doInBackground() { List<Image> images = new ArrayList<Image>(); for (String filename : filenames) { try { images.add(ImageIO.read(new File("./build/"+filename))); publish("Loaded " + filename); } catch (IOException ioe) { publish("Error loading " + filename); } } return images; }
}
</source>