Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Support

Empowering Your Carbon Application with the  VoiceOver Accessibility Interface

Each release of Mac OS X has brought increased support of "Universal Access" for users with disabilities. Universal Access is Apple's collective name for the Mac OS X technologies, features, and components that provide users with disabilities with access to Macintosh computers.

Before Tiger, Apple implemented several features, such as magnification and inverting the image on the screen, and the ability to move the mouse using the keyboard. In Tiger, Apple has added VoiceOver, a full-featured screen reader and control interface. VoiceOver speaks screen activities, and provides keyboard access to every aspect of an application's user interface. Apple's VoiceOver technology not only opens up new doors for users with disabilities. It also helps parents, teachers, and anyone else who interacts or collaborates with users with disabilities.

For Mac developers, making applications accessible to this audience is the smart thing to do, and it's easier now than ever before. This article will teach you how to fully implement support for accessibility through the VoiceOver interface in your Carbon application.

VoiceOver Technology

VoiceOver is a full-fledged accessibility interface, allowing users to control their computers using the keyboard and providing voice prompts. It is fully integrated into Mac OS X, and is available right from the very first installation screen, as well as from the login window. Because it is built in, VoiceOver also provides most applications with some functionality immediately. However, VoiceOver cannot do everything by itself; it uses information and features that applications provide in order to present the best interface to users.

When VoiceOver is activated, it becomes the medium of communication between the user and your application, taking place of the mouse and screen. You navigate with the keyboard, and you "hear" the user interface instead of "seeing" it.

You can find an overview of VoiceOver technology on the Mac OS X VoiceOver features page.

Modifying Your Carbon Application to Work with VoiceOver

Your application is partially VoiceOver-enabled automatically—the HIToolbox does that for you. However, partially enabled applications can sometimes give extraneous or misleading information to VoiceOver, which passes this information on to the user. Supporting VoiceOver is much easier if you've already adopted HIViews—otherwise you are setting yourself up for a lot of work. In this article, we assume that you have already adopted Carbon Events and HIViews.

Note: This article describes modifications necessary to use VoiceOver with Carbon applications. Cocoa developers should refer to the Cocoa Accessibility documentation for information on using Accessibility features in their applications. Or see the article, Enabling Accessibility in Your Cocoa Application.

The key data structure that VoiceOver uses is the "Accessibility Hierarchy" (AH). The Accessibility Hierarchy is a thin, toolbox-independent method of representing the user interface. Cocoa and Carbon each implement an AH, and VoiceOver communicates with your application through the AH. Each object in the AH corresponds to a visual item in the user interface, only some of which the user can interact with. For example, the user can interact with a button, but not with painted text, although both will probably have entries in the AH. The HIToolbox builds the hierarchy automatically, but you can augment it (or change it radically) at runtime for your application.

In Carbon, the Accessibility Hierarchy is composed of objects of type AXUIElementRef. They are Core Foundation objects, created using the routine AXUIElementCreateWithHIObjectAndIdentifier, and released with CFRelease. Each AXUIElementRef contains an HIObject reference (the HIObject that this AXUIElementRef refers to) and a 64 bit identifier which uniquely represents an accessible subcomponent of the interface object. The value 0 is reserved to mean "the whole HIObject" (i.e., the parent object). These objects are created by the HIObjects in response to various Carbon Events.

If you are using standard user interface controls, much of the work is already done for you. The standard controls respond to the accessibility Carbon Events, so, in general, they "do the right thing".

However, there is some information that the control cannot know; your application has to supply it. For example, if you are writing a browser, you will probably have a "Back" button in a toolbar. If the button shows the title "Back", then VoiceOver can read that to the user because VoiceOver will automatically return a kAXTitleAttribute with the value "Back". However, if, as is more likely, the button shows a picture of an arrow pointing to the left, VoiceOver has no way of knowing the function of the button. You can use the routine HIObjectSetAuxiliaryAccessibilityAttribute to add a description, such as "back", to the button. Note that you wouldn't add an AXTitle in this case since the button has no title text.

Note: The word "back" is not capitalized. It is recommended that descriptions not contain capital letters, except for acronyms or formal names.

Adding full support for VoiceOver typically involves four steps:

  1. Adding VoiceOver support to your custom HIViews.
  2. Customizing the AH in your application.
  3. Supporting accessibility for non-HIView user interface elements.
  4. Exploring the AH using Apple's tools, and with VoiceOver itself.

The rest of this article will examine these steps in more detail.

Adding VoiceOver Support to Your Custom HIViews

If you have custom HIView classes, you will need to add support for accessibility. Fortunately, this is fairly straightforward. Apple has created a sample HIView that implements accessibility called ImageMapView). The ImageMapView application reads an XML file and an image file, and mimics the behavior of the HTML imagemap.

There are several Carbon Events that your view needs to handle. In general, these events can be sent for the entire view, or for any "parts" of the view. You can find complete descriptions in the HIToolbox/CarbonEvents.h header file. The Carbon Events can be broken down into three groups: Child Navigation, Attribute Manipulation, and Actions.

