Because you very often need to be able to update a Swing components from
outside the event thread, Swing provides two useful methods which allow you to
cause your code to run in the event loop. They are static methods in the javax.swing.SwingUtilities
class (also in java.awt.EventQueue):
public static void invokeLater(Runnable doRun) public static void invokeAndWait(Runnable doRun)
throws InterruptedException,
InvocationTargetException
In each case, you must provide an instance of a class which implements the
Runnable interface. Each method will cause the run() method of this class
to be invoked asynchronously in the event dispatching thread. It will be
processed in turn, based on what other requests are pending in that thread.
An invokeLater() call will simply cause the scheduling
of the Runnable class
code.
An invokeAndWait() call will will block the thread until all pending AWT
events have been processed and (then) doRun.run() returns. For
obvious reasons, it should not be called from the event dispatch
thread.
Example
Here's an example. It is a modification of the first program to make
the GUI update thread-safe.
package notPotentialCorruption;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
/**
* This class invokes multiple threads which (incorrectly) update the contents
* of a Swing component from outside the event thread.
*/
public class NotPotentialCorruptor extends JFrame
{
//Construct the applet
public NotPotentialCorruptor()
{
setTitle("Not Potential Corruptor Example");
getContentPane().add( new ButtonPanel() );
pack();
}
class ButtonPanel extends JPanel implements ActionListener
{
ButtonPanel()
{
setBackground(Color.blue);
m_clickMeButton.addActionListener(this);
add(m_clickMeButton);
m_nowClickMeButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent ev)
{
ButtonPanel.this.setBackground(Color.yellow);
}
}
);
m_nowClickMeButton.setEnabled(false); // Disable for now
add(m_nowClickMeButton);
add( new JScrollPane(m_statusArea) );
}
public void actionPerformed(ActionEvent ev)
{
setBackground(Color.red);
m_nowClickMeButton.setEnabled(true);
for (int i = 1; i < 10; i++)
{
Thread thread = new CorruptorThread("Thread " + i);
thread.start();
}
}
private JButton m_clickMeButton = new JButton("Click me!");
private JButton m_nowClickMeButton = new JButton("Now try to click me!");
private JTextArea m_statusArea = new JTextArea(10, 20);
class CorruptorThread extends Thread
{
CorruptorThread(String name)
{
super(name);
}
public void run()
{
for (int i = 0; i < 100; i++)
{
try
{
Thread.sleep(1);
update("" + i);
}
catch (InterruptedException ie)
{
ie.printStackTrace();
}
}
update("Done");
}
private void update(final String text)
{
Runnable runnable = new Runnable()
{
public void run()
{
String temp = m_statusArea.getText();
temp += "\n" + getName() + ": " + text;
m_statusArea.setText(temp);
}
};
SwingUtilities.invokeLater(runnable);
}
}
}
public static void main(String[] args)
{
NotPotentialCorruptor frame = new NotPotentialCorruptor();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
} |
Note that the code inside the update method has been enclosed within the
run() method of an inner class which implements the Runnable interface.
The inner class is then passed into a call to the invokeLater() method.
Try It!
Here is an applet that invokes the above code when you click on its
"Start" button:
|
|
After you've brought the JFrame up,click on the "Click me!"
button, and see what actually happens!
This program is not an example of
what you should typically do. It is merely a somewhat
extreme example of how to solve a GUI synchronization problem.
|
Results
I hope that you observe that there is no corruption in the JTextArea, and
that the program eventually completes without incurring any corruption problems.
|