|
|
< Day Day Up > |
|
7.4 Long-Running Event CallbacksThere's another case when Swing programs and threads interact: a long-running event callback. While an event callback is executing, the rest of the GUI is unresponsive. If this happens for a long period of time, it can be very frustrating to users, who often assume that the program has hung. It's far better to execute the long-running task in a separate thread, providing GUI feedback as appropriate. This task can be accomplished in a few ways. By now, you should be familiar enough with thread programming to spawn your own thread and execute the task, and that's often the simplest route to take. A utility class called the SwingWorker class, available on Sun's java.sun.com web site, can handle many of the threading details for you (but, in the end, it is not really any easier than spawning your own thread). If you're going to have a lot of tasks like this, though, the easiest thing to do is use a thread pool or a task scheduler. If you have a lot of tasks to execute in parallel, you can use a thread pool (see Chapter 10). If you have only a single task to execute every now and then, you can use a task scheduler (see Chapter 11). Here's an example of how to take the first path and set up a thread in a long-running callback. Suppose that in our type tester, the start method must log into a server in order to get the data it is to display. You want to perform that operation in a separate thread because it may take a long time, during which you don't want the GUI to be unresponsive. In fact, you want to give the user an option to cancel that operation in case communicating with the server takes too long. Here's a class that simulates connecting to the server. While it's at it, the frame displays some progress messages: package javathreads.examples.ch07.example3;
import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class FeedbackFrame extends JFrame implements Runnable {
private SwingTypeTester stt;
private Thread t;
private JLabel label;
private int state;
static String[] stateMessages = {
"Connecting to server...",
"Logging into server...",
"Waiting for data...",
"Complete"
};
public FeedbackFrame(SwingTypeTester stt) {
this.stt = stt;
setupFrame( );
t = new Thread(this);
t.start( );
pack( );
show( );
}
private void setupFrame( ) {
label = new JLabel( );
label.setPreferredSize(new Dimension(200, 200));
Container c = getContentPane( );
JButton stopButton = new JButton("Stop");
stopButton.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent ae) {
error( );
}
});
c.add(label, BorderLayout.NORTH);
c.add(stopButton, BorderLayout.SOUTH);
}
private void setText(final String s) {
try {
SwingUtilities.invokeAndWait(new Runnable( ) {
public void run( ) {
label.setText(s);
}
});
} catch (InterruptedException ie) {
error( );
} catch (InvocationTargetException ite) {
error( );
}
}
private void error( ) {
t.interrupt( );
if (SwingUtilities.isEventDispatchThread( ))
closeDown( );
else SwingUtilities.invokeLater(new Runnable( ) {
public void run( ) {
closeDown( );
}
});
}
private void closeDown( ) {
stt.setupCancelled( );
hide( );
dispose( );
}
public void run( ) {
// Simulate connecting to server
for (int i = 0; i < stateMessages.length; i++) {
setText(stateMessages[i]);
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException ie) {}
if (Thread.currentThread( ).isInterrupted( ))
return;
}
SwingUtilities.invokeLater(new Runnable( ) {
public void run( ) {
stt.setupDone( );
hide( );
dispose( );
}
});
}
}We've used all our Swing utilities and techniques in this example. The component itself is a frame, and it starts a new thread. Every few seconds, that thread displays a new status message in the frame by calling the setText() method. That method isn't executing on the event-dispatching thread, so it must use the invokeAndWait() method to pass the text to the label. When the thread has finished displaying status messages (meaning that in the real world, it has connected to the server), it informs the SwingTypeTester class that setup is complete梐nd since that class expects everything to run on the event-dispatching thread, the setupDone() method must be called from an invokeLater() method. When the server gets an error or the user presses the Stop button, we need to tell the SwingTypeTester component that setup was cancelled. The code is the same, but the context is different: the actionPerformed() method runs on the event-dispatching thread while the exception in the run() method runs on a separate thread. So we must use the isEventDispatchThread( ) method to determine how to call the Swing components. |
|
|
< Day Day Up > |
|