IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
Coding for accessibility
code251KBe-mail it!
Contents:
Rethink your GUI
A user interface for the blind
Accessible keyboard navigation
Accessibility demonstration
The Accessibility Toolkit
Support for relationships
Support for mnemonics
Rendering complex components
Verifying your GUI
Conclusion
Resources
About the author
Rate this article
Related content:
Java 2 gets a new focus subsystem
IBM Accessibility Center
IBM Java accessibility guidelines
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Use JFC/Swing to build accessibility into your Java applications

Level: Intermediate

Barry A. Feigenbaum, PhD (mailto:feigenba@us.ibm.com?cc=&subject=Coding for accessibility)
Senior Consulting IT Specialist, IBM
15 October 2002

All Java applications should be accessible to users who have disabilities. Special care is required to achieve this with GUI applications. This article shows you how to achieve the maximum level of accessibility with a minimum level of effort, using a JFC/Swing-based accessibility toolkit.

Most GUI-based software designs are based on the assumption that the user can see the screen well and can effectively use a mouse to select items from a graphical user interface (GUI). For many disabled persons, especially those who have impaired vision or motor control, this assumption is problematic. In 1998, the United States Rehabilitation Act was amended (see the sidebar on section 508) to ensure that electronic and information technology products for government use be accessible to people with disabilities. As a result, many IT shops have begun to adopt accessibility guidelines as part of their overall GUI design standard. Since the passage of the U.S. Rehabilitation Act Amendments, accessibility has become an increasingly important issue in commercial software design as well, resulting in some changes and additions to the Java platform.

This article will help bring you up to speed on some of the federal accessibility requirements, and also assist you in using JFC/Swing to build GUIs that meet those requirements. I've developed two Swing-based toolkits to assist in the implementation of accessibility functions; the toolkits are as follows:

  • com.ibm.wac.AccessibilityUtils: A set of general-purpose utilities reusable for any Swing GUI

  • com.ibm.wac.demos.AccessibilityDemo1: A demonstration application that includes a set of more application-specific utilities, which can be reused for similar structures within a particular GUI

Although many of the methods you'll see in AccessibilityDemo1 were created for a single application, they could easily be generalized to support multiple GUIs. In fact, all of the code used in this article (see Resources) is open source and you are encouraged to modify the toolkits for your own purposes.

Because JFC/Swing is the foundation of all GUI development discussed in this article, it is assumed that you are familiar with the basic concepts of programming with Swing. It is further assumed that you are working with Java version 1.3.1 or higher, since some of the methods we'll discuss here aren't available in earlier versions of the Java platform.






AWT support for accessibility

All GUI construction in this article is based on JFC/Swing. The AWT currently provides limited support for accessibility functions. Although AWT components support the interface, they do not fully implement the AccessibleContext and other Accessible classes. As a result, many assistive technologies cannot effectively process AWT GUIs.

Rethink your GUI
Because most GUIs are oriented toward sighted people, they're often of limited or no use to people who have reduced vision or are blind. Likewise, most GUI designs rely on a mouse for navigation, which can be a barrier for people with motor and visual disabilities. In this article, we'll look at ways to add accessibility functions to a simple GUI, concentrating on functions for people who have sight and motor disabilities.

The "IBM Guidelines for Writing Accessible Applications Using 100% Pure Java" (see Resources) describe application design and coding guidelines to make Java applications accessible to people with disabilities. Of those guidelines, we'll concentrate on the following:

  • Providing keyboard equivalents for all actions
  • Setting mnemonics on components
  • Using accelerators for commonly used functions
  • Providing a logical keyboard tabbing order
  • Providing a logical component layout (for multimedia access)
  • Labeling components
  • Naming logical groups
  • Providing semantic relationships
  • Describing icons and graphics

See Resources to view the complete Guidelines document.

Section 508

Section 508 of the U.S. Rehabilitation Act requires that federal agencies' electronic and information technology be accessible to people with disabilities. The standards provide criteria specific to various types of technologies, including:
  • Software applications and operating systems
  • Web-based information or applications
  • Telecommunication products
  • Video and multimedia products
  • Self contained, closed products
  • Desktop and portable computers

For a product to be successful in a bid for a U.S. government contract, all its components must be accessible. If two or more products are competing for the bid and one isn't fully accessible, the accessible product will win the bid. If both products are accessible, then the most accessible one will win. To successfully compete for U.S. government contracts, Java applications must meet the accessibility standards set by section 508. See Resources for complete documentation of the section 508 accessibility standards and for the European Union's efforts pertaining to accessibility.

A user interface for the blind
Blind people must be able to access your application functions by means that do not depend on visual feedback. The most common assistive technologies for the blind are text-to-speech screen readers, refreshable Braille display systems, or related I/O devices. To make your Java applications accessible, you will need to describe each application component in a standard code format that can be translated by an assistive technology (AT) device. For example, in the case of GUI components such as buttons, you would name and describe their functions, which would then be delivered to the user as messages such as "Send," "Enter," or "Quit."

Some GUI application components require more thought than others to convey. How would a blind person access the information visually formatted in a table, for example, or an icon, a tree, or a scrolling list? Making these types of components accessible requires you to provide considerable descriptive information in text form. While this task can be tedious, it's an essential step toward creating accessibility-enabled applications.

