Using SwingWorker Threading Mode
It is very important for Swing developers to use concurrency carefully. A good Swing program uses concurrency mechanisms to create user interfaces that do not lose response - no matter what kind of user interaction, the program can always respond to it. To create a responsive program, developers must learn how to use multithreading in the Swing framework.
A Swing developer will deal with the following types of threads:
(1) Initial threads, such threads will execute the initialization application code.
(2) The event dispatch thread, all event processing codes are executed here. Most code that interacts with the Swing framework must execute this thread as well.
(3) Worker threads, also known as background threads, will perform all time-consuming tasks.
Developers do not need to create these threads explicitly in their code: they are provided by the runtime or Swing framework. The job of developers is to use these threads to create responsive, persistent Swing programs.
Like all other programs running on Java platforms, a Swing program can create additional threads and thread pools, which requires the use of the approach that will be introduced in this article. This article will introduce the above three threads. The discussion of worker threads will involve using the javax.swing.SwingWorker class. This class has many useful features, including communication and collaboration between worker thread tasks and other thread tasks.
1. Initial thread
Each program generates a series of threads at the beginning of the application logic. In a standard program, there is only one such thread: this thread will call the main method in the program's main class. In an applet, the initial thread is a constructor of the applet object, which will call the init method; these actions may be executed in a single thread, or in two or three different threads, all depending on the specific implementation of the Java platform. In this article, we call this type of thread initial threads.
In a Swing program, there is not much to do in the initial thread. Their most basic task is to create a Runnable object that initializes the GUI and orchestrates the objects used to execute events in the event dispatch thread. Once the GUI is created, the program will be driven primarily by GUI events, each of which will cause the execution of an event in the event dispatch thread. Program code can orchestrate additional tasks to event-driven threads (provided that they are executed quickly so that they do not interfere with the processing of events) or create worker threads (used to perform time-consuming tasks).
An initial thread orchestration GUI creation task is by calling javax.swing.SwingUtilities.invokeLater or javax.swing.SwingUtilities.invokeAndWait. Both methods take a unique parameter: Runnable is used to define new tasks. The only difference between them is: invokerLater only orchestrates the task and returns; invokeAndWait will wait for the task to be executed before returning.
See the following example:
SwingUtilities.invokeLater(new Runnable()) { public void run() { createAndShowGUI(); }} In applet, the task of creating the GUI must be put into the init method and use invokeAndWait; otherwise, the initial process will be possible before the GUI is created, which may cause problems. In other cases, orchestration GUI creation tasks are usually the last one in the initial thread to be executed, so both use invokeLater or invokeAndWait.
Why doesn't the initial thread create the GUI directly? Because almost all code used to create and interact with Swing components must be executed in the event dispatch thread. This constraint will be discussed below.
2. Event distribution thread
The processing code of the Swing event is executed in a special thread, which is called the event dispatch thread. Most of the code that calls the Swing method is executed in this thread. This is necessary because most Swing objects are "non-thread safe".
You can think of the execution of code as performing a series of short tasks in the event dispatch thread. Most tasks are called by event handling methods, such as ActionListener.actionPerformed. The remaining tasks will be orchestrated by the program code, using invokeLater or invokeAndWait. Tasks in the event dispatch thread must be able to be executed quickly. Otherwise, unprocessed events will be backlogged and the user interface will become "responsive".
If you need to determine whether your code is executed in the event dispatch thread, call javax.swing.SwingUtilities.isEventDispatchThread.
3. Worker threads and SwingWorker
When a Swing program needs to perform a long task, it will usually be done using a worker thread. Each task is executed in a worker thread, which is an instance of the javax.swing.SwingWorker class. The SwingWorker class is an abstract class; you have to define its subclass to create a SwingWorker object; usually use anonymous inner classes to do this.
SwingWorker provides some communication and control features:
(1) The subclass of SwingWorker can define a method, done. When the background task is completed, it will be automatically called by the event dispatch thread.
(2) SwingWorker class implements java.util.concurrent.Future. This interface allows background tasks to provide a return value to other threads. The methods in this interface also provide the functions that allow the undoing of background tasks and determine whether the background tasks have been completed or revoked.
(3) Background tasks can provide intermediate results by calling SwingWorker.publish, and the event dispatch thread will call this method.
(4) Background tasks can define binding properties. Changes in the binding attribute will trigger events, and the event dispatch thread will call the event handler to handle these triggered events.
4. Simple backend tasks
Here is an example, this task is very simple, but it is a potentially time-consuming task. TumbleItem applet imports a series of image files. If these image files are imported through the initial thread, there will be a delay before the GUI appears. If these image files are imported in the event dispatch thread, the GUI may temporarily fail to respond.
To solve these problems, the TumbleItem class creates and executes an instance of the StringWorker class when it is initialized. The doInBackground method of this object is executed in a worker thread, imports the image into an ImageIcon array, and returns a reference to it. Then the done method is executed in the event dispatch thread, get the returned reference, and put it in the member variable imgs of the applet class. Doing this allows the TumbleItem class to create a GUI immediately without having to wait for the image import to complete.
The following sample code defines and implements a SwingWorker object.
SwingWorker worker = new SwingWorker<ImageIcon[], Void>() { @Override public ImageIcon[] doInBackground() { final ImageIcon[] innerImgs = new ImageIcon[nimgs]; for (int i = 0; i < nimgs; i++) { innerImgs[i] = loadImage(i+1); } return innerImgs; } @Override public void done() { //Remove the "Loading images" label. animator.removeAll(); loopslot = -1; try { imgs = get(); } catch (InterruptedException ignore) {} catch (java.util.concurrent.ExecutionException e) { String why = null; Throwable cause = e.getCause(); if (cause != null) { why = cause.getMessage(); } else { why = e.getMessage(); } System.err.println("Error retrieving file: " + why); } } }}; All subclasses inherited from SwingWorker must implement doInBackground; the implementation of the done method is optional.
Note that SwingWorker is a paradigm class with two parameters. The first type parameter specifies the return type of doInBackground. It is also a type of get method, which can be called by other threads to obtain the return value from doInBackground. The second type parameter specifies the type of the intermediate result. This example does not return the intermediate result, so it is set to void.
Using the get method, you can make the reference to the object imgs (created in the worker thread) be used in the event dispatch thread. This allows sharing objects between threads.
There are actually two ways to get the object returned by the doInBackground class.
(1) There are no parameters to call SwingWorker.get. If the background task is not completed, the get method will block until it completes.
(2) Call SwingWorker.get with parameters to specify timeout. If the background task is not completed, block until it completes - unless the timeout expires, in which case get will throw a java.util.concurrent.TimeoutException.
5. Tasks with intermediate results
It is useful to have a working backend task provide intermediate results. Backend tasks can call the SwingWorker.publish method to do this. This method accepts many parameters. Each parameter must be one specified by the second type parameter of SwingWorker.
The SwingWorker.process can be override to save the results provided by the publish method. This method is called by the event dispatch thread. The result set from the publish method is usually collected by a process method.
Let's take a look at the examples provided by Filpper.java. This program generates a series of random boolean tests java.util.Random through a background task. It's like a coin throwing experiment. To report its results, the background task uses an object FlipPair.
private static class FlipPair { private final long heads, total; FlipPair(long heads, long total) { this.heads = heads; this.total = total; }} heads represent the result of true; total represents the total number of throws.
The background program is an instance of FilpTask:
private class FlipTask extends SwingWorker<Void, FlipPair> {
Because the task does not return a final result, there is no need to specify what the first type parameter is, use Void. After each "throwing", the task calls publish:
@Overrideprotected Void doInBackground() { long heads = 0; long total = 0; Random random = new Random(); while (!isCancelled()) { total++; if (random.nextBoolean()) { heads++; } publish(new FlipPair(heads, total)); } return null;} Since publish is often called, many FlipPair values will be collected before the process method is called by the event dispatch thread; the process only focuses on the last set of values returned each time, and uses it to update the GUI:
protected void process(List pairs) { FlipPair pair = pairs.get(pairs.size() - 1); headsText.setText(String.format("%d", pair.heads)); totalText.setText(String.format("%d", pair.total)); devText.setText(String.format("%.10g", ((double) pair.heads)/((double) pair.total) - 0.5));} 6. Cancel the background task
Call SwingWorker.cancel to cancel an executing background task. The task must be consistent with its own undo mechanism. There are two ways to do this:
(1) When an interrupt is received, it will be terminated.
(2) Call SwingWorker.isCanceled. If SwingWorker calls cancel, the method will return true.
7. Bind attributes and state methods
SwingWorker supports bound properties, which is very useful when communicating with other threads. Provides two binding properties: progress and state. progress and state can be used to trigger event processing tasks in event dispatch threads.
By implementing a property change listener, the program can catch changes in progress, state or other binding properties.
7.1The progress Bound Variable
The Progress binding variable is an integer variable with a range of 0 to 100. It predefined the setter (the protected SwingWorker.setProgress) and getter (the public SwingWorker.getProgress) methods.
7.2The state Bound Variable
Changes in State binding variables reflect the process of changing the SwingWorker object during its life cycle. This variable contains an enumeration type of SwingWorker.StateValue. Possible values are:
(1) PENDING
This state lasts for a time to know from the creation of the object that the doInBackground method is called.
(2) STARTED
This state lasts for a moment before the doInBackground method is called until the done method is called.
(3) DONE
The remaining time the object exists will remain in this state.
If you need to return the value of the current state, you can call SwingWorker.getState.
7.3Status Methods
Two methods provided by the Future interface can also report the status of background tasks. If the task is cancelled, isCancelled returns true. Furthermore, if the task is completed, i.e. it is either completed normally or cancelled, isDone returns true.
Using top-level container
Swing provides 3 top-level container classes: JFrame, JDialog, JApplet. When using these three classes, you must pay attention to the following points:
(1). In order to be displayed on the screen, each GUI component must be part of the containing hierarchy. The inclusion hierarchy is a tree structure of the component, and the top-level container is its root.
(2). Each GUI component can only be included once. If a component is already in a container and then attempts to add it to a new container, the component will be removed from the first container and added to the second container.
(3). Each top-level container has a content pane. Generally, this content panel will contain (directly or indirectly) all visual components of the top-level container GUI.
(4). You can add a menu bar to the top container. Usually this menu bar is placed in the top container, but outside the content panel.
1. Top-level containers and inclusion hierarchies
Each program that uses the Swing component has at least one top-level container. This top-level container is the root node containing the hierarchy—this hierarchy will contain all Swing components that will appear in this top-level container.
Typically, a separate Swing GUI-based application has at least one inclusion hierarchy and its root node is a JFrame. For example, if an application has one window and two dialog boxes, the application will have three inclusion levels, that is, there will be three top-level containers. A containment hierarchy takes JFrame as its root node, and two other containment hierarchys each have a JDialog as its root node.
An applet based on Swing components contains at least one inclusion hierarchy, and it can be determined that one of them must be made with a JApplet as its root node. For example, a applet with a dialog box, it will have two inclusion levels. The component in the browser window will be placed in a containment hierarchy, and its root node is a JApplet object. The dialog box will have a containing hierarchy, and its root node is a JDialog object.
2. Add components to the content panel
The following code operation is to get the frame's content panel in the above example and add a yellow label:
frame.getContentPane().add(yellowLabel, BorderLayout.CENTER);
As shown in the code, you must first find the content panel of the top-level container and implement it through the method getContentPane. The default content panel is a simple intermediate container, which inherits from JComponent, using a BorderLayout as its panel manager.
Customizing a content panel is simple - set up a panel manager or add borders. It must be noted here that the getContentPane method will return a Container object, not a JComponent object. This means that if you need to take advantage of some of the JComponent's functionality, you must also type convert the return value or create your own component as the content panel. Our example usually uses the second method. Because the second method is clearer and clearer. Another way we sometimes use is to simply add a self-defined component to the content panel to completely cover the content panel.
If you create your own content panel, then be careful to make sure it is opaque. An opaque JPanel will be a good choice. Note that by default, the layout of JPanel is managed as FlowLayout, and you may want to replace it with another layout manager.
In order for a component to be a content panel, you need to use the setContentPane method of the top-level container, for example:
//Create a panel and add components to it.JPanel contentPane = new JPanel(new BorderLayout());contentPane.setBorder(someBorder);contentPane.add(someComponent, BorderLayout.CENTER);contentPane.add(anotherComponent, BorderLayout.PAGE_END);//Make it the content pane.//contentPane.setOpaque(true);topLevelContainer.setContentPane(contentPane);
Note: Do not use transparent containers as content panels, such as JScrollPane, JSplitPane, and JTabbedPane. A transparent content panel will cause components to be confused. Although you can make any transparent Swing component opaque through the setOpaque(true) method, it will not look right when some components are set to be completely opaque. For example, a tag panel.
3. Adding a Menu Bar
In theory, each top-level container can have a menu bar. But the facts show that the menu bar only appears in Frame or Applet. To add a menu bar to the top-level container, you need to create a JMenuBar object, assemble some menus, and then call the setJMenuBar method. The TopLevelDemo instance adds a menu bar to its Frame through the following code.
frame.setJMenuBar(cyanMenuBar);
4. The Root Pane
Each top-level container relies on an implicit intermediate container called the root container. This root container manages the content panel and menu bar, along with two or more other containers (see Layered Pane, etc.). You don't usually need to know about using the Swing component root container. However, if you want to intercept a mouse click or draw on multiple components, you need to know the root container.
The above has been described about the content panel and the optional menu bar, and will not be repeated here. The other two components contained in the root container are the layout panel and the glass panel. The layout panel directly contains the menu panel and the content panel, and allows you to sort the other components added by Z coordinates. Glass panels are usually used to intercept input actions that occur in the top layer and can also be used to draw on multiple components.