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.
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.
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:
- Subclass the HWND of the IAccessible object.
- Intercept the WM_GETOBJECT message for the correct IParam/OBJID value.
- Forward the WM_GETOBJECT message to the base class using CallWndProc(). If zero is returned, call CreateStdAccessibleObject; otherwise, call LresultFromObject on the returned value to obtain the control's native IAccessible interface pointer.
- Create a wrapper class, which implements IAccessible and wraps the IAccessible interface pointer returned from the previous step. This wrapper class forwards 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 ITypeLib::Invoke(), 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.
Some 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 Microsoft 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 track bar, 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 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.