Adding descriptive information
The first step to making your application accessible to the visually impaired is to provide descriptions of the components that will receive focus. A component receives focus when a user or AT reader selects it, usually via keyboard controls. Components that receive focus are essential to the application's function, not its design or layout. So, for example, JPanels that contain other components do not themselves receive focus, although the individual components within them probably will. On the other hand, if a panel groups information, it is sometimes necessary to make that grouping accessible. Similarly, labels can be associated with other components by using the setLabelFor(Component) method.

In Swing, we use the javax.accessibility.Accessible interface to provide descriptive information about application components. All Swing components implement the Accessible interface, which has just one method: javax.accessibility.AccessibleContext getAccessibleContext(). Using the getAccessibleContext() method, an AT reader can access all the information it needs to both present the component's description to the user and interact with and use the component.

AccessibleContext() defines two methods that can be used to provide accessible information:

  • setAccessibleName(String name) sets the name to associate with a given Accessible object. Typically, an assistive reader will present this name whenever a component receives focus.

  • setAccessibleDescription(String description) sets the description to associate with a given Accessible object. Typically, an assistive reader will present this description when the user asks for more details about a component.

The standard Swing components often come with default values for AccessibleName and AccessibleDescription. For example the text of a JLabel or JTextField will be used as its default accessible name. Likewise, any component's ToolTip will be used as its default accessible description. It is my experience that the defaults don't provide the best name or description for a given component, however, so I recommend that you explicitly set your component values.

To set the values for a text field, you would enter something like the code shown in Listing 1:

Listing 1. Set the values for a text field


import javax.swing.*;
     :
JTextField streetField = new JTextField("<enter street>", 20);
streetField.setName("streetField");
streetField.getAccessibleContext().
       setAccessibleName("Street Entry Field");
streetField.getAccessibleContext().
       setAccessibleDescription("Enter a street address");
streetField.setToolTip("Street Address");
     :
-- set any other desired characteristics --

Similarly, to set the values for a button, you might enter the code shown in Listing 2:

Listing 2. Set the values for a button


import javax.swing.*;
     :
JButton okButton = new JButton("OK");
okButton.setName("okButton");
okButton.getAccessibleContext().setAccessibleName("OK Button");
okButton.getAccessibleContext().setAccessibleDescription(
       "Activate to commit changes and continue");
okButton.setToolTip("Commit changes");
okButton.setMnemonic((int)'O');
     :
-- set any other desired characteristics --

Accessible keyboard navigation
Normally Swing allows keyboard navigation with the Tab, back-Tab, and arrow keys. Unfortunately, this system can be difficult and time consuming, because it requires the user to navigate through all the intermediate components just to get to the one he needs. For more efficient keyboard navigation, the user should be able to quickly switch between important components, without regard for their order in the GUI layout. We can use a mnemonic keyboard setup to enable this for subclasses of javax.swing.AbstractButton and javax.swing.JLabel, as well as items in the application menus. Mnemonics are often called accelerators, because they work directly from the GUI content.

After you've established a mnemonic system for your interface, the user can navigate to any desired component by using the Alt key and the mnemonic key that represents that component on the keyboard (Alt+Key). One problem with this setup, however, is that it is global to the top-level component (typically a JFrame or a JDialog). This means there are basically only 26 unique values, which must be spread across all menus and menu items and the basic GUI content. On a busy GUI, not all components can be linked to a mnemonic key, so you'll have to determine which components are most essential for the user and set them accordingly. I recommend you establish mnemonic links for menu items, significant action buttons such as OK or Cancel, and the initial component in each logical group in your GUI, and then let the user tab to each additional component.

Setting the Tab order and initial focus
For the most logical Tab-based navigation, I recommend that you add components to containers in the order you want the Tabs to select them. You will likely want to organize nested containers (that is, JPanels) in the same way. Although a top-to-bottom, left-to-right (T2B, L2R) order is standard, you may want to establish a different system, such as one based on column arrangement. You can use the method JComponent.setNextFocusableComponent(Component c) (or, in Java 1.4, the class java.awt.FocusTraversalPolicy) to force a custom Tab order. The AccessibilityDemo1 GUI illustrates a Tab system based on adding components to containers in T2B, L2R order.

After you've defined your Tab order, you'll need to ensure each initial component will receive focus when its container is selected. When a container receives focus (see Resources for more information on FocusListener), it should issue a java.awt.Component.requestFocus() (or java.awt.Component.requestFocusInWindow() on Java 1.4) to the desired initial component.

Another approach would be to set the initial focus upon window activation. For example, the following code adds a WindowListener to the JFrame that requests focus for the JTextField when the window is activated:

Listing 3. Set the initial focus upon activation


import java.awt.event.*;
import javax.swing.*;
     :
JFrame frame = new JFrame();
JTextField field = new JTextField();  /// field to get initial focus
boolean focusSet;
     :
