Alternatives to Dynamic Annotation

There are other ways to provide customized IAccessible support for UI elements, and in some cases, they are the correct solution. Prior to Dynamic Annotation, these alternative techniques were the only options available to developers. They include implementing all of the IAccessible interface and programmatic techniques.

Implementing All of the IAccessible Interface

One alternative technique is to implement all of the IAccessible interface. This approach is often necessary for custom controls or radically different UI elements; however, the development and test costs are significant enough that it should be avoided unless truly necessary. If the goal is to modify a single property, the cost is difficult to justify.

Programmatic Techniques

Another option is to use subclassing and wrapping techniques to modify the information being exposed for a specific property. This is the technique that Dynamic Annotation is intended to replace. To override a single property by using subclassing and wrapping, the developer must perform the following steps:

  1. Subclass the HWND of the IAccessible object.
  2. Intercept the WM_GETOBJECT message for the correct IParam/OBJID value.
  3. Forward the WM_GETOBJECT message to the base class using the CallWndProc callback function. If zero is returned, call CreateStdAccessibleObject; otherwise, call LresultFromObject on the returned value to obtain the control's native IAccessible interface pointer.
  4. Create a wrapper class, which implements IAccessible and wraps the IAccessible interface pointer returned from the previous step. This wrapper class sends all methods and properties to the original IAccessible interface pointer, except those that are to be overridden. This involves writing forwarding code for all of the IAccessible interface's 21 properties and methods, regardless of how many are actually overridden.

Also, developers must verify the following conditions:

  • The overridden method or property must only handle the required child IDs, and forward all others to the original IAccessible interface pointer.
  • Wrapping must also forward IEnumVARIANT and IOleWindow interfaces only if the original object supports them.
  • Reference counting must be handled correctly, especially if other interfaces are supported.
  • IDispatch return values must be handled correctly, especially with the ITypeInfo::Invoke method, which must be called with an interface pointer to the wrapper interface, not a pointer to the original IAccessible interface.

These techniques require a considerable amount of work, even if only one or two properties need to be overridden. The majority of the resulting code is concerned with subclassing and wrapping, and only a small fraction is actually providing the overridden information.

However, there are scenarios in which these techniques are needed. For example, if you are making structural changes to create a placeholder UI element, then you should use these techniques rather than Dynamic Annotation.

Fixing Names Derived from Labels

Some Microsoft Win32 common controls, such as the edit box control, are nearly always used with a label (an LTEXT entry in the resource file) or a group box (GROUPBOX in the resource file). Microsoft Active Accessibility automatically derives the name property of the control from its label. For such controls, the window text (shown in Microsoft Visual Studio as the Name or ID property) is ignored, because it is usually autogenerated and seldom very descriptive; for example, "IDC_EDIT1".

If the user interface of the application is not designed correctly, Microsoft Active Accessibility might not be able to set the name correctly. To be associated with a control, the label or group box must be placed immediately before the dynamic control in the tab order.

Tab order can be changed by using the tool in Visual Studio (on the Format menu when the resource editor is open) or by directly editing the resource file.

The following example shows a resource file's description of a dialog box that contains two labeled edit boxes.

IDD_INPUTNAME DIALOGEX 22, 17, 312, 118
STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
CAPTION "Enter your name"
FONT 8, "System", 0, 0, 0x0
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,179,35,30,11,WS_GROUP
    LTEXT           "First Name:",IDC_STATIC,8,16,43,8
    LTEXT           "Last Name:",IDC_STATIC,8,33,43,8
    EDITTEXT        IDC_EDITFIRSTNAME,53,15,120,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_EDITLASTNAME,53,34,120,12,ES_AUTOHSCROLL
END

In this example, the labels and controls are not listed in the correct tab order. As a result, Microsoft Active Accessibility assigns the name "Last Name" to the first-name edit box, and no name at all to the last-name edit box.

The following example shows the correct resource listing. Note also that shortcut keys have been designated in the labels.

IDD_INPUTNAME DIALOGEX 22, 17, 312, 118
STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
CAPTION "Enter your name"
FONT 8, "System", 0, 0, 0x0
BEGIN
    LTEXT           "&First Name:",IDC_STATIC,8,16,43,8
    EDITTEXT        IDC_EDITFIRSTNAME,53,15,120,12,ES_AUTOHSCROLL
    LTEXT           "&Last Name:",IDC_STATIC,8,33,43,8
    EDITTEXT        IDC_EDITLASTNAME,53,34,120,12,ES_AUTOHSCROLL
    DEFPUSHBUTTON   "OK",IDOK,179,35,30,11,WS_GROUP
END

When controls have supplementary labels, such as for minimum and maximum values on a trackbar, these labels should be placed after the control in the tab order. The main label of the control must appear immediately before the control itself.

Naming Controls Without Labels

It is not always possible or desirable to have a visible label for every control. However, you can still provide a name for the control by adding an invisible label. As always, the invisible label must immediately precede the control in the tab order.

If you are using the Resource Editor in Microsoft Visual Studio .NET, you can set the Visible property to False. To make the label invisible when editing the resource file (.rc), add NOT WS_VISIBLE or to the style part of the label control, as shown in the following example.

    LTEXT           "&FullName:",IDC_STATIC,111,23,44,8,NOT WS_VISIBLE

Note that any designated shortcut key works even though the label is invisible.