|
|
Using the JFC/Swing Packages |
Note: This section is a simplified version of an article in The Swing Connection. For more information about Swing thread issues, see the article: Threads and Swing.
This section tells you how to use the Swing API in a thread-safe way. If your program is an applet, it's generally safe to construct its GUI in the
initmethod. If your program is an application with the following common pattern, then you're safe:However, if your program creates threads to perform tasks that affect the GUI, or it manipulates the already-visible GUI in response to anything but an AWT event, then read on! Four subsections follow; please read at least the first two.//Thread-safe example public class MyApplication { public static void main(String[] args) { JFrame f = new JFrame(...); ...//Add components to the frame here... f.pack(); f.setVisible(true); //Don't do any more GUI work here. } ... //All manipulation of the GUI -- setText, getText, etc. -- //is performed in event handlers such as actionPerformed(). ... }
- The Single-Thread Rule
- Swing components can be accessed by only one thread at a time. Generally, this thread is the event-dispatching thread.
- Exceptions to the Rule
- A few operations are guaranteed to be thread-safe.
- How to Execute Code in the Event-Dispatching Thread
- If you need access to the UI from outside event-handling or drawing code, then you can use the
SwingUtilitiesinvokeLaterorinvokeAndWaitmethod.- How to Create Threads
- If you need to create a thread -- for example, to handle a job that's computationally expensive or I/O bound -- you can use a thread utility class such as
SwingWorkerorTimer.The single-thread rule is as follows:
Rule: Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.
This rule might sound scary, but for many simple programs, you don't have to worry about threads. Before we go into detail about how to write Swing code, let's define two terms: realized and event-dispatching thread.
Realized means that the component's
paintmethod has been or might be called. A Swing component that's a top-level window is realized by having one of these methods invoked on it:setVisible(true),show, or (this might surprise you)pack. Once a window is realized, all components that it contains are realized. Another way to realize a component is to add it to a container that's already realized. You'll see examples of realizing components later.The event-dispatching thread is the thread that executes drawing and event-handling code. For example, the
paintandactionPerformedmethods are automatically executed in the event-dispatching thread. Another way to execute code in the event-dispatching thread is to use the SwingUtilitiesinvokeLatermethod.Exceptions to the Rule
There are a few exceptions to the rule that all code that might affect a realized Swing component must run in the event-dispatching thread:
- A few methods are thread-safe.
- In the Swing API documentation, thread-safe methods are marked with this text:
This method is thread safe, although most Swing methods are not. Please see Threads and Swing for more information.- An application's GUI can often be constructed and shown in the main thread.
- As long as no components (Swing or otherwise) have been realized in the current runtime environment, it's fine to construct and show a GUI in the main thread of an application. To help you see why, here's an analysis of the thread safety of the thread-safe example. To refresh your memory, here are the important lines from the example:
public static void main(String[] args) { JFrame f = new JFrame(...); ...//Add components to the frame here... f.pack(); f.setVisible(true); //Don't do any more GUI work here. }
- The example constructs the GUI in the main thread. In general, you can construct (but not show) a GUI in any thread, as long as you don't make any calls that refer to or affect already-realized components.
- The components in the GUI are realized by the
packcall.- Immediately afterward, the components in the GUI are shown with the
setVisible(orshow) call. Technically, thesetVisiblecall is unsafe because the components have already been realized by thepackcall. However, because the program doesn't already have a visible GUI, it's exceedingly unlikely that apaintcall will occur beforesetVisiblereturns.- The main thread executes no GUI code after the
setVisiblecall. This means that all GUI work moves from the main thread to the event-dispatching thread, and the example is, in practice, thread-safe.
- An applet's GUI can be constructed and shown in the
initmethod:- Existing browsers don't draw an applet until after its
initandstartmethods have been called. Thus, constructing the GUI in the applet'sinitmethod is safe, as long as you never callshow()orsetVisible(true)on the actual applet object.By the way, applets that use Swing components must be implemented as subclasses of
JApplet, and components should be added to theJAppletcontent pane, rather than directly to theJApplet. As for any applet, you should never perform time-consuming initialization in theinitorstartmethod; instead, you should start a thread that performs the time-consuming task. [PENDING: move this paragraph to the applet page, leaving an x-ref here.]
- The following
JComponentmethods are safe to call from any thread:
repaint,revalidate, andinvalidate.- The
repaintandrevalidatemethods enqueue requests for the event-dispatching thread to callpaintandvalidate, respectively. Theinvalidatemethod just marks a component and all of its direct ancestors as requiring validation.
- Listener lists can be modified from any thread.
- It's always safe to call the
addListenerTypeListenerandremoveListenerTypeListenermethods. The add/remove operations have no effect on an event dispatch that's under way.How to Execute Code in the Event-Dispatching Thread
Most post-initialization GUI work naturally occurs in the event-dispatching thread. Once the GUI is visible, most programs are driven by events such as button actions or mouse clicks, which are always handled in the event-dispatching thread.However, some programs need to perform non-event-driven GUI work after the GUI is visible. Here are some examples:
- Programs that must perform a lengthy initialization operation before they can be used:
- This kind of program should generally show some GUI while the initialization is occurring, and then update or change the GUI. The initialization should not occur in the event-dispatching thread; otherwise, repainting and event dispatch would stop. However, after initialization the GUI update/change should occur in the event-dispatching thread, for thread-safety reasons.
- Programs whose GUI must be updated as the result of non-AWT events:
- For example, suppose a server program can get requests from other programs that might be running on different machines. These requests can come at any time, and they result in one of the server's methods being invoked in some possibly unknown thread. How can that method update the GUI? By executing the GUI update code in the event-dispatching thread.
The
SwingUtilitiesclass provides two methods to help you run code in the event-dispatching thread:
invokeLater: Requests that some code be executed in the event-dispatching thread. This method returns immediately, without waiting for the code to execute.
invokeAndWait: Acts likeinvokeLater, except that this method waits for the code to execute. As a rule, you should useinvokeLaterinstead of this method.This page gives you some examples of using this API. Also see the BINGO example, especially the following classes:
CardWindow,ControlPane,Player, andOverallStatusPane.
Using the
invokeLaterMethodYou can call
invokeLaterfrom any thread to request the event-dispatching thread to run certain code. You must put this code in therunmethod of aRunnableobject and specify theRunnableobject as the argument toinvokeLater. TheinvokeLatermethod returns immediately, without waiting for the event-dispatching thread to execute the code. Here's an example of usinginvokeLater:Runnable doWorkRunnable = new Runnable() { public void run() { doWork(); } }; SwingUtilities.invokeLater(doWorkRunnable);Using the
invokeAndWaitMethodThe
invokeAndWaitmethod is just likeinvokeLater, except thatinvokeAndWaitdoesn't return until the event-dispatching thread has executed the specified code. Whenever possible, you should useinvokeLaterinstead ofinvokeAndWait. If you useinvokeAndWait, make sure that the thread that callsinvokeAndWaitdoes not hold any locks that other threads might need while the call is occurring.Here's an example of using
invokeAndWait:void showHelloThereDialog() throws Exception { Runnable showModalDialog = new Runnable() { public void run() { JOptionPane.showMessageDialog(myMainFrame, "Hello There"); } }; SwingUtilities.invokeAndWait(showModalDialog); }Similarly, a thread that needs access to GUI state, such as the contents of a pair of text fields, might have the following code:
void printTextField() throws Exception { final String[] myStrings = new String[2]; Runnable getTextFieldText = new Runnable() { public void run() { myStrings[0] = textField0.getText(); myStrings[1] = textField1.getText(); } }; SwingUtilities.invokeAndWait(getTextFieldText); System.out.println(myStrings[0] + " " + myStrings[1]); }How to Create Threads
If you can get away with it, avoid using threads. Threads can be difficult to use, and they make programs harder to debug. In general, they just aren't necessary for strictly GUI work, such as updating component properties.
However, sometimes threads are necessary. Here are some typical situations where threads are used:
- To perform a time-consuming task without locking up the event-dispatching thread. Examples include making extensive calculations, doing something that results in many classes being loaded (initialization, for example), and blocking for network or disk I/O.
- To perform an operation repeatedly, usually with some predetermined period of time between operations.
- To wait for messages from clients.
You can use two classes to help you implement threads:
SwingWorker: Creates a background thread to execute time-consuming operations.
Timer: Creates a thread that executes some code one or more times, with a user-specified delay between executions. For detailed information about timers, see How to Use Timers.Using the
SwingWorkerClassTheSwingWorkerclass is implemented inSwingWorker.java, which is not in the Swing release.SwingWorkerdoes all the dirty work of implementing a background thread. Although many programs don't need background threads, background threads are sometimes useful for performing time-consuming operations, which can improve the perceived performance of a program.To use the
SwingWorkerclass, you first create a subclass of it. In the subclass, you must implement theconstructmethod so that it contains the code to perform your lengthy operation. When you instantiate yourSwingWorkersubclass, theSwingWorkercreates a thread that calls yourconstructmethod. When you need the object returned by theconstructmethod, you call theSwingWorker'sgetmethod. Here's an example of usingSwingWorker:...//in the main method: final SwingWorker worker = new SwingWorker() { public Object construct() { return new ExpensiveDialogComponent(); } }; ...//in an action event handler: JOptionPane.showMessageDialog (f, worker.get());When the program's
mainmethod creates theSwingWorkerobject, theSwingWorkerimmediately starts a new thread that instantiatesExpensiveDialogComponent. Themainmethod also constructs a GUI that consists of a window with a button.When the user clicks the button, the program blocks, if necessary, until the
ExpensiveDialogComponenthas been created. The program then shows a modal dialog containing theExpensiveDialogComponent. You can find the entire program inPasswordDemo.java. Also, the example program provided in How to Use Progress Bars runs a long task in aSwingWorkerthread.
|
|
Using the JFC/Swing Packages |