frame.addWindowListener(new WindowAdapter() {
      public void windowActivate() {
         if ( !focusSet ) {
             field.requestFocus();
             focusSet = true;
         }
      }
}

If you want to set the initial focus to a button rather than a JTextField, you can set the DefaultButton field, as follows:

Listing 4. Set the DefaultButton field


import java.awt.event.*;
import javax.swing.*;
     :
JFrame frame = new JFrame();
JButton button = new JButton();
     :
panel.add(button);
     :
frame.getRootPane().setDefaultButton(button);

You only need to set the initial focus once, because Swing will always restore focus once it has been initially set.

Accessibility demonstration
The accessibility demonstration GUI shown in Figure 1 is not intended to do any real work (that is, it has no functional code behind it); rather, its purpose is to demonstrate the majority of Swing GUI components and show you how accessibility information could be added to each of them. Figure 1 shows one panel of the demonstration application, containing several common component types such as entry fields, radio buttons, check boxes, and buttons.

Figure 1. First screen shot of AccessibilityDemo1
AcessibilityDemo1 screen shot 1

Note that in this GUI all the buttons use mnemonics (the underlined letter) and, thus, it overuses mnemonics. As I previously noted, mnemonics are best used only for selective components to assist in gross navigation between component groups. Also, although it is possible to select the tabs through standard keyboard support, doing so can require many keystrokes. To improve the usability of tab panes you can add keys that directly select tabs, by registering a java.awt.event.KeyListener on the javax.swing.JTabbedPane or its container.

Figure 2 is another panel of the demonstration application, containing more complex component types such as split panes, trees, and an editor pane showing HTML content. Figure 2 also shows that component ToolTips are defined.

Figure 2. Second screen shot of AccessibilityDemo1
AcessibilityDemo1 screen shot 2

It's difficult to see the results of adding accessibility information without access to an AT reader. I'll show you how to display this information later in the article. For now, Figure 3 shows the results of adding accessibility information to an application. It is the part of the output that comes from the presence of the New Document toolbar button (New icon image) in Figure 1.

Figure 3. Subset output from the HTML dump of AccessibilityDemo1
Component Tree (subset)
Level Index Object
6 0 *javax.swing.JButton:16E7BF1D-button5
in javax.swing.JToolBar:17FAFF1D-toolBar0

Name Value
Component Fields
name button5
text
toolTipText Create a new document
value -- not available --
mnemonic 78  =  'N'

Name Value
AccessibleContext Fields
name New
role push button
stateSet enabled,focusable,visible,showing,opaque
indexInParent 0
description Create a new document
action javax.swing.JButton$AccessibleJButton@1bd8bf1d
value javax.swing.JButton$AccessibleJButton@1bd8bf1d
text null
editableText -- not available --
table null
icon [Ljavax.accessibility.AccessibleIcon;@2ae9ff1e
relationSet -- empty AccessibleRelationSet --
childrenCount 0

The colors of the text indicate the status of the item. Blue text indicates no concern. Yellow text indicates an item that may potentially cause accessibility problems. Red text with italics (not shown in the example) indicates an item likely to cause accessibility problems.

The use of colors and other formatting enhancements such as the use of italics should not be the only indication of special text. These enhancement are often not indicated by AT devices, and thus may not be noticed. Although not shown in this example, it is recommended that other indicators be used in addition to or instead of color. For example, you may surround the text with parentheses or asterisks.

The Accessibility Toolkit
Setting the values for every component in a complex GUI can get quite tedious, which often leads to errors or the complete omission of important steps. To remedy this, I've created an accessibility toolkit, a set of utility methods that can significantly reduce the volume of "set" methods needed to provide accessible information in your GUIs.

The following utility methods are public static members of the com.ibm.wac.AccessibleUtils class:

  • setAccessibleValues(ResourceBundle rb, Accessible a, AccessibleValues av) sets the most common accessible component values.

  • Accessible setMemberRelationship(Accessible group, Collection members) creates a membership relationship between a group component and a set of accessible objects defined by a collection.

  • Accessible setMemberRelationship(Accessible group, Accessible[] members) creates a membership relationship between a group component and a set of accessible objects defined by an array.

  • Accessible setLabelRelationship(Accessible label, Accessible target) creates relationships between an accessible target and a label. setLabelRelationship is typically used to provide accessible information for components that do not naturally have appropriate accessible information. It also allows components that do not support keyboard mnemonics (for example, JTextField) to be accessed by keyboard mnemonics.

In the sections that follow, we'll look closely at the toolkit's setAccessibleValues() method, seeing how it aids in the creation and definition of a number of GUI components. In the sections detailing support for relationships and mnemonics, you'll also get a picture of how the other utility methods (and their helper methods) work.

Using setAccessibleValues
The setAccessibleValues() method has three arguments. The ResourceBundle argument (from java.util) allows automatic support for internationalization; it will be null if no text translation is required. The Accessible argument is updated by the setAccessibleValues() method. The AccessibleValues argument (from com.ibm.wac) provides the most common accessible attributes. You are free to add more frequently used component attributes to this set.

Listing 5 shows a condensed version of the AccessibleValues class:

Listing 5. Condensed version of AccessibleValues


public static class AccessibleValues {
       public String name;              // component's name/id
       public String shortDescription;  // == accessible name
       public String longDescription;   // == accessible description
       public String toolTip;           // component's tool tip
       public String text;              // component's text
       public String borderText;        // component border's text
       public int    mnemonic;          // component's mnemonic

       public AccessibleValues(String name,
                               String text,
                               String shortDescription<,
                               String longDescription<,
                               String toolTip<,
                               int mnemonic<,
                               String borderText>>>>) {...}
}

Not all components require all the values in this class, so it provides multiple constructors that implement optional arguments. When using the setAccessibleValues method, it is best to set the text of the component, if any, using this method and not the component's normal methods. Listing 6 illustrates how the setAccessibleValues method would be used to set the values for a button component:

Listing 6. setAccessibleValues for a button component


JButton b = new JButton();
AccessibleUtils.setAccessibleValues(null, (Accessible)b,
      new AccessibleUtils.AccessibleValues(
          "button1",
          "OK",
          "OK Button",
          "Activate to commit changes and continue",
          "Commit changes",
          (int)'O');

While the code in Listing 6 does the same thing as the button sequence in Listing 2, it offers the following advantages:

  • The setAccessibleValues() syntax forces an error if required arguments are omitted.

  • The setAccessibleValues() method is more concise than the button sequence in Listing 2. (The method can even take fewer lines, if all the arguments are placed on one line).

  • Because a method is called, that method can do extra processing and validation.

  • Internationalization can be automatically supported, by translating the supplied text through a java.util.ResourceBundle.

A utility method at work
Listing 7 shows how the setAccessibleValues() method works. First have a look at the code, then see the notes that follow.

Listing 7. setAccessibleValues() at work


protected static final Class[] _sType = {String.class};
protected static final Class[] _iType = {Integer.TYPE};
     :
Accessible setAccessibleValues(
         ResourceBundle rb, Accessible a, AccessibleValues av) {
       if ( av.name != null ) {
           throw new NullPointerException(
               "accessible components require a name");
       }
       if ( a instanceof Component ) {     // nearly always true
           ((Component)a).setName(av.name);
       }
       if ( av.text != null ) {
           Method m = resolveMethod(a, "setText", _sType);
           try {
             invokeMethod(a, m, new String[] {resolveString(rb, av.text)});
           }
           catch ( Exception e ) {
               throw new AccessibleException(
                   "cannot invoke method setText(String text) - " + a, e);
           }
       }
       if ( av.borderText != null ) {
           JComponent c = (JComponent)a;
           Border b = c.getBorder();
           Border tb = new TitledBorder(resolveString(rb, av.borderText));
           c.setBorder(b != null ? new CompoundBorder(b, tb) : tb);
       }
       if ( av.toolTip != null ) {
         String text = resolveString(rb, av.toolTip.equalsIgnoreCase("=ld")
               ? av.longDescription : av.toolTip);
         if ( a instanceof JComponent ) {
               ((JComponent)a).setToolTipText(text);
         }
         else if ( a instanceof ImageIcon ) {
               ((ImageIcon)a).setDescription(text);
         }
       }
       if ( av.mnemonic >= 0 ) {
           Method m = resolveMethod(a, "setMnemonic", _iType);
           if ( m == null ) {
               m = resolveMethod(a, "setDisplayedMnemonic", _iType);
           }
           try {
               invokeMethod(a, m, new Integer[] {new Integer(av.mnemonic)});
           }
           catch ( Exception e ) {
               throw new AccessibleException(
                 "cannot invoke method set{Displayed}Mnemonic(int key) - " 
                 + a, e);
           }
       }
       if ( av.shortDescription == null ) {
           throw new NullPointerException(
               "accessible components require a shortDescription");
       }
       if ( av.shortDescription.equalsIgnoreCase("=tt") ) {
           av.shortDescription = av.toolTip;
       }
       if ( av.shortDescription.equalsIgnoreCase("=ld") ) {
           av.shortDescription = av.longDescription;
       }
       if ( av.shortDescription.length() == 0 ) {
           av.shortDescription = null;
       }
       if ( av.shortDescription != null ) {
           if ( a instanceof ImageIcon ) {
               ((ImageIcon)a).setDescription(
                   resolveString(rb, av.shortDescription));
           }
       }
       AccessibleContext ac = a.getAccessibleContext();
       if ( ac == null ) {
           throw new NullPointerException(
               "AccessibleContext cannot be null on an Accessible object " 
               + formatClassToken(a));
       }
       if ( av.shortDescription != null ) {
           ac.setAccessibleName(resolveString(rb, av.shortDescription));
       }
       if ( av.longDescription != null ) {
           ac.setAccessibleDescription(
               resolveString(rb, av.longDescription.equalsIgnoreCase("=tt")
                   ? av.toolTip : av.longDescription));
       }
       return a;
}

Notes about the code:

  • In Swing (and AWT) each component can be optionally identified by an ID string; however, the setAccessibleValues() method requires a name. You'll better understand the utility of names further on in the article.

  • If a text parameter is provided, then the component's text is set. This allows the text to be omitted on the component's constructor and for internationalization translations to occur. In order to not require that a component be a certain type, reflection (instead of a downcasting) is used to find and invoke the setText() method.

  • If a border-text parameter is provided, then a titled border is created and set for the component. Titled borders provide information about groups of components. (We'll discuss component groups further on in the article.)

  • If a ToolTip-text parameter is provided, then either the component's ToolTip or the icon's description is set.

  • If a mnemonic parameter is provided, then the component's mnemonic is set. In order to not require that a component be a certain type, reflection is used to find and invoke the available method.

  • If a short-description parameter is provided, then the component's short description is set. The short description is another term for the AccessibleName. It can be set to default to either the ToolTip (by entering =tt) or the long description (by entering =ld). Short descriptions are required (that is, they cannot be null). A blank short description is allowed but is not recommended.

  • If a long-description parameter is provided, the long description is set. The long description is another term for AccessibleDescription. You can set the long description default to the ToolTip by entering "=tt".

A little help from your friends
A more realistic setAccessibleValues() usage example is shown in Listing 8, where resourceBundle is an instance field. Note the use of helper methods.

Listing 8. setAccessibleValues usage example


JButton setupButton(String name, String action, int vKey) {
       return (JButton)AccessibleUtils.setAccessibleValues(resourceBundle,
           (Accessible)new JButton(),
           new AccessibleUtils.AccessibleValues(
               idGen.nextId("button"),
               name,
               AccessibleUtils.formatText(resourceBundle,
                   "{0} button", name),
               "=tt",
               AccessibleUtils.formatText(resourceBundle,
                   "Press to {0}", action),
               vKey));
}

The setupButton method further simplifies and standardizes the creation of buttons by wrapping the setAccessibleValues() call. Notice the use of "=tt" to set the accessible description to the ToolTip. IdGenerator.nextId(String base) is a utility method that generates unique names. idGen is an instance of IdGenerator.

The various public static AccessibilityUtils.formatText() methods format a string by inserting values. The formatText method uses the java.text.MessageFormat class. formatText has the following forms:


String formatText(String pattern, String args, String delims);
String formatText(ResourceBundle rb, String pattern, Object[] args);
String formatText(ResourceBundle rb, String pattern, String args);
String formatText(ResourceBundle rb, String pattern, String args,
       String delims);

Similar helper methods are used for most types of components. See the AccessibilityDemo1 file in the source code download for more examples.

Support for relationships
Groups of components in a GUI are often intricately related. By making these relationships apparent to an AT reader, you enable it to enhance its presentation of the group, thereby conveying more complex information to the user. In Swing, we use the AccessibleContext() method's AccessibleRelationSet getAccessibleRelationSet() method to define relationships.

An AccessibleRelationSet contains a set of AccessibleRelations. Each AccessibleRelation describes the relationship between two Accessible objects: a source and a target. Currently, these relationships are defined as follows:

  • CONTROLLED_BY identifies a given target as the controller for a given component.

  • CONTROLLER_FOR indicates a given component controls a given target.

  • LABELLED_BY indicates a given component is labelled by a given target.

  • LABEL_FOR indicates a given component is the label for a given target.

  • MEMBER_OF indicates a given component is a member of a given target group.

The Accessibility Toolkit in AccessibilityDemo1 offers several utility methods that can assist you in defining accessible relationships. A very common relationship is the "only-one-selected" relationship between radio buttons. In Swing, we use javax.swing.ButtonGroup to implement this relationship. Listing 9 shows the Accessibility Toolkit's utility methods for defining radio buttons in button groups. You should note that there are methods to define individual radio buttons and methods to define sets (or groups) of radio buttons.

Listing 9. Methods for defining buttons in groups


JRadioButton setupRadioButton(
         String name, String action, int vKey, boolean selected) {
       JRadioButton b = new JRadioButton();
       b.setSelected(selected);
       return (JRadioButton)AccessibleUtils.setAccessibleValues(
           resourceBundle, (Accessible)b,
           new AccessibleUtils.AccessibleValues(
             idGen.nextId("radioButton"),
             name,
             AccessibleUtils.formatText(resourceBundle, "{0} Button", name),
             "=tt",
             AccessibleUtils.formatText(resourceBundle,
                 "Press to {0}", action), vKey));
}

JPanel setupRadioButtonSet(String title, JRadioButton[] bs) {
       return setupRadioButtonSet(title, Arrays.asList(bs));
}
JPanel setupRadioButtonSet(String title, Collection bs) {
     JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT, 20, 10));
     AccessibleUtils.setAccessibleValues(resourceBundle, (Accessible)p,
         new AccessibleUtils.AccessibleValues(
             idGen.nextId("panel"),
             null,
             "languages",
             "=tt",
             "Select the desired language"));
     AccessibleUtils.setMemberRelationship(p, bs);
     p.setBorder(new TitledBorder(title));
     addAll(p, bs);
     ButtonGroup bg = new ButtonGroup();
     addAll(bg, bs);
     return p;
}

void addAll(ButtonGroup g, Collection l) {
       for ( Iterator i = l.iterator(); i.hasNext(); ) {
           g.add((AbstractButton)i.next());
       }
}

You might use the above helper methods to define the set of language-selection radio buttons shown in Figure 1, as demonstrated by Listing 10:

Listing 10. Define language-selection radio buttons (from Figure 1)


protected JComponent createTextFieldDemoUI() {
      :
       ArrayList buttons = new ArrayList();
       StringTokenizer st2 = new StringTokenizer(
           "English!French!Spanish!German!Italian!Japanese!Chinese!" +
           "Korean!Arabic!Hebrew!Russian", "!");
       for ( int i = 0; st2.hasMoreTokens(); i++ ) {
           String name = nextToken(st2);
           buttons.add(name, setupRadioButton(name,
                AccessibleUtils.formatText(rb, "select {0}", name),
                (int)name.charAt(0), i == 0));
       }
       JPanel formBox2 = setupRadioButtonSet("Languages", buttons);
      :
}

Radio buttons are set up to be accessible in the same way that buttons are. Unlike buttons, however, radio buttons are frequently added to a ButtonGroup and also to an AccessibleRelationSet. The AccessibileUtils() methods to assist in this task are detailed in Listing 11:

Listing 11. AccessibileUtils methods


Accessible setMemberRelationship(Accessible group, Accessible member) {
       return setMemberRelationship(group, new Accessible[] {member});
}
Accessible setMemberRelationship(Accessible group, Accessible[] members) {
       return setMemberRelationship(group, Arrays.asList(members));
}
Accessible setMemberRelationship(Accessible group, Collection members) {
       for ( Iterator i = members.iterator(); i.hasNext(); ) {
           Accessible a = (Accessible)i.next();
           AccessibleContext ac = a.getAccessibleContext();
           if ( ac == null ) {
               throw new NullPointerException(
                 "AccessibleContext cannot be null on an Accessible object" 
                 + formatClassToken(a));
           }
           AccessibleRelationSet ars = ac.getAccessibleRelationSet();
           AccessibleRelation ar =
               new AccessibleRelation(AccessibleRelation.MEMBER_OF, group);
           ars.add(ar);
       }
       return group;
}

See the code source to learn more about setMemberRelationship.

Support for mnemonics
Some Swing components do not support mnemonics directly. As I previously discussed, Swing compensates for this by allowing a JLabel to "label" the component. The Accessibility Toolkit provides helper methods to assist in labeling components. Listing 12 shows the helper methods at work on a JTextField component:

Listing 12. Helper methods for labeling components


JTextField setupLabelledField(
         JPanel lp, JPanel fp, String name, int vKey) {
       JLabel l = new JLabel("", JLabel.RIGHT);
       AccessibleUtils.setAccessibleValues(resourceBundle, (Accessible)l,
           new AccessibleUtils.AccessibleValues(
               idGen.nextId("label"),
               name,
               name + " label",
               "=tt",
               AccessibleUtils.formatText(resourceBundle,
                   "Identifies the {0} field", name),
               vKey));
       lp.add(l);
       JTextField tf = new JTextField("", 40);
       AccessibleUtils.setAccessibleValues(resourceBundle, (Accessible)tf,
           new AccessibleUtils.AccessibleValues(
               idGen.nextId("textField"),
               null,
               name + " entry field",
               "=tt",
               AccessibleUtils.formatText(resourceBundle,
                   "Enter the value for {0}", name)));
       fp.add(tf);
       AccessibleUtils.setLabelRelationship(l, tf);
       return tf;
}

Although not shown, note that the name + "label" and name + "entry field" clauses should also be processed with AccessibleUtils.formatText to fully enable internationalization translation.

Now check out the method details, in Listing 13:

Listing 13. Details of setLabelRelationship helper methods


Accessible setLabelRelationship(Accessible label, Accessible target) {
       if ( label instanceof JLabel ) {
           ((JLabel)label).setLabelFor((Component)target);
/* *** done by setLabelFor ***
           AccessibleContext ac1 = label.getAccessibleContext();
           if ( ac1 == null ) {
               throw new NullPointerException(
                 "AccessibleContext cannot be null on an Accessible object" 
                 + formatClassToken(label));
           }
           AccessibleRelationSet ars1 = ac1.getAccessibleRelationSet();
           AccessibleRelation    ar1  = new AccessibleRelation(
               AccessibleRelation.LABEL_FOR, target);
           ars1.add(ar1);
*/
           AccessibleContext ac2 = target.getAccessibleContext();
           if ( ac2 == null ) {
               throw new NullPointerException(
                 "AccessibleContext cannot be null on an Accessible object" 
                 + formatClassToken(target));
           }
           AccessibleRelationSet ars2 = ac2.getAccessibleRelationSet();
           AccessibleRelation    ar2  = new AccessibleRelation(
               AccessibleRelation.LABELLED_BY, label);
           ars2.add(ar2);
       }
       return label;
}

See the code source to learn more about setLabelRelationship.

Rendering complex components
In Swing, certain complex components (such as JTree, JTable, JList, and JComboBox) do not directly render their content. Instead, they delegate that task to a component created by a renderer. A renderer is a factory object for creating a component to display the row/cell values of the complex component. The component is used only for the brief moment that the row/cell is being drawn. By providing a customized component, you can control how the row/cell is presented to the user, including providing accessible information for the AT reader to use.

Components generated by the renderer need to be as accessible to the user as the simpler components we've discussed so far, which means we have to be able to set their accessibility values. In Swing, we usually do this by creating a xxxCellRenderer subclass, where xxx is the base component type. Listing 14 shows the cell renderer for a JList. Note the toolkit's helper classes at work.

Listing 14. Cell renderer for a JList


class DemoListCellRenderer implements ListCellRenderer {
       protected ListCellRenderer _lcr = new DefaultListCellRenderer();
       public Component getListCellRendererComponent(
             JList list,
             Object value,
             int index,
             boolean isSelected,
             boolean cellHasFocus) {
           String name = value.toString();
           String shortDesc =
             AccessibleUtils.formatText(resourceBundle, "months {0}", name);
           String longDesc =
             AccessibleUtils.formatText(resourceBundle, "Selects month {0}",
                 (String)_monthsMap.get(name.substring(0,3)));
           JComponent c = (JComponent)_lcr.getListCellRendererComponent(
               list,
               name,
               index,
               isSelected,
               cellHasFocus);
           return (Component)AccessibleUtils.setAccessibleValues(
               resourceBundle, (Accessible)c,
               new AccessibleUtils.AccessibleValues(
                   idGen.nextId("label"),
                   name,
                   shortDesc, longDesc, "=ld"));
       }
}

As you can see, from an accessibility standpoint, working with renderer-provided components is very similar to working with normal components. While this discussion shows only how to add accessibility support to renderers, very similar considerations are needed for editors used on editable rows or cells. See the code source to learn more about the toolkit's support for rendering complex components.

Verifying your GUI
For most of this article we've talked about making Swing applications accessible, but how do you verify the accessibility of your GUI? Testing every component on a complex GUI is time consuming, and would require you to have an AT reader on hand. Sun Microsystems offers tools to help you test your GUI without an AT reader (see Resources), but the tools require extensive human interaction to be effective.

To remedy this, AccessibilityUtils offers a reporting framework that points out potential or actual missing accessibility information. The public static AccessibilityUtils.output() methods use an com.ibm.wac.Outputter implementation to generate reports, as shown here:

void output(Component c, PrintWriter pw, Outputter out);
void output(Component c, OutputStream os, Outputter out);
void output(Component c, String fileName, Outputter out) throws
IOException;

The output method is implemented as shown in Listing 15:

Listing 15. output() method implementation


public static void output(Component c, PrintWriter pw, Outputter out) {
      if ( out.isEnabled() ) {
          out.begin(pw);
          try {
              outputWorker(0, new HashSet(), c, pw, out);
          }
          finally {
              out.end(pw);
          }
      }
}
protected static void outputWorker(
        int level, Set seen, Component c, PrintWriter pw, Outputter out) {
      out.beginLevel(level, pw);
      try {
          if ( seen.add(c) ) {              // only do the first time seen
              // output self
              out.beginObject(level, c, pw);
              try {
                  out.identifyObject(level, c, pw);
                  out.recommendEol(level, pw);
                  out.outputComponent(level, c, pw);
                  if ( c instanceof Accessible ) {
                      out.recommendEol(level, pw);
                      out.outputAccessible(level, (Accessible)c, pw);
                  }
                  out.recommendEol(level, pw);

                  // output children (if any)
                  if ( c instanceof Container ) {
                      Component[] components =
                          ((Container)c).getComponents();
                      if ( components.length == 0 ) {
                          out.emptyGroup(level, pw);
                      }
                      else {
                          out.beginGroup(level, pw);
                          for ( int i = 0;
                                i <
                                components.length; i++ ) {
                              out.separateGroupMembers(level, i, pw);
                              out.identifyGroupMember(level, i, pw);
                              Component xc = components[i];
                              if ( xc instanceof JComponent ) {
                                  outputWorker(level + 1, seen,
                                               (JComponent)xc, pw, out);
                              }
                              else {
                                  out.outputObject(level, xc, pw);
                              }
                          }
                          out.endGroup(level, pw);
                          out.recommendEol(level, pw);
                      }
                  }
              }
              finally {
                  out.endObject(level, c, pw);
              }
          }
          else {
              out.outputObject(level, c, pw);
              out.recommendEol(level, pw);
          }
      }
      finally {
          out.endLevel(level, pw);
      }
}

Notes about the code:

  • The output method only reports the specified component (often a JFrame, JDialog, or JPanel). If your application is composed of multiple frames or dialogs, you will need to call the output() method for each of them to get a complete picture of the application.

  • Any component can be reported but it should be a JComponent. Typically, the top-most component (such as a JFrame) is used, but lower-level or dynamic components (such as a pop-up JDialog) can also be reported.

An outputter defines the following methods:

Listing 16. Methods defined by an outputter


boolean isEnabled();

// signal report begin/end
void begin(PrintWriter pw);
void end(PrintWriter pw);

// signal level begin/end
void beginLevel(int level, PrintWriter pw);
void endLevel(int level, PrintWriter pw);

// signal object begin/end
void beginObject(int level, Object c, PrintWriter pw);
void endObject(int level, Object c, PrintWriter pw);

// report object identity
void identifyObject(int level, Object c, PrintWriter pw);

// report object
void outputObject(int level, Object c, PrintWriter pw);
void outputComponent(int level, Component c, PrintWriter pw);
void outputAccessible(int level, Accessible a, PrintWriter pw);

// optional indent report
String indent(int level);
String indent(int level, String pad);

// optionally end report line
void recommendEol(int level, PrintWriter pw);

// signal group (i.e, container) processing
void beginGroup(int level, PrintWriter pw);
void separateGroupMembers(int level, int index, PrintWriter pw);
void identifyGroupMember(int level, int index, PrintWriter pw);
void endGroup(int level, PrintWriter pw);
void emptyGroup(int level, PrintWriter pw);

Similar to how a SAX XML parser works, these methods are called by the AccessibilityUtils.output() methods when the event represented by the method name occurs. Included in this example of AccessibleUtils are the following outputters:

TextOutputter
Generates a simple text format report. A subset example is:


*javax.swing.JButton:16E7BF1D-button5

Component(id=button5,
            text=,
            toolTipText=Create a new document,
            value=?,
            mnemonic=78,
            ...)    ** others omitted for brevity **
Accessible(name=New,
             role=push button,
             description=Create a new document,
             action=javax.swing.JButton$AccessibleJButton@1bd8bf1d,
             value=javax.swing.JButton$AccessibleJButton@1bd8bf1d,
             text=null,
             table=null,
             relationSet=,
             ...)    ** others omitted for brevity **

This text (without all the wrapping) was generated by the code:


AccessibleUtils.output(frame, "demo.txt", new TextOutputter());
HtmlOutputter
Generates an HTML report for presentation in a browser. A subset example is shown in Figure 3. This HTML was generated by the code:


AccessibleUtils.output(frame, "demo.html", new
  HtmlOutputter(HtmlOutputter.defaultHeader("Accessibility Demo 1")));
XmlOutputter
Generates an XML report that allows further processing, such as by an XSLT stylesheet. A subset example is:


<container level="6"
                desc="javax.swing.JButton:1588FF1D-button6"
                pdesc="javax.swing.JToolBar:17FAFF1D-toolBar0">
    <fields>
      <field context="self" type="java.lang.String">
        <name>name</name>
        <value status="ok">button6</value>
      </field>
      <field context="self" type="java.lang.String">
        <name>text</name>
        <value status="ok"></value>
      </field>
      <field context="self" type="java.lang.String">
        <name>toolTipText</name>
        <value status="ok">Open an existing document</value>
      </field>
      <field context="self" type="java.lang.String">
        <name>value</name>
        <value status="warning">??</value>
      </field>
      <field context="self" type="java.lang.Integer">
        <name>mnemonic</name>
        <value status="ok">79</value>
      </field>
      <field context="accessible" type="java.lang.String">
        <name>name</name>
        <value status="ok">Open</value>
      </field>
      <field context="accessible"
          type="javax.accessibility.AccessibleRole">
        <name>role</name>
        <value status="ok">push button</value>
      </field>
      <field context="accessible" type="java.lang.String">
        <name>description</name>
        <value status="ok">Open an existing document</value>
      </field>
      <field context="accessible"
          type="javax.swing.JButton$AccessibleJButton">
        <name>action</name>
        <value status="ok">
          javax.swing.JButton$AccessibleJButton@1a05bf1d
        </value>
      </field>
      <field context="accessible"
          type="javax.swing.JButton$AccessibleJButton">
        <name>value</name>
        <value status="ok">
          javax.swing.JButton$AccessibleJButton@1a05bf1d
        </value>
      </field>
      <field context="accessible">
        <name>text</name>
        <value status="warning">null</value>
      </field>
      <field context="accessible">
        <name>table</name>
        <value status="warning">null</value>
      </field>
      <field context="accessible"
          type="javax.accessibility.AccessibleRelationSet">
        <name>relationSet</name>
        <value status="ok"></value>
      </field>
    </fields>
    <!-- empty container -->
</container>

This XML output (without the indentation) was generated by the code:


AccessibleUtils.output(frame, "demo.xml", new
  XmlOutputter(XmlOutputter.defaultHeader("Accessibility Demo 1")));

The DTD for the XML is:



<!-- DTD for gui xml -->
<!ELEMENT gui (component|container)>
<!ATTLIST gui   xmlns  CDATA #IMPLIED>

<!ELEMENT component (fields?)>
<!ATTLIST component desc   CDATA #REQUIRED
                      pdesc  CDATA #IMPLIED
                      level  CDATA #IMPLIED
>

<!ATTLIST container desc   CDATA #REQUIRED
                      pdesc  CDATA #IMPLIED
                      level  CDATA #IMPLIED
>
<!ELEMENT container (fields?,children?)>

<!ELEMENT children (component|container)*>

<!ELEMENT fields (field)*>

<!ELEMENT field (name,value)>
<!ATTLIST field context (self|accessible) "self"
                  type CDATA #IMPLIED
>

<!ELEMENT name (#PCDATA)>

<!ELEMENT value (#PCDATA)>
<!ATTLIST value status (ok|warning|error) "ok">

Notice the status attribute in the DTD. It contains information about the quality of the field's value. The following status values are defined:

  • ok: The value is sufficient.
  • warning: The value is missing or suspect and may cause an AT to not properly process the component.
  • error: The value is missing or in error and will likely cause an AT to not properly process the component.

The outputters shown in this article are simple examples. Typically, one would build display outputters (such as the one in Figure 3) that format more of the information and possibly expand the output of complex types, such as ImageIcons.

An alternative to the report framework would be to add validation code to the AccessibleUtils.output() methods. Instead of reporting missing information with special report formatting, this code would throw an exception if required accessible information were omitted on any components (although, realistically, this technique would work best on non-container components).

Adding validation code could assist you in catching accessibility errors more quickly, without having to examine an entire report. Furthermore, with validation code in place, report generation would serve as a validation test case, making it far less likely that a GUI implementation would ship without all the required accessible information set. For an example of this type of exception code, see the setAccessibleValues() method in Listing 7. Note that setAccessibleValues requires each component to have a name.

Conclusion
In this article you've learned how to add accessibility values to the Swing components that make up a GUI. In the process, you've developed some familiarity with the accessibility standards established by the section 508 amendments to the U.S. Rehabilitations Act of 1998. You've also been introduced to an example set of utility methods that remove much of the repetitiveness involved in setting the required accessibility values.

The utilities in the Accessibility Toolkit handle the following aspects of the accessible GUI development process:

  • Enforcing required values
  • Validating values
  • Hiding differences in the way GUI components set accessible values
  • Offering ease-of-coding help, such as default values and internationalization support options
  • Helping to ensure a consistent format (such as wording style) for accessible information

The Accessibility Toolkit also comes with an extendable reporting framework, which assists you in verifying the accessible components of your GUI. You've learned how to use the framework to generate reports on the accessible status of a component hierarchy. By building custom report generators (or XML report processors) you can construct different kinds of reports to verify the components in your application.

By careful use of the techniques you've learned in this article you can begin to build more accessible applications for people who have visual and motor disabilities.

Resources

About the author
author Dr. Barry Feigenbaum is a member of the IBM Worldwide Accessibility Center, where he serves as a member of a team that helps IBM make its own products accessible to people with disabilities. Dr. Feigenbaum has published several books and articles, holds several patents, and has spoken at industry conferences such as JavaOne. He serves as an Adjunct Assistant Professor of Computer Science at the University of Texas, Austin. You can contact Dr. Feigenbaum at feigenba@us.ibm.com.


code251KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact