Visual Basic

Retrieving Accessibility Information

Three main functions allow you to retrieve information about objects in an application: AccessibleObjectFromEvent, AccessibleObjectFromWindow, and AccessibleObjectFromPoint. We won't be looking at events here, but we will be retrieving objects directly from a window and retrieving objects based on where the mouse is pointing on the form.

Retrieving objects from a window

The frmAccess form in the AAccess sample contains a number of standard controls, including list boxes, check boxes, and radio buttons. So the first thing we want to do is find which of these controls are accessible through the Active Accessibility interface. We need to start with a type declaration and a function declaration, which must go in a standard module, in this case mAccess.

Type tGUID
      lData1            As Long
      nData2            As Integer
      nData3            As Integer
      abytData4(0 To 7) As Byte
  End Type
  Declare Function AccessibleObjectFromWindow Lib "oleacc" _
      (ByVal hWnd As Long, ByVal dwId As Long, _
      riid As tGUID, ppvObject As Object) As Long

The tGUID type is a UDT that maps to the C++ REFIID structure, often called the class ID. The value we assign to this UDT is the class ID of the interface we want to retrieve. We'll be retrieving the IAccessible interface provided by Active Accessibility. AccessibleObjectFromWindow will return the IAccessible interface for the window object with the given window handle (hWnd).

    Dim oIA     As IAccessible
      Dim tg      As tGUID
      Dim lReturn As Long
      ' Define the GUID for the IAccessible interface.
      ' {618736E0-3C3D-11CF-810C-00AA00389B71}
      With tg
          .lData1 = &H618736E0
          .nData2 = &H3C3D
          .nData3 = &H11CF
          .abytData4(0) = &H81
          .abytData4(1) = &HC
          .abytData4(2) = &H0
          .abytData4(3) = &HAA
          .abytData4(4) = &H0
          .abytData4(5) = &H38
          .abytData4(6) = &H9B
          .abytData4(7) = &H71
      End With
      ' Retrieve the IAccessible interface for the form.
      lReturn = AccessibleObjectFromWindow(frmAccess.hwnd, 0, tg, oIA)

We now have an IAccessible interface for the form object contained in oIA. What we really want is to find out what accessible objects are on the form. For this we need to make a call to the AccessibleChildren function. Declare the function in the mAccess module as follows:

Declare Function AccessibleChildren Lib "oleacc" _
      (ByVal paccContainer As IAccessible, ByVal iChildStart As Long, _
       ByVal cChildren As Long, rgvarChildren As Variant, _
       pcObtained As Long) As Long

The first parameter is the IAccessible interface we just retrieved. The second parameter, iChildStart, is a zero-based index specifying which child you want to start retrieving. The third parameter indicates how many children you want to retrieve. AccessibleChildren returns an array of Variants in the rgvarChildren parameter. This array can contain either a list of objects or a list of system-defined object identifiers. The final parameter returns the number of objects that were retrieved into the array.

    Dim lStart      As Long
      Dim lHowMany    As Long
      Dim avKids()    As Variant
      Dim lGotHowMany As Long
      ' Get the IAccessible interface
      ' Initialize
      lStart = 0
      ' accChildCount returns the number of children for the given object.
      lHowMany = oIA.accChildCount
      ReDim avKids(lHowMany _ 1) As Variant
      lGotHowMany = 0
      ' Retrieve the form's children.
      lReturn = AccessibleChildren(oIA, lStart, lHowMany, _
                                   avKids(0), lGotHowMany)

At this point it appears that we have an IAccessible interface for the form and an array of child objects. Unfortunately, a quick look at the avKids array in the debugger shows this not to be the case.

AccessibleChildren has returned an array of Longs. These Long values represent those system-defined object identifiers I mentioned earlier. Fortunately, one of these identifiers happens to identify the form, so we must search for the form and start again.

