Return-Path: Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id OAA29763; Tue, 21 May 2002 14:26:43 -0400 (EDT) Received: from hermes.sun.com (hermes.sun.com [64.124.140.169]) by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id OAA13356 for ; Tue, 21 May 2002 14:26:42 -0400 (EDT) Date: Tue, 21 May 2002 10:26:42 GMT-08:00 From: "JDC Tech Tips" To: alexp@mit.edu Message-Id: <15547504-345853897@hermes.sun.com> Subject: JDC Tech Tips, May 21, 2002 (Swing Timers, Adding Help) Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: SunMail 1.0 J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, May 21, 2002. This issue covers: * Using Swing Timers * Adding help to Your Applications With JavaHelp(tm) Software 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. (http://www.jzventures.com). You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2002/tt0521.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING SWING TIMERS The May 30, 2000 JDC Tech Tip "Using Timers to Run Recurring or Future Tasks on a Background Thread" (http://java.sun.com/jdc/TechTips/2000/tt0530.html#tip2) 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 Java(tm) 2 Standard Edition (J2SE(tm)) 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 straighforward 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" (http://java.sun.com/docs/books/tutorial/uiswing/misc/timer.html) 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" (http://java.sun.com/jdc/JDCTechTips/2002/tt0423.html#tip2), 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 (http://java.sun.com/products/javahelp/download_binary.html). 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, Privacy, and Licensing 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, (http://developer.java.sun.com/subscription/), choose the newsletters you want to subscribe to and click "Update". - To unsubscribe, go to the subscriptions page, (http://developer.java.sun.com/subscription/), uncheck the appropriate checkbox, and click "Update". - To use our one-click unsubscribe facility, see the link at the end of this email: - 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. To receive future newsletters in HTML, select the following URL: http://bulkmail.sun.com/servlet/PreferenceServlet?action=change&pref_name=content-type&pref_value=html&id=15547504-345853897 To use our one-click unsubscribe facility, select the following URL: http://bulkmail.sun.com/unsubscribe?15547504-345853897