Note: For detailed information on handling and returning information from Carbon Accessibility Events, see the Carbon Accessibility Reference.

Child Navigation

The Carbon Events in the child navigation group are:

Carbon Event Description
kEventAccessibleGetChildAtPoint Return the "child part" of the view at a particular point on the screen.
kEventAccessibleGetFocusedChild Return which child part of the view currently has the focus.

Attribute Manipulation

The attribute manipulation Carbon Events are:

Carbon Event Description
kEventAccessibleGetAllAttributeNames
  • Return an array of the accessibility attributes that the HIView supports.
  • Attribute names should not be localized.
  • A full list is available in the "Attributes" section of the Carbon Accessibility Reference.
kEventAccessibleGetAllParameterizedAttributeNames
  • Return an array of the parameterized attributes that the HIView supports
  • Used mainly for objects that contain user-entered text, a parameterized attribute can refer to something as specific as a particular line within an editable text area.
  • In addition, parameterized attributes can also allow VoiceOver to discover the properties of attributed static text. For example, distinguishing between bold and non-bold text, or telling VoiceOver what color a particular piece of attributed text is.
  • If your application handles customized text areas, you may need to support most, if not all, of the parameterized attributes.
  • A list can be found in the "Parameterized Attributes" section of the Carbon Accessibility Reference.
kEventAccessibleGetNamedAttribute Return the value for a single accessibility attribute.
kEventAccessibleSetNamedAttribute The counterpart to the previous event. Sets an accessibility attribute to a value.
kEventAccessibleIsNamedAttributeSettable Returns a value indicating whether or not the accessibility attribute is settable. If not, the attribute is read-only.

Actions

The Carbon Events in the actions group are:

Carbon Event Description
kEventAccessibleGetAllActionNames Return a list of "actions" supported by this HIView (or part of the view). The strings returned by this event are not required to be human-readable (though they can be). Like attribute names, action names should not be localized. However, the action descriptions do need to be both human-readable and localized.
kEventAccessiblePerformNamedAction Transmits the user's request from an assistive application (such as VoiceOver), to the application itself. This is how VoiceOver tells the application to perform an action.
kEventAccessibleGetNamedActionDescription Returns a human-readable, localizable description of an action. An assistive application might speak this string to the user.

Customizing the Application Hierarchy

Now that you have updated your custom HIViews to return accessibility information, it is time to work on your application.

Developers often display a static text control near another control (such as an edit text control) to alert the user to the purpose of the control. However, a user who is visually impaired may not be able to read the static text control, or may not be able to associate the text with the control. For example, in Mail's composition window, there is a text field with the word "To:" placed immediately to the left. Visually, the text field is adorned with a title, but the text field's title is provided by another element. In this situation, you can use HIObjectSetAuxiliaryAccessibilityAttribute to link a static text control (such as the "To:" label) to another control (such as the text field). By doing this you are telling VoiceOver, in effect, "this control serves as the title for that control".

Some user interface elements, such as panes that group controls (enabling them to move or resize together, for example) are not interactive, and have no physical presence on the screen. To the end user, these elements do not exist; they are merely a developer convenience. If you have controls like this, you should mark them as "ignored" in the AH. Carbon provides the HIObjectSetAccessibilityIgnored attribute for your use. This prevents VoiceOver from announcing them to the user, thus providing a simpler, less confusing user interface. If you tell the AH to ignore a control, then the control does not exist in the AH, and the children of the ignored control become children of the parent control (in the AH).

Note that a customized HIView is ignored by default. In this case you must explicitly set the HIObjectSetAccessibilityIgnored attribute to false to override this behavior.

The ImageMapView sample demonstrates several aspects of customization:

  • It sets the description for the image map, so that the user knows what the image map is representing:
    HIObjectSetAuxiliaryAccessibilityAttribute( ..., kAXDescriptionAttribute, ... );
  • It marks a static text item as the title of another one, and then tells the static text that it is acting as the title:
    HIObjectSetAuxiliaryAccessibilityAttribute ( ..., kAXTitleUIElementAttribute, ... );
    and
    HIObjectSetAuxiliaryAccessibilityAttribute ( ..., kAXServesAsTitleForUIElementsAttribute, ... );
  • It sets description of various sub-elements of the image map:
    HIObjectSetAuxiliaryAccessibilityAttribute( ..., kAXDescriptionAttribute, ... );
    This cannot be done in the view, because the view doesn't know what the application is trying to represent.
  • It marks a user pane to be ignored by VoiceOver, since the pane is only used to group other elements:
    HIObjectSetAccessibilityIgnored( ..., true );

For the ImageMapView application, that's all we need to do!

Supporting Accessibility for non-HIView User Interface Elements

Even if you are using Carbon Events and HIViews everywhere you can, there is still probably a portion of your user interface where you are not using HIViews. For example, a flowchart application will probably not use HIViews to display and manipulate individual elements in a flow chart. Similarly, a word processing program would not create an HIView for each paragraph, sentence, or word in a document. But that doesn't mean that users don't want to navigate or manipulate those parts of your application.

