Context-sensitive help is an incredibly useful feature, especially on the day when a software product comes with a 500-page manual. While you should strive towards making your program completely intuitive, requiring no special training or
documentation, that isn't always possible. Context-sensitive help can lessen the need for other documentation and is much more timely and relevant than an online user's manual.
In case you are unclear about what "context-sensitive help" is, it is simply help on what you are currently doing. For example, if you are entering text in a field and you suddenly press the help key, you should expect to get help for the text field you are entering. Many software products require that you pop up the online manual and skip to the page discussing the field you want information on. If you know what the user is currently doing, you know what to tell them when they ask for help.
The way you present context-sensitive help is up to you. One very useful method, which is also quite passive, is to display a one-line message at the bottom of the screen giving a quick description of the area where the mouse is. For example, when you are looking at a Web page and pass the mouse over a link, your browser may display the destination URL for that link at the bottom of the page. This is a form of context-sensitive help. You can also define a particular key to be the "help" key. Tell the user to press that key any time he needs help. The F1 key in many software packages is the help key; you should seriously consider making it the help key in your applets, too. Remember that you want to give all your applets and
applications a similar feel in the same way that most cars have a similar feel. It doesn't take long to figure out how to drive a car you've never seen before. It should be that way with software.
Listing 7.2 shows a HelpDialog class that is useful for popping up screens containing help text. The help dialog is a simple OK dialog box-it displays a text message and a button labeled "OK" which, when clicked, makes the dialog box
Chapter 7 -- Creating Smarter Forms
disappear. One thing to keep in mind when you want to create dialog boxes is that you must have a parent frame for the dialog box. When you are running an applet, you can't normally access the applet's parent frame. The HelpDialog class addresses this problem by creating its own frame. It saves the frame in a static variable so it doesn't have to create a new frame the next time it needs to create a dialog window.
You can actually access the parent frame for an applet. Sometimes it will work exactly like you want. It usually works for dialogs, but it fails miserably on some platforms when you try to create a menu for the parent frame. You can use the
getParent method from the component class to trace back up through the component hierarchy to find the applet's
parent frame. The following code fragment finds an applet's parent frame:
Component parentFrame = getParent(); while ((parentFrame != null) &&
!(parentFrame instanceof Frame)) { parentFrame = parentFrame.getParent(); }
Frame myFrame = (Frame) parentFrame;
At this point, myFrame would either contain the parent frame of the applet, or null if it couldn't find the parent frame.
Listing 7.2 Source Code for HelpDialog.java
import java.awt.*;
// The HelpDialog class is a variation on the OKDialog class. // It allows you to create an OK dialog with a textarea instead // of a label. You can use this to display help text.
public class HelpDialog extends Dialog {
protected Button okButton;
protected static Frame createdFrame;
public HelpDialog(Frame parent, String message) {
super(parent, false); // Must call the parent's constructor
// Create the OK button and the message to display okButton = new Button("OK");
TextArea helpInfo = new TextArea(message, 10, 40); helpInfo.setEditable(false); setLayout(new BorderLayout()); add("Center", helpInfo); add("South", okButton); resize(500, 300); }
Chapter 7 -- Creating Smarter Forms
// The action method just waits for the OK button to be clicked;
// when it is, it hides the dialog, causing the show() method to return // back to whoever activated this dialog.
public boolean action(Event evt, Object whichAction) { if (evt.target == okButton) { hide(); if (createdFrame != null) { createdFrame.remove(this); createdFrame.hide(); dispose(); return true; } } return true; }
// Shortcut to create a frame automatically, the frame is a static variable // so all dialogs in an applet or application can use the same frame.
public static void createHelpDialog(String helpText) {
// If the frame hasn't been created yet, create it if (createdFrame == null)
{
createdFrame = new Frame("Help"); }
// Create the dialog now
HelpDialog helpDialog = new HelpDialog(createdFrame, helpText); // Shrink the frame to nothing
createdFrame.resize(0, 0); // Show the dialog
createdFrame.show(); helpDialog.show(); }
}
In addition to the HelpDialog class, you need a way to assign help information directly to your AWT components. It would have been nice if Sun had built that right into the AWT, and maybe they will in the future, but for now you have to do it yourself. You could subclass all the AWT components to support help if you really had nothing better to do for a month or two, but there are easier ways. One simple way is just to store the components and their corresponding help text in a hash table. Whenever someone requests help from within an AWT component, look in the table and see if you have defined any help for that component. Listing 7.3 shows the HelpSystem class that enables you to assign help text to
Chapter 7 -- Creating Smarter Forms
AWT components. It also contains a method to display the help for a component, but it makes no assumptions on how you actually request the help in the first place.
Listing 7.3 Source Code for HelpSystem.java
import java.awt.*; import java.util.*;
// Help system is a container for help strings. You can add // and remove help strings for components. It also provides // a doHelp method that actually pops up the help dialog. public class HelpSystem extends Object
{
Hashtable helpTable; public HelpSystem() {
helpTable = new Hashtable(); }
public void addHelp(Component comp, String text) {
helpTable.put(comp, text); }
public void removeHelp(Component comp) {
helpTable.remove(comp); }
public boolean doHelp(Component comp) {
if (comp == null) return false;
String helpString = (String) helpTable.get(comp); if (helpString == null) { return false; } HelpDialog.createHelpDialog(helpString); return true; } }
Chapter 7 -- Creating Smarter Forms
key to your applet. Going back to the Lycos search form applet, you can modify it to use F1 as the help key. The AWT components are polite enough to ignore keyDown events for keys they do not recognize, and they all leave the F1 key alone. You can trap the F1 key in your applet and display the appropriate help text. To add context-sensitive help to the
LycosForm class, you need to create an instance of the help system. Since there are several methods that actually use the
help system, you declare it as an instance variable:
protected HelpSystem helpSystem = new HelpSystem();
Next, for each component that will have a help screen, you add the component to the help system. For example, once you create the queryString text field, you can add a help string for it with the following code fragment:
helpSystem.addHelp(queryString,
"QUERY HELP\nEnter the words you want to search\n"+ "for separated by spaces. Avoid common words like\n "+ "\"the\" or \"and\".");
The trickiest part of implementing the help system is grabbing the F1 key and figuring out which component the user wants help on. When you receive keyboard events, you are given an x and y coordinate where the keystroke occurred.
Unfortunately, this does not really indicate where the mouse was when you pressed the key. The x and y coordinates are bounded by the component that currently has the keyboard focus. For context-sensitive help, you don't want the user to have to move the keyboard focus to another component before requesting help. If this were the case, they would have to click a button before they could get help for that button. What you must do, instead, is track the movement of the mouse all the time. You can do this very simply by creating two instance variables in your class, mouseX and mouseY:
protected int mouseX; // the current X coord of the mouse protected int mouseY; // the current Y coord of the mouse
Next, you override the mouseMove method. This method is called whenever the mouse moves. You simply copy the x and y coordinates of the mouse and return:
public boolean mouseMove(Event evt, int x, int y) {
mouseX = x; mouseY = y; return false; }
Notice that you return false from the mouseMove method. This indicates that you haven't actually handled the mouse movement event, allowing the event to be passed to another component. If you do not want another component to see the mouse movement event, you should return true instead.
The hardest part of implementing this context-sensitive help system is determining which component the user wants help on. The problem here is that you have to take the x and y coordinates of the mouse and locate the component at those coordinates.
The locate method does this, sort of. The locate method takes an x and y coordinate and returns the component at
those coordinates. It only looks one level deep in the component hierarchy, however. If you are using nested panels, as the LycosForm applet does, the locate method will only return the panel enclosing the component you really want.
Chapter 7 -- Creating Smarter Forms
The solution for this problem is simple. If the locate method returns a container, you use the locate method in that container. You keep repeating the process until locate returns a component that is not a container.
There is one additional little sticking point here. The locate method expects the x and y coordinates to be relative to the container you are searching. The first time you call locate, everything is fine, since the mouse x and y coordinates are relative to your applet. After that, you have to adjust them to be relative to the container returned by locate. For example, suppose you had mouse coordinates of 100, 50 and the locate method returned an instance of the Panel class for those coordinates. Suppose that the panel's upper-left corner was at 65, 45. You would subtract the panel's coordinates from the original mouse coordinates, giving a new location of 35,5. Now you call the locate method in the panel with the new coordinates. You can use the location method to get the coordinates of the upper-left corner of any component. Listing 7.4 shows a keyDown method for the LycosForm applet that uses this technique to identify the component where the F1 key was pressed.
Listing 7.4 Source Code for the keyDown Method in LycosForm3.java
public boolean keyDown(Event evt, int ch) {
if (ch == Event.F1) { int x = mouseX; int y = mouseY;
// Find out which component this x,y is inside
Component whichComp = locate(x, y);
// If the component is a container, descend into the container and // find out which of its components contains this x,y
while (whichComp instanceof Container) {
// If you have to search within a container, adjust the x,y to be relative // to the container.
x -= whichComp.location().x; y -= whichComp.location().y;
Component nextComp = whichComp.locate(x, y); // if locate returns the component itself, you're done
if (nextComp == whichComp) break; whichComp = nextComp;
}
// Display any available help on the component helpSystem.doHelp(whichComp); }
return false; }
Chapter 7 -- Creating Smarter Forms
Figure 7.3 : Context-sensitive help screens make your applets easier to use.