|
|
|
Contents: |
|
|
|
Related content: |
|
|
|
Subscriptions: |
|
|
| Use JFC/Swing to build accessibility into your Java
applications
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, JPanel s 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,
JPanel s) 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
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
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
() 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
AccessibleRelation s. 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 ImageIcon s.
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 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.
|
|
|