Dim oNewIA       As IAccessible
  Dim avMoreKids() As Variant
  ' The first call to AccessibleChildren retrieved system information.
  ' Now we need to find the form object again.
  For i = 0 To lGotHowMany _ 1
      If oIA.accName(avKids(i)) = frmAccess.Caption Then
          Set oNewIA = oIA.accChild(avKids(i))
          lHowMany = oNewIA.accChildCount
      End If
  Next i
  ' Retrieve the children for the actual form IAccessible interface
  ' this time.
  ReDim avMoreKids(lHowMany _ 1) As Variant
  lReturn = AccessibleChildren(oNewIA, lStart, lHowMany, _
                               avMoreKids(0), lGotHowMany)

If you check the avKids array now, you'll see it's full of IAccessible objects, as shown here.

In the AAccess sample, I put this functionality in the cmdOK_Click event procedure so that we could retrieve the objects and then display them in a list box on the screen. Here are the results:

You might be surprised at the list you see. To start with, if you scroll through the list you'll notice that nine controls were returned. But if you count the controls on the form, you'll find eleven (two labels, one text box, two list boxes, two check boxes, two radio buttons, and two command buttons). The missing controls in the list are the labels. Labels are not included as accessible objects.

The next thing you'll notice is that some of the controls in the list don't have names. We retrieve the name from the IAccessible.accName property. For Visual Basic forms and controls, accName is the same as the corresponding Caption property. The list boxes and the text box don't have Caption properties, so their names aren't displayed in the list box.

Retrieving objects from a point

Another means of retrieving information about accessible objects is to query the object at a specific point on the screen. With Active Accessibility this is done with the AccessibleObjectFromPoint function. In our AAccess sample, when the user clicks the mouse, we'll find the accessible object at the point of the mouse click and display some of the object's properties in a list box. First let's declare the types and functions we need in our standard module.

      lx As Long
      ly As Long
  End Type
  Declare Function GetCursorPos Lib "user32" _
      (lpPoint as tPOINT) As Long
  Declare Function AccessibleObjectFromPoint Lib "oleacc" _
      (ByVal lx As Long, ByVal ly As Long, ppacc As IAccessible, _
       pvarChild As Variant) As Long

The tPOINT UDT maps to the Win32 POINT structure, which contains the coordinates for a point on the screen. You pass these x and y coordinates to AccessibleObjectFromPoint to retrieve the IAccessible object located at that point. The last parameter, pvarChild, returns a Variant. If the value is 0, the ppacc parameter contains the IAccessible interface of the child object, such as a command button on the form. A non-zero value in pvarChild indicates that the ppacc parameter contains the IAccessible interface of the parent object.

Because we're capturing the mouse location with Click events, the AccessibleObjectFromPoint logic is in a private subroutine that is called by each of the Click event procedures.

Private Sub GetClickEvent()
      Dim tp      As tPOINT
      Dim oIA     As IAccessible
      Dim vKid    As Variant
      Dim lResult As Long
      ' Get the cursor position where the mouse was clicked.
      Call GetCursorPos(tp)
      ' Get the IAccessible interface of the object located under
      ' the cursor position.
      lResult = AccessibleObjectFromPoint(tp.lx,, oIA, vKid)
      ' Add the object's accessibility information to the list box.
      On Error Resume Next  ' Not all the acc properties will work
                            ' with all the returned objects.
      With lstList2
          .AddItem "Object Name: " & oIA.accName(vKid)
          .AddItem "Default Action: " &  oIA.accDefaultAction(vKid)
          .AddItem "Keyboard Shortcut: " & _
      End With
  End Sub

Now when you run the AAccess sample you can click the different controls on the form and see what happens. The results will be similar to those shown here:

You can see that clicking labels doesn't have any effect. This means they're not supported as accessible objects. If you click the Form Objects button you see the properties for the button in the list box on the right, and the list on the left is populated with the form objects we retrieved with calls to AccessibleObjectFromWindow and AccessibleChildren. Once the list box on the left is populated, you can click individual list elements and see their properties in the list box on the right. You can also see how the default action changes when you toggle check boxes on and off.