|
Tech Tips Archive
May 21, 2002
WELCOME to the Java Developer Connection (JDC) Tech Tips, May 21, 2002. This issue covers:
These tips were developed using Java 2 SDK, Standard Edition, v 1.4.
This issue of the JDC Tech Tips is written by John Zukowski,
president of JZ Ventures, Inc.
USING SWING TIMERS
The May 30, 2000 JDC Tech Tip "Using Timers to Run Recurring or
Future Tasks on a Background Thread"
discussed using the java.util.Timer and java.util.TimerTask
classes to run recurring tasks. In this tip, you'll look at the
Swing class of the same name: javax.swing.Timer.
You'll learn how it differs from the java.util version. You'll
also learn when to use each version, and how to use the
Swing version of the class.
The javax.swing.Timer class is the original Timer class for the
core JavaTM 2 Standard Edition (J2SETM) libraries.
Originally provided as part of J2SE version 1.2, the
javax.swing.Timer class lets you define a task to perform at
a predefined rate. The Timer class follows an event handling
metaphor. The class is associated with an ActionListener and
a delay. At a time designated by the delay, and at repeated
intervals, the listener is initiated and executes on the
standard AWT/Swing event dispatch thread.
The java.util.Timer class was introduced in J2SE release 1.3. You
use the class to schedule tasks for execution in a background
thread. In order to do this, you define a TimerTask to
act as a Runnable object executing within the thread of the
java.util.Timer class.
Before comparing the two versions of the Timer class, it's
important to understand that the Swing component set was
purposely designed to not be thread-safe. Because events are
dispatched from a single thread, the event dispatching thread,
this typically is not an issue. Occasionally there is a need to
do processing on another thread, or wait a certain amount of
time before updating the user interface. As such, a mechanism
was needed to schedule processing to occur on the event
dispatching thread from any other thread.
There are actually three different ways available with the Swing
libraries to execute tasks on the event dispatch thread. Two
involve the EventQueue class (duplicated in the SwingUtilities class), the third involves javax.swing.Timer.
With the EventQueue class, you create a Runnable object that
defines the task you want to execute. You then call the
invokeLater or invokeAndWait method of EventQueue to execute the
task. With invokeLater, the currently executing event on the
event queue is completed before the Runnable object executes
(possibly with other events handled in between). The
invokeAndWait method is meant to be called from a thread other
than the event dispatching thread. You call it when you want
to block the calling thread until (1) all pending events on the
event queue are dispatched, and (2) the event dispatching thread
executes the Runnable object.
Here's a simple example of using invokeLater to update the label
on a JButton. Selecting the button causes the label to change
after a half second delay. You can't block the event dispatch
thread for that half second, so you must kick off another thread
to wait. Because the waiting thread is not the event dispatch
thread, a second Runnable object needs to be created, and that
second Runnable object needs to be passed to the initial
Runnable object for execution.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ButtonChange {
public static void main(String args[]) {
JFrame frame = new JFrame("InvokeLater");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
Container content = frame.getContentPane();
final JButton button = new JButton("0");
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
final Runnable task = new Runnable() {
public void run() {
String label = button.getText();
int count = Integer.parseInt(label);
button.setText(""+(count+1));
}
};
Runnable runner = new Runnable() {
public void run() {
try {
// Sleep a half sec
Thread.sleep(500);
EventQueue.invokeLater(task);
} catch (InterruptedException ie) {
// ignore
return;
}
}
};
// Start delay
new Thread(runner).start();
}
};
button.addActionListener(listener);
content.add(button, BorderLayout.CENTER);
frame.setSize(200, 100);
frame.show();
}
}
|
Having to create an intermediate runner thread that simply sleeps
before doing anything underscores why you want to use the
javax.swing.Timer class instead of the EventQueue: it's more straightforward to use.
Here is the constructor for a javax.swing.Timer:
public Timer(int delay, ActionListener listener)
The ActionListener defines the task to perform. The delay
comprises two delay properties, initialized to one common setting.
The Timer class supports both an initial delay and a repeating
delay. The initial delay specifies how long to wait before
executing the task for the first time. The repeating delay
specifies how long to wait between subsequent runs of the task.
That's right, the Swing Timer automatically repeats. This is
similar to specifying an interval when scheduling the
java.util.Timer.
If you don't want the Timer to repeat the task, simply set the
repeats property to false:
Timer t = new Timer(...);
t.setRepeats(false);
Here's an update to the ButtonChange program that uses the
javax.swing.Timer class. The only change here (besides the window
title) is in the ActionListener for the JButton. The rest of the
code is identical to the previous version of the ButtonChange
program.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ButtonChange2 {
public static void main(String args[]) {
JFrame frame = new JFrame("Timer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container content = frame.getContentPane();
final JButton button = new JButton("0");
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
ActionListener task = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
String label = button.getText();
int count = Integer.parseInt(label);
button.setText(""+(count+1));
}
};
Timer timer = new Timer(500, task);
timer.setRepeats(false);
timer.start();
}
};
button.addActionListener(listener);
content.add(button, BorderLayout.CENTER);
frame.setSize(200, 100);
frame.show();
}
}
|
If you need to perform a long task in an event handler for
a component, you'll still need to kick off a thread to perform
the task. Then, after the task is completed, use the
invokeLater method to update the component state information.
If you want to see a message when the Timer goes off to notify
the action listeners, you can call setLogTimers(true). This
displays a message to System.out. And, yes, the "action
listeners" wording is correct. You can add more than one listener
to the timer through the addActionListener method (or remove them
with removeActionListener). You can also stop, restart, or start
the Timer.
One thing to consider is the potential for class name conflicts.
These conflicts can occur if you use wild card imports and import
both the java.util and javax.swing package. To avoid possible name conflicts, you need to explicitly use the fully-qualified
class name when you make references to a class in one of these
packages, or you need to explicitly tell the compiler which of
the two Timer classes you want to use. The following shows how to
import both packages with wildcards, and tell the compiler to use
the Swing Timer class when it sees "Timer" alone as a class name.
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
For more information about Swing timers, see the section "How to
Use Swing Timers" in the Java Tutorial.
ADDING HELP TO YOUR APPLICATIONS WITH JAVAHELP SOFTWARE
In the April 23, 2002 Tech Tip "Creating a HelpSet with JavaHelp Software", you learned how to create a HelpSet, the set of files you need to use JavaHelp Software. In this tip, you'll learn how to integrate the JavaHelp system into your applications.
To start, you'll need to get the developer version of the
JavaHelp software from the JavaHelp download page. You'll also need to have the HelpSet files from the April 23 tip. If you don't have the HelpSet files available, you can download
a ZIP file that contains them. The ZIP file is available at
http://www.jzventures.com/javahelp.zip.
Essentially, the way you connect an application to the HelpSet is
by mapping the target names to the components in an application.
Another way of saying this is that you map the HelpIDs of the Map
file in the HelpSet file to the components in an application.
It's simply a question of how to do the mapping and how do you
bring up the help viewer. There is actually very little code
necessary besides creating a GUI.
Here's the program to start with. It provides two menu items and
two buttons. The second button (the one on the bottom) will be
used to get context sensitive help for the top button.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class HelloHelp {
public static void main(String args[]) {
JFrame frame = new JFrame("Hello, JavaHelp");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
Container content = frame.getContentPane();
JMenuBar menubar = new JMenuBar();
JMenu helpMenu = new JMenu("Help");
JMenuItem overview = new JMenuItem("Overview");
JMenuItem specific = new JMenuItem("Specific");
helpMenu.add(overview);
helpMenu.add(specific);
menubar.add(helpMenu);
frame.setJMenuBar(menubar);
JButton button1 = new JButton("The Button");
JButton button2 = new JButton("Context");
content.add(button1, BorderLayout.NORTH);
content.add(button2, BorderLayout.SOUTH);
frame.setSize(200, 200);
frame.show();
}
}
|
You'll need the jh.jar file from the JavaHelp download to be in
your class path or in the jre/lib/ext (UNIX) or jre\lib\ext
(Windows) subdirectory of your J2SE SDK or J2SE jre installation
directory. The jh.jar file contains the necessary class files for
the JavaHelp API. They are found in the javax.help package and
subpackages.
The JavaHelp API isn't small, however much of the JavaHelp work
is done with a core set of three classes: HelpSet, HelpBroker, and CSH. The HelpSet class describes the collection of help files (in this case, the help files that were created in the April 23 tip). The HelpBroker "brokers" (that is, negotiates) the help presentation between an application and the help components. The CSH class (short for context-sensitive help) provides a set of
convenience methods and inner classes for triggering the help
viewer.
So, the first thing to do is import the javax.help classes:
import javax.help.*;
Next, you need to find the HelpSet file. You do this by
specifying a URL. Alternatively, you can use the findHelpSet
method of HelpSet to locate the file (and get the URL from that).
The findHelpSet method uses a ClassLoader to find the HelpSet file. In most cases, this means that the directory of the helpset file or its JAR file needs to be in the class path or otherwise
loadable through the ClassLoader (that is, URLClassLoader).
After you have the URL, you call the HelpSet constructor:
import java.net.*;
...
HelpSet helpset = null;
ClassLoader loader = null;
URL url = HelpSet.findHelpSet(loader, "hello.hs");
try {
helpset = new HelpSet(loader, url);
} catch (HelpSetException e) {
System.err.println("Error loading");
return;
}
|
You then need to get the HelpBroker from the HelpSet. This will later be used for mapping components to context-sensitive help.
HelpBroker helpbroker = helpset.createHelpBroker();
The final piece is connecting components to help content. For
starters, just connect the "Overview" help menu to the default
top-level help from the HelpSet. The CSH class has an inner class, DisplayHelpFromSource, that, given a HelpBroker, displays the appropriate help text in the Help Viewer.
ActionListener listener =
new CSH.DisplayHelpFromSource(helpbroker);
overview.addActionListener(listener);
At this point, you can compile your program and select the Overview
option from the menu to get the Help Viewer to display the default
(top-level) help text. Remember to include in your class path
the directory where the HelpSet files are located.
While displaying the default help screen is useful, providing
context-sensitive help is even better. To display
context-sensitive help, you need to map the component to the
target names from the HelpSet. Again, the CSH class comes in handy. Here, the setHelpIDString method is used to map
a component to the target name from the HelpSet's Map.jhm file. The following code maps the second menu item, named specific, to a specific target name:
CSH.setHelpIDString(specific, "one");
specific.addActionListener(listener);
Notice that the same ActionListener is used here. The mapping is
done directly in the CSH class, not with a specific
ActionListener. To display more help, simply add more mappings to
the CSH, and associate the same ActionListener to the control.
Alternatively, you can use the following code:
helpbroker.enableHelpOnButton(specific, "one", helpset);
As shown here, the HelpBroker class has convenience methods that
permit associating help with a Button, AbstractButton, or MenuItem. You don't have to explicitly fetch the ActionListener and associate it with the component.
In the case of the JButton in the frame, you do want to display
help, but you don't want the button to display the help window
when selected. Instead, you can select a component that
"enables" field level help. In this case, the next component
a user clicks on brings up the Help Viewer, which displays the
help associated with that component. This involves a different
inner class of CSH called DisplayHelpAfterTracking. However, you
still need to associate the proper target name to the component,
both of which are shown here.
CSH.setHelpIDString(button1, "two");
ActionListener tracker =
new CSH.DisplayHelpAfterTracking(helpbroker);
button2.addActionListener(tracker);
At this point, you can compile your program again here and run
it. If you click on the bottom button, the cursor changes to a
question mark with an arrow. If you then click on the top button,
the Help Viewer appears. It displays the text for the target name
that is registered with the CSH. If no help is associated with the
component, the HelpBroker looks in the Container of the component.
It continues looking until something is matched. If it can't find
a match, no help viewer is opened.
The final piece to show here is how to enable the default Help
Key (usually the F1 key) that provides "window level help."
To do this, assign a target name to the HelpBroker for the
JRootPane of a JFrame.
JRootPane rootpane = frame.getRootPane();
helpbroker.enableHelpKey(rootpane, "two", helpset);
If the window level help key is hit anywhere in the frame, the
appropriate target name is displayed in the Help Viewer. Window
level help cycles through the hierarchy of the component with
focus. If a component, or it's hierarchical parent, has a target
name associated with it, the help content associated with that
target name is displayed. The default is the target name
specified in the enableHelpKey method. Note that Java 2 SDK v 1.4
requires the component in enableHelpKey to be the JRootPane due to a change in the focus manager.
Putting all the pieces together gives the following program.
Remember you need to use the HelpSet from the April 23 tip.
If that HelpSet used different target names, you'll need to
adjust the references in the sample code.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.help.*;
import java.net.*;
public class HelloHelp {
public static void main(String args[]) {
JFrame frame = new JFrame("Hello, JavaHelp");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
Container content = frame.getContentPane();
JMenuBar menubar = new JMenuBar();
JMenu helpMenu = new JMenu("Help");
JMenuItem overview = new JMenuItem("Overview");
JMenuItem specific = new JMenuItem("Specific");
helpMenu.add(overview);
helpMenu.add(specific);
menubar.add(helpMenu);
frame.setJMenuBar(menubar);
JButton button1 = new JButton("The Button");
JButton button2 = new JButton("Context");
content.add(button1, BorderLayout.NORTH);
content.add(button2, BorderLayout.SOUTH);
HelpSet helpset = null;
ClassLoader loader = null;
URL url = HelpSet.findHelpSet(loader, "hello.hs");
try {
helpset = new HelpSet(loader, url);
} catch (HelpSetException e) {
System.err.println("Error loading");
return;
}
HelpBroker helpbroker = helpset.createHelpBroker();
ActionListener listener =
new CSH.DisplayHelpFromSource(helpbroker);
overview.addActionListener(listener);
CSH.setHelpIDString(specific, "one");
specific.addActionListener(listener);
CSH.setHelpIDString(button1, "two");
ActionListener tracker =
new CSH.DisplayHelpAfterTracking(helpbroker);
button2.addActionListener(tracker);
JRootPane rootpane = frame.getRootPane();
helpbroker.enableHelpKey(rootpane, "three", helpset);
frame.setSize(200, 200);
frame.show();
}
}
|
 |
IMPORTANT: Please read our Terms of Use and Privacy policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:
jdc-webmaster@sun.com
SUBSCRIBE/UNSUBSCRIBE
- To subscribe, go to the subscriptions page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
- ARCHIVES
You'll find the JDC Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
- COPYRIGHT
Copyright 2002 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
JDC Tech Tips
May 21, 2002
Sun, Sun Microsystems, Java, Java Developer Connection, J2SE,
and JavaHelp are trademarks or registered trademarks of
Sun Microsystems, Inc. in the United States and other countries.
|