import java.awt.*;
import java.util.*;
import javax.swing.*;
/**
* This program demonstrates that a thread that runs in parallel with the event
* dispatch thread can cause errors in Swing components.
* @version 1.24 2015 -06 -21
* @author Cay Horstmann
*/
public class SwingThreadTest
{
public static void main(String[] args)
{
EventQueue.invokeLater(() -> {
JFrame frame = new SwingThreadFrame();
frame.setTitle("SwingThreadTest" );
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true );
}) ;
}
}
/**
* This frame has two buttons to fill a combo box from a separate thread . The
* "Good " button uses the event queue , the "Bad " button modifies the combo box
* directly .
*/
class SwingThreadFrame extends JFrame
{
public SwingThreadFrame ()
{
final JComboBox <Integer > combo = new JComboBox <>() ;
combo .insertItemAt (Integer.MAX_VALUE, 0 ) ;
combo .setPrototypeDisplayValue (combo.getItemAt(0 )) ;
combo .setSelectedIndex (0 ) ;
JPanel panel = new JPanel () ;
JButton goodButton = new JButton ("Good" ) ;
goodButton .addActionListener (event ->
new Thread(new GoodWorkerRunnable(combo)).start()) ;
panel .add (goodButton) ;
JButton badButton = new JButton ("Bad" ) ;
badButton .addActionListener (event ->
new Thread(new BadWorkerRunnable(combo)).start()) ;
panel .add (badButton) ;
panel .add (combo) ;
add (panel) ;
pack () ;
}
}
/**
* This runnable modifies a combo box by randomly adding and removing numbers .
* This can result in errors because the combo box methods are not synchronized
* and both the worker thread and the event dispatch thread access the combo
* box .
*/
class BadWorkerRunnable implements Runnable
{
private JComboBox <Integer > combo ;
private Random generator ;
public BadWorkerRunnable (JComboBox<Integer> aCombo)
{
combo = aCombo ;
generator = new Random () ;
}
public void run ()
{
try
{
while (true )
{
int i = Math .abs (generator.nextInt()) ;
if (i % 2 == 0 )
combo .insertItemAt (i, 0 ) ;
else if (combo.getItemCount() > 0 )
combo .removeItemAt (i % combo.getItemCount()) ;
Thread .sleep (1 ) ;
}
}
catch (InterruptedException e)
{
}
}
}
/**
* This runnable modifies a combo box by randomly adding and removing numbers .
* In order to ensure that the combo box is not corrupted , the editing
* operations are forwarded to the event dispatch thread .
*/
class GoodWorkerRunnable implements Runnable
{
private JComboBox <Integer > combo ;
private Random generator ;
public GoodWorkerRunnable (JComboBox<Integer> aCombo)
{
combo = aCombo ;
generator = new Random () ;
}
public void run ()
{
try
{
while (true )
{
EventQueue .invokeLater (() ->
{
int i = Math.abs(generator.nextInt());
if (i % 2 == 0 )
combo.insertItemAt(i, 0 );
else if (combo.getItemCount() > 0 )
combo.removeItemAt(i % combo.getItemCount());
}) ;
Thread .sleep (1 ) ;
}
}
catch (InterruptedException e)
{
}
}
}
import java.awt.*;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import javax.swing.*;
/**
* This program demonstrates a worker thread that runs a potentially time-consuming task.
* @version 1.11 2015 -06 -21
* @author Cay Horstmann
*/
public class SwingWorkerTest
{
public static void main(String[] args) throws Exception
{
EventQueue.invokeLater(() -> {
JFrame frame = new SwingWorkerFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true );
}) ;
}
}
/**
* This frame has a text area to show the contents of a text file , a menu to open a file and
* cancel the opening process , and a status line to show the file loading progress .
*/
class SwingWorkerFrame extends JFrame
{
private JFileChooser chooser ;
private JTextArea textArea ;
private JLabel statusLine ;
private JMenuItem openItem ;
private JMenuItem cancelItem ;
private SwingWorker <StringBuilder , ProgressData > textReader ;
public static final int TEXT_ROWS = 20;
public static final int TEXT_COLUMNS = 60;
public SwingWorkerFrame ()
{
chooser = new JFileChooser () ;
chooser .setCurrentDirectory (new File("." )) ;
textArea = new JTextArea (TEXT_ROWS, TEXT_COLUMNS) ;
add (new JScrollPane(textArea)) ;
statusLine = new JLabel (" " ) ;
add (statusLine, BorderLayout.SOUTH) ;
JMenuBar menuBar = new JMenuBar () ;
setJMenuBar (menuBar) ;
JMenu menu = new JMenu ("File" ) ;
menuBar .add (menu) ;
openItem = new JMenuItem ("Open" ) ;
menu .add (openItem) ;
openItem .addActionListener (event -> {
// show file chooser dialog
int result = chooser.showOpenDialog(null );
// if file selected, set it as icon of the label
if (result == JFileChooser.APPROVE_OPTION)
{
textArea.setText("" );
openItem.setEnabled(false );
textReader = new TextReader(chooser.getSelectedFile());
textReader.execute();
cancelItem.setEnabled(true );
}
}) ;
cancelItem = new JMenuItem ("Cancel" ) ;
menu .add (cancelItem) ;
cancelItem .setEnabled (false ) ;
cancelItem .addActionListener (event -> textReader.cancel(true )) ;
pack () ;
}
private class ProgressData
{
public int number ;
public String line ;
}
private class TextReader extends SwingWorker <StringBuilder , ProgressData >
{
private File file ;
private StringBuilder text = new StringBuilder () ;
public TextReader (File file)
{
this .file = file ;
}
// The following method executes in the worker thread ; it doesn 't touch Swing components .
@Override
public StringBuilder doInBackground () throws IOException , InterruptedException
{
int lineNumber = 0;
try (Scanner in = new Scanner(new FileInputStream(file), "UTF-8" ))
{
while (in .hasNextLine())
{
String line = in .nextLine () ;
lineNumber ++;
text .append (line) .append ("\n" ) ;
ProgressData data = new ProgressData () ;
data .number = lineNumber ;
data .line = line ;
publish (data) ;
Thread .sleep (1 ) ; // to test cancellation ; no need to do this in your programs
}
}
return text ;
}
// The following methods execute in the event dispatch thread .
@Override
public void process (List<ProgressData> data)
{
if (isCancelled()) return ;
StringBuilder b = new StringBuilder () ;
statusLine .setText ("" + data.get(data.size() - 1 ).number) ;
for (ProgressData d : data) b .append (d.line) .append ("\n" ) ;
textArea .append (b.toString()) ;
}
@Override
public void done ()
{
try
{
StringBuilder result = get () ;
textArea .setText (result.toString()) ;
statusLine .setText ("Done" ) ;
}
catch (InterruptedException ex)
{
}
catch (CancellationException ex)
{
textArea .setText ("" ) ;
statusLine .setText ("Cancelled" ) ;
}
catch (ExecutionException ex)
{
statusLine .setText ("" + ex.getCause()) ;
}
cancelItem .setEnabled (false ) ;
openItem .setEnabled (true ) ;
}
};
}