Java/JDK 6/Swing Worker
Swing Worker from JDK 6 SE
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 !");
}
}
Swing worker from org.jdesktop.swingworker
/*
* $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.
*
* <p>
* 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}:
* <pre>
* 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);
* }
* </pre>
*
* <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:
* <pre>
* AccumulativeRunnable<Rectangle> doHandleDirtyRegions =
* new AccumulativeRunnable<Rectangle>() {
* @Override
* protected void run(List<Rectangle> args) {
* handleDirtyRegions(args);
* }
* };
* void addDirtyRegion(Rectangle rect) {
* doHandleDirtyRegions.add(rect);
* }
* </pre>
*
* @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 <i>Event Dispatch Thread</i>.
*
* @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 <i>Event
* Dispatch Thread</i> 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
* <i>Event Dispatch Thread</i> this implementation uses
* {@code SwingUtilities.invokeLater} to send out the notification
* on the <i>Event Dispatch Thread</i>. This ensures listeners
* are only ever notified on the <i>Event Dispatch Thread</i>.
*
* @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 ==
<!-- start source code -->
<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;
}
}