Apple has provided the AXCanvas example to help you in these types of situations. This example is available in the /Developer/Examples/Accessibility folder.

Exploring the Accessibility Hierarchy

Apple supplies a tool called "Accessibility Inspector", located in the /Developer/Applications/Utilities/Accessibility Tools folder. The Accessibility Inspector displays the AH of the user interface element the mouse is currently over. The display also includes the supported attributes and actions for that UI Element. You can either explore the hierarchy by moving the mouse, or press cmd-F7 to lock the display on a particular element. For example, if you hover the mouse over the green button ("+") in the upper left of any window, you will see a display similar to the following:

<AXApplication: "DragPeeker X">
	<AXWindow: "DragPeeker X">
		<AXButton>

Attributes:
	AXRole:  "AXButton"
	AXRoleDescription:  "zoom button"
	AXSubrole:  "AXZoomButton"
	AXParent:  "<AXWindow: "DragPeeker X">"
	AXWindow:  "<AXWindow: "DragPeeker X">"
	AXTopLevelUIElement:  "<AXWindow: "DragPeeker X">"
	AXPosition:  "x=50 y=26"
	AXSize:  "w=14 h=16"
	AXEnabled:  "true"

Actions:
	AXPress - press
				

The description tells us where the item lives in the AH, that it is a button control, and a description of what it does. In this case, the "+" button "zooms". If the user interface element has an AXTitle attribute, it will also be displayed. Note the "+" button does not have this attribute, because it does not have a text representation of a title. There is a single action that this button supports—it can be pressed. If you press Cmd-F7 to lock the display, a dialog appears that lets you explore further, as shown in Figure 1:

Accessibility Inspector Locked on Dialog

Figure 1: Accessibility Inspector Locked on Dialog

You can look at each of the attributes of the button; if any of them are settable you can set them (note there are no settable attributes for the "+" button). Attributes you can set are marked with "(W)" at the end of the name. You can also navigate the AH from this point, either moving up (to the window or application) or down (to the button's children). If the item supports any actions, you can perform them here—if you click on the Perform button, it will have exactly the same effect as clicking on the button. In this case, the window will zoom. You can navigate up or down the hierarchy using the goto popup menu. An example of this is shown below. Finally, if you click on the "Highlight" checkbox, the area of the screen that the item occupies will be colored a pale red.

Let's look at a more complicated example, from TextEdit. The screenshot in Figure 2 was taken while the mouse was over the Center button in TextEdit's tool bar:

TextEdit Application

Figure 2: TextEdit Application

Accessibility Inspector displays the following information for the Center button:

<AXApplication: "TextEdit">
	<AXWindow: "Untitled">
		<AXScrollArea>
			<AXRuler>
			 <AXGroup>
				<AXGroup>
					<AXRadioGroup>
						<AXRadioButton>

Attributes:
	AXValue:  "false"
	AXRoleDescription:  "radio button"
	AXParent:  "<AXRadioGroup>"
	AXWindow:  "<AXWindow: "Untitled">"
	AXHelp:  "Center"
	AXSize:  "w=25 h=25"
	AXRole:  "AXRadioButton"
	AXPosition:  "x=249 y=78"
	AXTitle:  "(null)"
	AXEnabled:  "true"
	AXFocused:  "false"
	AXDescription:  "align center"
	AXTopLevelUIElement:  "<AXWindow: "Untitled">"

Actions:
	AXPress - press
	           

As you can see, the segment control identifies itself as a radio button. That is because its behavior is exactly the same as a radio button group—only one item can be selected at once. Clicking on one element in the control causes the other elements to be deselected. Currently, it is not selected (AXValue: false), but it is enabled (AXEnabled: true) and its help text is "Center". The help text appears when the mouse cursor hovers over the element, and it can also be read to the user; it's a bit brief in this example, but much better than nothing. The AXTitle attribute is to be used when the element's title is visually represented with text, for example, an "OK" button. The AXDescription attribute is used when the element's title is not represented visually. For example, the "back" button in a web browser, which is represented visually by an arrow.

TextEdit has a more complicated AH, so there is more here to navigate. Press cmd-F7 to display the Locked on dialog. If you select the goto menu, you can select the parent AXRadioGroup (for example). From there, you can see that the radio group has four children, which is what we expect, given that the segmented control has four options. We can navigate all the way up to the TextEdit application, if we choose.

With the Accessibility Inspector, you can check out your Accessibility Hierarchy and compare it to the on-screen controls. This way, you can be sure that your controls have the correct accessibility information, attributes, and actions.

Apple also provides a tool called "Accessibility Verifier", located in the /Developer/Applications/Utilities/Accessibility Tools folder. The Accessibility Verifier can look at your AH and find common errors, such as missing roles or descriptions. It's quite handy for checking out your AH. However, it finds lots of different errors, including a few that are caused by bugs in the Carbon Toolbox. This sometimes makes it hard to determine which errors are yours, and which ones come from the Carbon Toolbox.

Once you are convinced that your AH is correct, all that is left is to turn on VoiceOver, and try out your application with VoiceOver.

For More Information

Posted: 2006-06-12