Tag Archive: testing


UI Automation and Microsoft Active Accessibility

UI Automation and Microsoft Active Accessibility

Microsoft Active Accessibility was the earlier solution for making applications accessible. Microsoft UI Automation is the new accessibility model for Microsoft Windows and is intended to address the needs of assistive technology products and automated testing tools. UI Automation offers many improvements over Active Accessibility.

This topic includes the main features of UI Automation and explains how these features differ from Active Accessibility.

Windows GUI自动化测试技术的比较和展望

【这里的自动化测试专指GUI自动化(不包含Web)】

以前写过一篇跟UI自动化测试有关的技术,谈到了一个自动化测试工具必备的几个功能,而且也提到了Windows平台自动化测试工具所基于的一些技术。下边就说一下这些技术的比较和展望,同时也包含了一些纠结……

  1. Windows API
    识别窗口:需要通过FindWindowEnumWindows来查找到窗口句柄,然后再调用其它API(GetWindowText,GetWindowRect, GetWindowLong…)来获取窗口属性,以此来找到想要的控件(窗口)
    操作窗口和获取属性:通过SetWindowTextGetWindowText来操作控件上显示的文字,通过SetForegroundWindow设置顶层窗口,GetForegroundWindow获取当前的顶层窗口,类似的还有GetActiveWindowSetActiveWindow。其它操作就不一一列举了……从理论上来说,通过Windows API和Windows Message可以完成对大部分窗口(控件也是窗口,一起都是窗口)的操作,也可以获取部分控件的部分属性。但是缺点和优点也比较明显:
    优点:就是看起来很高深很强大很底层,对标准Windows的控件支持还不错
    缺点:底层意味着复杂,意味着需要多层的封装,意味着开发效率低下,也意味着对Windows API的完全依赖。如果碰到一个非标准(自定义控件),用这种方式实现的自动化工具基本上完全束手无策,即使有开发团队的支持也很难完成自动化。当然了,算坐标,甚至用全局坐标来做也可以做,但这是野蛮做法,自动化测试的代码非常不稳定,维护和分析结果的成本也很高。
    总而言之,言而总之,没有哪个自动化测试工具完全用Windows API来实现……当然了,也不是说Windows API就没有用了,基本上所有的自动化工具都会或多或少,或直接或间接的调到Windows API,也没人敢说他不调一个Windows API就能做自动化测试工具的,最起码鼠标键盘消息的模拟他就没法弄……
  2. MSAA – Microsoft Active Accessibility
    MSAA在1997年在Windows 95中就已经包含的技术,也许你没有听说过,但是在所有的标准控件中,都实现了MSAA的接口,在微软所有的操作系统中都集成了MSAA的组件,在这里,我就不讨论MSAA的历史和相关技术了,就啰嗦一点点感慨:MSAA天生就不是设计给自动化测试的,它存在的意义在于提供一套接口,让开发人员可以方便的给残疾人开发可以使用的软件,比如读屏程序(鼠标移动到按钮的时候,可以发出声音,辅助视力障碍的人操作电脑),从而实现微软将电脑普及到每一个家庭的梦想。
    下面进入正题,虽然MSAA不是设计给自动化测试的,但是现有的所有自动化测试工具都是基于或者部分基于MSAA来实现的。MSAA很多时候直接把它叫做IAccessible,它本身是一个COM组件,最主要是实现了IAccessible的接口,它提供了一些方法,通过这些方法,可以获取控件更详细的信息,也可以通过一些方法对控件进行简单的操作(DoDefaultAction)。有兴趣的,可以参考Microsoft Active Accessibility
    优点:比起Windows API来说,用户只需要跟IAccessible打交道,通过这个接口能获得的控件信息相对丰富很多,基本操作也不需要通过Windows Message的方式来实现。另外一个比较大的优点就是,自定义控件的支持,当然了,并不是说开发写一个自定义控件,这个控件就可以通过MSAA来识别,而是说当开发人员在实现自定义控件的时候,可以实现IAccessible的接口,并且通过这个接口,把一些的属性和操作暴露出来,测试人员就可以将这个控件当作标准控件,并通过MSAA来自动化。看起来麻烦了点,但是最起码对自动化自定义控件提供了可能。
    缺点:天生不足,MSAA从来就不是给自动化测试设计的,所以也不会考虑自动化测试的需求,获取到的控件信息比Windows API多,但是相对自动化测试的需求来说还是远远不够,而且仅仅支持一个基本操作,其它的操作还必须通过Windows Message。另外就是微软推出WPF以后,MSAA的局限性越加明显(这也是因为WPF的控件属性更加丰富,更具定制性,更自由,用MSAA难以描述),这也是微软推出UIAutomation的一个的诱因。
  3. UIAutomation
    伴随着自动化测试的应用越来越广泛,以及WPF的发布,微软在MSAA的基础上,对MSAA进行封装,重新设计并实现了UIAutomation的类库(.Net),微软根据自动化测试的需求,重新实现了一套自动化体系,大家可以看下边的图,这个比较准确。从此自动化测试人员迎来了更广阔的一片蓝天(虽然还飘着点小小的乌云……),随之也有了一些小小的纠结:
    a. UIAutomation (后边就简称为UIA) Vs MSAA
    在UIA发布的时候,基于MSAA的自动化工具已经发展的非常成熟,比如Silktest和WinRunner… 那么大家在开发一个自己的自动化工具的时候,应该用MSAA呢,还是UIA? 这篇文章可以给一个两者的大概关系:UI Automation and Microsoft Active Accessibility。 按照微软的想法和设计,UIA是要取代MSAA成为自动化测试的标准类库,并且对WPF来说,UIA才是一等公民。从架构上来讲,UIA在针对标准控件的时候,通过UI Automation Proxy调用了MSAA Server,基本上覆盖了MSAA的功能:

Value of Selected Server Side CheckboxList Item in Client Javascript

[Issue]

If you are using CheckboxList ASP.NET

Data Driven Testing

Summary: Data Driven Testing is a good practice that allows you to have a broad range of test values, which are repeatable

Article demonstrating DDT with an Excel sheet: http://msdn.microsoft.com/msdnmag/issues/06/11/TestRun/default.aspx

VSTS has built-in DDT hooks. Find more info here: TFS Data Driven Testing

DDT API
An alternative to using VSTS for DDT is creating a simple DDT API with some methods that generate values (these values can be different each time depending on desired behavior) for you. If you want it to do more than one value than you simply put it in a loop. We used this approach heavily in the Geo Segmentation project. Our test would ask for an IP address within Japan and the API would hand us one randomly. This was a lot faster than if we would’ve put the data into an Excel sheet and used the VSTS hooks. Also, in our case it provided an even broader range of values without having to run every single value every time.

Accessibility Object Roles

The following values, defined in oleacc.h, describe the roles of objects within an application. Use only the values from the following list; do not add custom roles or roles that are not predfined. Prior to using these object roles, client applications should use Inspect to verify that the object role is used by UI elements.

Clients retrieve an object’s role by calling IAccessible::get_accRole, which returns either a string or one of the following values. Clients call GetRoleText with the role value to retrieve a localized string that describes the object’s role. The localized strings for the role values are in the file oleaccrc.dll.

ROLE_SYSTEM_ALERT
The object represents an alert or a condition that a user should be notified about. This role is used only for objects that embody an alert but are not associated with another user interface element such as a message box, graphic, text, or sound.
ROLE_SYSTEM_ANIMATION
The object represents an animation control, which contains content that changes over time, such as a control that displays a series of bitmap frames. Animation controls are displayed when files are copied, or when some other time-consuming task is performed.
ROLE_SYSTEM_APPLICATION
The object represents a main window for an application.
ROLE_SYSTEM_BORDER
The object represents a window border. The entire border is represented by a single object rather than by separate objects for each side.
ROLE_SYSTEM_BUTTONDROPDOWN
The object represents a button that drops down a list of items.
ROLE_SYSTEM_BUTTONDROPDOWNGRID
The object represents a button that drops down a grid.
ROLE_SYSTEM_BUTTONMENU
The object represents a button that drops down a menu.
ROLE_SYSTEM_CARET
The object represents the system caret.
ROLE_SYSTEM_CELL
The object represents a cell within a table.
ROLE_SYSTEM_CHARACTER
The object represents a cartoon-like graphic object, such as Microsoft Office Assistant, which is displayed to provide help to users of an application.
ROLE_SYSTEM_CHART
The object represents a graphical image used to represent data.
ROLE_SYSTEM_CHECKBUTTON
The object represents a check box control, an option that is turned on or off independently of other options.
ROLE_SYSTEM_CLIENT
The object represents a window’s client area. Active Accessibility uses this role as a default if there is a question about the role of a UI element.
ROLE_SYSTEM_CLOCK
The object represents a control that displays time.
ROLE_SYSTEM_COLUMN
The object represents a column of cells within a table.
ROLE_SYSTEM_COLUMNHEADER
The object represents a column header, providing a visual label for a column in a table.
ROLE_SYSTEM_COMBOBOX
The object represents a combo box; an edit control with an associated list box that provides a set of predefined choices.
ROLE_SYSTEM_CURSOR
The object represents the system mouse pointer.
ROLE_SYSTEM_DIAGRAM
The object represents a graphical image used to diagram data.
ROLE_SYSTEM_DIAL
The object represents a dial or knob.
ROLE_SYSTEM_DIALOG
The object represents a dialog box or message box.
ROLE_SYSTEM_DOCUMENT
The object represents a document window. A document window is always contained within an application window. This role applies only to multiple-document interface (MDI) windows and refers to the object that contains the MDI title bar.
ROLE_SYSTEM_DROPLIST
The object represents the calendar control, SysDateTimePick32. Oleacc uses this role to indicate that Active Accessibility has found either a date or a calendar control.
ROLE_SYSTEM_EQUATION
The object represents a mathematical equation.
ROLE_SYSTEM_GRAPHIC
The object represents a picture.
ROLE_SYSTEM_GRIP
The object represents a special mouse pointer, which allows a user to manipulate user interface elements such as windows. For example, a user clicks and drags a sizing grip in the lower-right corner of a window to resize it.
ROLE_SYSTEM_GROUPING
The object logically groups other objects. There is not always a parent-child relationship between the grouping object and the objects it contains.
ROLE_SYSTEM_HELPBALLOON
The object displays a Help topic in the form of a ToolTip or Help balloon.
ROLE_SYSTEM_HOTKEYFIELD
The object represents a hot-key field that allows the user to enter a combination or sequence of keystrokes.
ROLE_SYSTEM_INDICATOR
The object represents an indicator, such as a pointer graphic, that points to the current item.
ROLE_SYSTEM_IPADDRESS
The object represents an edit control designed for an Internet Protocol (IP) address. The edit control is divided into sections for the different parts of the IP address.
ROLE_SYSTEM_LINK
The object represents a link to something else. This object might look like text or a graphic, but it acts like a button.
ROLE_SYSTEM_LIST
The object represents a list box, allowing the user to select one or more items.
ROLE_SYSTEM_LISTITEM
The object represents an item in a list box or the list portion of a combo box, drop-down list box, or drop-down combo box.
ROLE_SYSTEM_MENUBAR
The object represents the menu bar (positioned beneath the title bar of a window) from which menus are selected by the user.
ROLE_SYSTEM_MENUITEM
The object represents a menu item, which is an entry in a menu that a user can choose to carry out a command, select an option, or display another menu. Functionally, a menu item is equivalent to a push button, radio button, check box, or menu.
ROLE_SYSTEM_MENUPOPUP
The object represents a menu, which presents a list of options from which the user can make a selection to perform an action. All menu types must have this role, including drop-down menus that are displayed by selecting from a menu bar, and shortcut menus that are displayed by clicking the right mouse button.
ROLE_SYSTEM_OUTLINE
The object represents an outline or tree structure, such as a tree view control, that displays a hierarchical list and allows the user to expand and collapse branches.
ROLE_SYSTEM_OUTLINEBUTTON
The object represents items that navigate like an outline item. You can use the up and down arrows to move through the outline. However, instead of expanding and collapsing the menus by using left and right arrow keys, these menus expand or collapse when the space bar or enter key is pressed and the item has focus.
ROLE_SYSTEM_OUTLINEITEM
The object represents an item in an outline or tree structure.
ROLE_SYSTEM_PAGETAB
The object represents a page tab. The only child of a page tab control is a ROLE_SYSTEM_GROUPING object that contains the contents of the associated page.
ROLE_SYSTEM_PAGETABLIST
The object represents a container of page tab controls.
ROLE_SYSTEM_PANE
The object represents a pane within a frame or document window. Users can navigate between panes and within the contents of the current pane, but cannot navigate between items in different panes. Thus, panes represent a level of grouping lower than frame windows or documents, but above individual controls. The user navigates between panes by pressing TAB, F6, or CTRL+TAB, depending on the context.
ROLE_SYSTEM_PROGRESSBAR
The object represents a progress bar, dynamically showing the user the percent complete of an operation in progress. This control takes no user input.
ROLE_SYSTEM_PROPERTYPAGE
The object represents a property sheet.
ROLE_SYSTEM_PUSHBUTTON
The object represents a push button control.
ROLE_SYSTEM_RADIOBUTTON
The object represents an option button, also called a radio button. It is one of a group of mutually exclusive options. All objects sharing a single parent that have this attribute are assumed to be part of single mutually exclusive group. Use ROLE_SYSTEM_GROUPING objects to divide them into separate groups.
ROLE_SYSTEM_ROW
The object represents a row of cells within a table.
ROLE_SYSTEM_ROWHEADER
The object represents a row header, which provides a visual label for a table row.
ROLE_SYSTEM_SCROLLBAR
The object represents a vertical or horizontal scroll bar, which is part of the client area or used in a control.
ROLE_SYSTEM_SEPARATOR
The object is used to visually divide a space into two regions, such as a separator menu item or a bar that divides split panes within a window.
ROLE_SYSTEM_SLIDER
The object represents a slider, which allows the user to adjust a setting in given increments between minimum and maximum values.
ROLE_SYSTEM_SOUND
The object represents a system sound, which is associated with various system events.
ROLE_SYSTEM_SPINBUTTON
The object represents a spin box, which is a control that allows the user to increment or decrement the value displayed in a separate “buddy” control associated with the spin box.
ROLE_SYSTEM_SPLITBUTTON
The role represents a button on a toolbar that has a drop-down list icon directly adjacent to the button.
ROLE_SYSTEM_STATICTEXT
The object represents read-only text, such as labels for other controls or instructions in a dialog box. Static text cannot be modified or selected.
ROLE_SYSTEM_STATUSBAR
The object represents a status bar, which is an area at the bottom of a window that displays information about the current operation, state of the application, or selected object. The status bar has multiple fields, which display different kinds of information.
ROLE_SYSTEM_TABLE
The object represents a table that contains rows and columns of cells, and optionally, row headers and column headers.
ROLE_SYSTEM_TEXT
The object represents selectable text that allows edits or is designated read-only.
ROLE_SYSTEM_TITLEBAR
The object represents a title or caption bar for a window.
ROLE_SYSTEM_TOOLBAR
The object represents a toolbar, which is a grouping of controls that provides easy access to frequently used features.
ROLE_SYSTEM_TOOLTIP
The object represents a ToolTip that provides helpful hints.
ROLE_SYSTEM_WHITESPACE
The object represents blank space between other objects.
ROLE_SYSTEM_WINDOW
The object represents the window frame, which contains child objects such as a title bar, client, and other objects contained in a window.

Copy from: http://www.vckbase.com/document/viewdoc/?id=883

MS Active Accessibility 接口技术编程尝试

编译/崔传凯

下载源代码

Software Driving Software:
Active Accessibility-Compliant Apps Give Programmers New Tools to Manipulate Software

Dmitri Klementiev

This article assumes you’re familiar with C++, COM, and Win32
Level of Difficulty

Making Custom Controls Accessible

These articles provide an overview of Microsoft technologies that can be used to make a custom control accessible. Techniques range from creating or overriding properties with Dynamic Annotation, to using the new IAccessibleEx interface to add UI Automation support to controls that already support Microsoft Active Accessibility.

Introduction to Microsoft Active Accessibility (By Joseph Dempsey)

Copy from: http://www.codeproject.com/KB/winsdk/msaa_intro.aspx?display=Print

Introduction

This is the first is a series of articles that I hope to write concerning the Microsoft Active Accessibility (MSAA) technology. Much of my current work has surrounded this topic and there seems to be very little in the way of useful introductions to the topic. To start, I would like to provide an introduction to MSAA so that an understanding of what it does and why it exists is present. After the groundwork is established we can move into how it would be implemented in an application.

In the final articles in this series I would like to offer a few new tools to aide in the development and debugging of your MSAA enhanced applications and perhaps even go over how MSAA can be used for application automation. The tools are meant to be a replacement for the tools provided by Microsoft which seem to fall very short of usable. These tools will have their source code made available for you to review. The tools are developed with the same concepts that will be shown throughout this series and hence should provide you with a perspective on implementing applications that use MSAA.

The rest of the articles in this series will be written as my time permits. Since there is not sure way to gauge this, each article will stand on its own providing relevant information without the presence of the others. Enjoy!

What is Accessibility?

Accessibility, in this context, is creating your application or framework in such a manner that it will be usable by those users with certain physical handicaps such as blindness, low version, hard-of-hearing or deafness. Depending on where you are developing your application or who you are developing it for you may be legally required to ensure your application is accessible. When working with Microsoft Windows there are two main forms of accessible technologies available to you.

The first and simplest is the system’s ability to enter a High Contrast (HC) mode. In this mode the system enters a color scheme, selected by the user, that is supposed to provide great contrast between the foreground and background. This is targeted at low-vision users. If you would like to see what your systems look like in High Contrast mode you can hold down (LEFT-ALT + LEFT-SHIFT + PRNTSCRN). You can also go to Control Panel -> Accessibility Options -> Display. Once activated you will see your system change into a contrasting color scheme. The default high contrast scheme is black backgrounds with white foregrounds. Notice that the title bars also change colors. This can be an important thing to remember when developing your application because the switch is based on system colors. These are the colors that can be obtained through the ::GetSysColor(..) API. In all places that you use custom colors, those colors will stay the same even when in HC mode. This may make your application harder to use for those people with vision impairments. When possible, you should strive to use system colors to keep your application accessible.

The second form of accessibility available to applications is MSAA. If your application uses the standard Win32 controls and that is all then it is very likely you will have little to no work to do to be accessible. Microsoft provides interfaces for all of its controls. If you are developing a toolkit of custom controls or perhaps just a single control for use in your application then it may become necessary for you to add MSAA support for those custom controls. Since Windows does not know the purpose of your control, what it is or how it is structured it is necessary for you to be able to relay that information so that Assistive Technologies (ATs) such as screen readers, magnifiers and other devices such as Braille displays can properly relay information about your control or controls to the user. The sections below will go into more detail concerning how MSAA works and what is required for you to implement it for a given control.

The Pieces to the MSAA Puzzle

There are two main mechanisms to be aware of when in comes to MSAA; the MSAA event model and the IAccessible COM interface. The event model is the mechanism applications/controls use to notify the system that a particular event has taken place within a control. This can be something such as a the control just gained focus or the user modified value of the control. The IAccessible interface is a COM interface that can be implemented in relation to a control so that an AT can query information about the control. This interface would be used to get the current value, determine the state of the control (selected, focused, disabled, etc..) and even perform actions such as moving through the MSAA DOM.

The IAccessible Interface

The first thing to cover is going to be the IAccessible interface and its inner workings. This is necessary because some of the fundamental concepts explained here, as they relate to the MSAA DOM, are required to understand how MSAA events transmitted to the system are then in turn queried by ATs for a greater level of information. Please note that this section goes into a fair bit of detail concerning the various aspects of the MSAA interface including DOM construction, roles, identification elements such as names and descriptions and state data. For a single control it is unlikely you will be concerned with building an entire DOM of elements rather you will be looking to implement a single element in an existing MSAA DOM. Even though most will be looking at the smaller case I wanted to provide as much detail as possible so that those people looking for an understanding of the whole picture can see how everything works. From that point, you should be able to infer all the information you need for a single element. People that are likely to need to build an entire MSAA DOM are those people putting together an entire UI toolkit of custom controls. Since you cannot rely on Microsoft’s controls at that point the burden of providing MSAA interfaces falls on you.

The MSAA DOM for an application is a tree structured group of elements that defines the accessible elements for an application. This means that each control that can be used by the end user should show up in the DOM. For a standard application, one that uses Std. Win32 controls, each control element such as edits, lists, list items, trees, tree items, menus, etc… will have a related elements within the MSAA DOM. If you have a custom control that has a visual-only purpose, such as a spacer control or other type of element that helps with the layout of visual/usable controls it it not necessary for it to implement IAccessible. Since the user cannot interact with this control and it provides no information to the user, it can be left out of the DOM. This is can even be considered good practice as the DOM will be more compact, hence more efficient to walk.

Below you can see a view of an MSAA DOM I retrieved by inspecting a folder view through Windows Explorer.

Screenshot - msaa_intro1.jpg

This image shows us a couple of things. First, I started the inspection at the control level rather than the window level. What we get in a standard list control and all my files/folders which show up as elements within the list.

First, let’s examine the structure of a MSAA DOM, using the above image as a reference point, and after that we can examine the various information that can be conveyed through the IAccessible interface on each of the elements. Notice in the structure above the first element is that of the list control itself. Each of the items within the list exist as children to the container. You also can see, at the bottom of the tree, where the column headers are shown. Each of these elements exist as children to the list as well, albeit at a different level.

Before we dive headfirst into how the IAccessible interface is used to walk over and read from the MSAA DOM take a look at the code block below. This is what the actual COM interface looks like.

interface IAccessible : IDispatch
{
  HRESULT STDMETHODCALLTYPE get_accParent([retval][out]
      IDispatch **ppdispParent);
  HRESULT STDMETHODCALLTYPE get_accChildCount(
      [retval][out] long *pcountChildren);
  HRESULT STDMETHODCALLTYPE get_accChild([in] VARIANT varChild,
      [retval][out] IDispatch **ppdispChild);
  HRESULT STDMETHODCALLTYPE get_accName([in] VARIANT varChild,
      [retval][out] BSTR *pszName);
  HRESULT STDMETHODCALLTYPE get_accValue([in] VARIANT varChild,
      [retval][out] BSTR *pszValue);
  HRESULT STDMETHODCALLTYPE get_accDescription([in] VARIANT varChild,
      [retval][out] BSTR *pszDescription);
  HRESULT STDMETHODCALLTYPE get_accRole([in] VARIANT varChild,
      [retval][out] VARIANT *pvarRole);
  HRESULT STDMETHODCALLTYPE get_accState([in] VARIANT varChild,
      [retval][out] VARIANT *pvarState);
  HRESULT STDMETHODCALLTYPE get_accHelp([in] VARIANT varChild,
      [retval][out] BSTR *pszHelp);
  HRESULT STDMETHODCALLTYPE get_accHelpTopic([out] BSTR *pszHelpFile,
      [in] VARIANT varChild,[retval][out] long *pidTopic);
  HRESULT STDMETHODCALLTYPE get_accKeyboardShortcut([in] VARIANT varChild,
      [retval][out] BSTR *pszKeyboardShortcut);
  HRESULT STDMETHODCALLTYPE get_accFocus([retval][out] VARIANT *pvarChild);
  HRESULT STDMETHODCALLTYPE get_accSelection([retval][out]
      VARIANT *pvarChildren);
  HRESULT STDMETHODCALLTYPE get_accDefaultAction([in] VARIANT varChild,
      [retval][out] BSTR *pszDefaultAction);
  HRESULT STDMETHODCALLTYPE accSelect([in] long flagsSelect,
      [in] VARIANT varChild);
  HRESULT STDMETHODCALLTYPE accLocation([out] long *pxLeft,
      [out] long *pyTop,[out] long *pcxWidth,[out] long *pcyHeight,
      [in] VARIANT varChild);
  HRESULT STDMETHODCALLTYPE accNavigate([in] long navDir,
      [in] VARIANT varStart, [retval][out] VARIANT *pvarEndUpAt);
  HRESULT STDMETHODCALLTYPE accHitTest([in] long xLeft, [in] long yTop,
      [retval][out] VARIANT *pvarChild);
  HRESULT STDMETHODCALLTYPE accDoDefaultAction([in] VARIANT varChild);
  HRESULT STDMETHODCALLTYPE put_accName([in] VARIANT varChild,
      [in] BSTR szName);
  HRESULT STDMETHODCALLTYPE put_accValue([in] VARIANT varChild,
      [in] BSTR szValue);
};

When it comes to constructing the MSAA DOM and in turn, walking through it, there are two main points to understand. Point #1 is that not all elements implement the IAccessible interface. Point #2 is that all elements can be found using a child ID. Children under a given element are located through a 1-based index. There is a special child id called CHILDID_SELF (this constant equals 0) that, when used with a function like get_accChild, returns the element itself rather than a child.

Elements in the MSAA DOM that implement the IAccessible interface are called complex elements while elements that do not implement this interface are called simple elements. If a control has child items then it will be implementing the interface and hence, is a complex element. If it didn’t implement IAccessible there would be no way to gain access to the child elements. Elements that do not have any children do not need to implement the interface. Information about the element can be obtained from the complex parent element by passing in the associated child ID. In the DOM shown in the image above we have a list control that is a complex element. Under the list itself exists all of the list items. Since list items have no children and hence do not implement IAccessible they are simple elements.

Accessible Data

The IAccessible interface is provided so that information regarding a non-standard control can be obtained by an AT in order to provide the user feedback as to what is happening on screen. There are a number of different pieces of information available for this purpose. Below is a list of the data bits that can be retrieved through IAccessible that are relevant to the user. I have left out things such as parent, child, child count, keyboard shortcut, etc… as I imagine they are pretty self explanatory.

  • Role – The role of a given control is what ATs use to determine what behavior to expect from a control. You should select a role after reviewing all of the available roles provided by MSAA and determining which role best fits your control. For example, if you were creating a control that could be clicked with the mouse and perhaps even activated with the spacebar than it would share very similar traits with a standard button. In that case, you would assign it a role of ROLE_SYSTEM_PUSHBUTTON. See MSDN for a complete list of available MSAA roles.
  • Name – The accessible name of the control should be a brief identifying string. In most cases if your control has visible text that will be what the accessible name will be. For example, a button with the text “I’m a Button” on it should give “I’m a Button” back through get name function. A more complex example would be in the case of an edit field. If you have an edit field that the user is supposed to type a username into then your control should return “Username”, or some variant thereof, back to the caller. In many cases you would have a static text control showing users what is to go in the field. In those cases it may be easier for you to retrieve the text that a sighted user would see from the static text control and return it instead. Please note, however, it is good practice to keep the name short and to the point. Too much textual information could lead to confusion for blind or low vision users.
  • Value – The value of a control depends completely on what the control is. For example, a button has no value. Whether or not it is pressed is determined by its state. A good example of a control with a value would be an edit field. A field with an accessible name of “Username” would have a value of whatever the user has typed into the field or what has been set programmatically. Remember that this must always stay up-to-date with the actual value in the control so the user always has an accurate verbal representation of what is in the control.
  • Description – The accessible description is a slightly open ended field. This attribute can be used in many different ways by different AT vendors. In most cases, it is good to populate this field with a slightly more verbose description of what the element is for. Previously we talked about returning “Username” for the username edit field. If we were to come up with a description of this field it could perhaps be set to “Please type in the your username for access to the Delta Corp. secure network”.
  • State – The state is just that; the state of the control. The state is returned back to the caller as a bunch of bits set in a 32 bit long. The states that are supported will depend on the control you implemented. For a list of all the available state constants please see MSDN.

MSAA Events

As mentioned above, MSAA events are the mechanism by which the application/control notifies the system that an event has happened pertaining to the control. ATs can determine when these events have happened and respond by notifying the user. An example would be if the user had a screen reader, such as Windows Narrator, running while using an application and a focus change event occurs. As focus moves to the MSAA compatible control an event is generated and passed to the OS. The screen reader would be listening for these events and would respond to the event by reading off the name of the newly focused control to the user. The same example can also be applied to states of controls. For example, if focus is sitting on a checkbox and the user uses the spacebar to change the selected state of the control. An event would be fired and eventually the screen reader would respond by notifying the user of the new state of the checkbox.

Microsoft provides a fairly comprehensive list of events that can be sent through the system to notify all listening ATs. If you would like to see a list of all the events available you can view them on this MSDN site. All events are transmitted to the OS by the API call NotifyWinEvent(..). The prototype for this function can be seen below.

void WINAPI NotifyWinEvent(
  DWORD event,
  HWND hwnd,
  LONG idObject,
  LONG idChild
);

Please note that I have been careful to say that all events are transmitted to the system (OS) and not the ATs themselves. All accessible events are pushed through the NotifyWinEvent(..) call and from there the OS does the work. All assistive technologies that wish to respond to the events on the system do so by using a hook. That hook is called after each event and from there the AT can decide on what to do with it. Below you will see the prototype for the function that is used to create such a hook.

HWINEVENTHOOK WINAPI SetWinEventHook(
  UINT eventMin,
  UINT eventMax,
  HMODULE hmodWinEventProc,
  WINEVENTPROC lpfnWinEventProc,
  DWORD idProcess,
  DWORD idThread,
  UINT dwflags
);

// The event procedure need to use this hook must have the 
// following prototype. (WINEVENTPROC)

VOID CALLBACK WinEventProc(
  HWINEVENTHOOK hWinEventHook,
  DWORD event,
  HWND hwnd,
  LONG idObject,
  LONG idChild,
  DWORD dwEventThread,
  DWORD dwmsEventTime
);

Putting it Together

So far we have talked about the IAccessible interface and events that are generated by controls. Most ATs respond to events by notifying the user of that event in some way. To do this, they often need to acquire information from the control itself which means they will need to get an IAccessible interface from us. So for the moment, the question for us is; How do we provide external applications with an IAccessible interface? The answer is simple: WM_GETOBJECT. When an AT gets an event or perhaps arrives at our window by walking down the MSAA DOM of parent windows they can use various Win32 API functions such as AccessibleObjectFromWindow or AccessibleObjectFromEvent to get back an interface. Under the hood Windows sends the server application a WM_GETOBJECT message. We then return the related interface back to the caller as the return value for the message after pushing it through the LresultFromObject API call. To see this in action look for the sample application we are going to build next!

Information Put to the Test

Now that you have a fair bit of background on all the players involved in this little dance its time to start putting some of this to use. To demonstrate how to create a custom control with an MSAA backing to it we are going to build an application that uses a revolutionary new control call “UglyButton”. Now this control is no ordinary button mind, it is a button that has two more buttons contained within it. This control will have a single HWND for the control itself. The two buttons contained with the control will no HWND but will be represented to the user as part of the MSAA DOM. We will also have the ability to have a line of text above the two inner buttons since we are just that kind of wild and crazy folk.

Quick Note: I am not going to go over the inner workings of how to create custom controls and/or how to handle standard windows messages or anything else of the like. I expect you already have experience with Windows programming as a whole and if you need to understand how the control itself is being built please look here.

The Application

We are going to build an application that implements our ugly button control and places one in our main window. This application is a standard win32 application with no MFC support. The reason I have elected to do this without MFC is that I would like to expose the true flavor of implementing MSAA on a control that otherwise has no accessibility support. MFC has its own support of the IAccessible interface built into the CWnd base class. Once you understand how everything works together, going back and using MFC’s built-in support should be a simple. It is important to understand the more complicated method first though as you may find yourself needing to work with a UI class library that is not MFC based.

Also of note, the control we are going to create does not have much functionality or standard support for things like resizing, moving, etc… This is just here to illustrate the MSAA side of things. Now that the disclaimer is out of the way; onward we march!

Our application has three main pieces; The core application file where the app’s message pump exists, the ugly button control file and of course our accessible proxy object which has the implementation for IAccessible. I assume that those reading this article can navigate the UglyButton.cpp/h files and determine most everything that is going on there. There is not much to it and I will not waste time going over it here except in those places it relates to our MSAA implementation for the control. The same goes for the application file. This leaves us with the object implementing IAccessible, CAccProxyObject.

There is something very important to note here. We keep a CAccProxyObject stored in a window property for each ugly button. This object serves as our proxy to our ugly button control. We push a pointer to the button data structure into this proxy for it to hold on to. This is an important concept because if our ugly button goes away before the interface itself is ref counted to zero the button data within the proxy object will be nulled out. This allows all client applications that are holding onto this interface to get a special error code, CO_E_OBJNOTCONNECTED, if they attempt to use an interface after the element it represents is gone. If you didn’t implement a proxy you could end up referencing a window and children elements that do not exist anymore through an interface that was obtained when they were still valid.

Reviewing the Proxy Object

At this point you may want to take a moment to review the code for the IAccessible object. Most of the code there is pretty easy to read and I attempted to keep it as well commented as possible to make understanding what was going on and why as easy as possible. While I do not intend to go over each function (I figure that’s something you can do on your own) I would like to hit on a couple bullet points about using and interacting with the interface.

Working With Child IDs

It is important to notice that almost every single function in the IAccessible interface requires you pass it a child ID. If you remember from above, child IDs are used to reference elements contained under a complex element. In the case of our ugly button we have the interface being implemented for the button itself which is our complex element. This element can also be referred to by the special child ID, CHILDID_SELF. Under that element there are two simple elements which are the two inner buttons. These buttons are child IDs 1 and 2 respectively. If we had more dynamic content, such as a list box where the list items themselves were child items, we would have to determine the child IDs using a more dynamic process. For our purposes though, just knowing our button has two inner buttons lets us statically reference them by the 1 and 2 child IDs.

You can see the structure of our button control is the image of the MSAA DOM of our sample program below.

Screenshot - msaa_intro2.jpg

Since the concept of the child IDs is the same no matter what function we are dealing with I am just going to go through one of the functions quickly and leave the rest up to you for review. We will take a look at one of the more frequently used functions, get_accName. The implementation for this function is shown below.

STDMETHODIMP CAccProxyObject::get_accName(VARIANT varChild, BSTR *pszName)
{
  HRESULT retCode = DATACHECK(mData);
  if (SUCCEEDED(retCode))
  {
    if (pszName && VALID_CHILDID(varChild))
    {
      GENBTNDATA* pData = NULL;
      switch (varChild.lVal)
      {
        case CHILDID_SELF:
          pData = &mData->btnSelf;
          break;
        case 1:
          pData = &mData->btnOne;
          break;
        case 2:
          pData = &mData->btnTwo;
          break;
      };

      if (pData)
      {
        // First preference goes to the set accessible name. 
        // If no accessible name is available 
        if (pData->pszAccName)
          *pszName = ::SysAllocString(pData->pszAccName);
        else if (wcscmp(L"", pData->szText) != 0)
          *pszName = ::SysAllocString(pData->szText);
      }
    }
    else
      retCode = E_INVALIDARG;
  }
  return retCode;
}

As you can see from the code above, there really isn’t much to implementing this function. The first parameter for the this function is the child ID of the item that we are to retrieve the name for. There are two macros that are used in this function that are not shown in the code here. DATACHECK is a macro that verifies that mData is non-null. If it isn’t null the return code is set to S_OK. If our data element is null then it sets the return code to CO_E_OBJNOTCONNECTED. Remember, this was the whole point of having a proxy object. The other macro is VALID_CHILDID. This macro simply verifies that the type of the variant is VT_I4 and that the lVal member of the variant structure is between CHILDID_SELF (0) and 2. If it isn’t within that range then we were given a bad child ID and need to return the invalid argument error code.

Once we are sure that we have a valid data element and that our child ID is correct we can go forward in retrieving the information requested. The switch statement takes the child ID passed in and gets the data block associated with either the outer button or one of the two inner buttons. From there we just create a copy of the string and set it to the storage location provided by the caller. They are responsible for freeing the string’s memory. You may also note that we support two different types of accessible names. If someone calls the put_accName function to manually override the accessible name we store it in the pszAccName. If this element is never set then we simply attempt to use whatever visual text is on the button element itself.

Retrieving the Interface

Now that we have covered the interface, it would be helpful to know how it is obtained. The small block of code below is all it takes to get our IAccessible interface back to the client application.

case WM_GETOBJECT:
  {
    // This is the message that we must handle so that we can return the
    // IAccessible pointer that relates to this control.
    CAccProxyObject* pProxy = (CAccProxyObject*)GetProp(hWnd, MAKEINTATOM(
        UBPROPATOM_CACC));
    if (pProxy)
    {
      LRESULT lRes = LresultFromObject(IID_IAccessible, wParam, static_cast(
          pProxy));
      return lRes;
    }
  }
  break;

After a MSAA event is processed or one of the Win32 API function is used on our window a WM_GETOBJECT message will be sent to us requesting we give back an accessible interface for our window. Since we should already have our interface created we simply look it up from the window property and call the LresultFromObject API call to convert it to value we need to return. Since our proxy objects persist this is all we need to do here. If you were creating these objects on an as-needed basis only (this is not recommended) then you would need to call the Release() method after the LresultFromObject call since it does an AddRef() on your behalf. That’s it! With this code a client application can now can now retrieve a copy of your IAccessible implementation.

Generating MSAA Events

Last but not least is our implementation of MSAA events. For this sample I did not bother implementing every possible event our button could possible have. Instead I picked the most important ones as they relate to a button control.

As the user interacts with the application, specifically our button, we need to pass MSAA events down to the OS so it can notify all listening ATs that something just happened. You can read the available reference above for a list of all the events but lets take a look at one in specific.

if (PtInRect(&lpData->btnOne.rcBounds, point))
{
  lpData->btnOne.bPushed = TRUE;
  lpData->btnOne.bHasKbFocus = TRUE;
  lpData->btnTwo.bHasKbFocus = FALSE;
  lpData->btnSelf.bHasKbFocus = FALSE;            

  NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hWnd, (LONG)&lpData->btnOne, 1);
}
else if(PtInRect(&lpData->btnTwo.rcBounds, point))
{
  lpData->btnTwo.bPushed = TRUE;
  lpData->btnOne.bHasKbFocus = FALSE;
  lpData->btnTwo.bHasKbFocus = TRUE;
  lpData->btnSelf.bHasKbFocus = FALSE;            

  NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hWnd, (LONG)&lpData->btnTwo, 2);
}
else
{
  lpData->btnSelf.bPushed = TRUE;
  lpData->btnOne.bHasKbFocus = FALSE;
  lpData->btnTwo.bHasKbFocus = FALSE;
  lpData->btnSelf.bHasKbFocus = TRUE;            

  NotifyWinEvent(EVENT_OBJECT_STATECHANGE, hWnd, (LONG)&lpData->btnSelf,
      CHILDID_SELF);
}

This code comes out of the left button down handler. When someone presses down on the left mouse button the button it is over goes into a pressed state. Since the state of our button has now changed we need to notify the system that the change has happened. To do this we use the NotifyWinEvent Win32 API function and pass in the state change constant. Also note that we have to pass in the child ID that to which the event relates. For our button control we are passing in a custom identifier (a pointer to the button data) as the object ID. While in our sample application, we do not do anything special with this ID it would be possible, in a more complicated application, to use this custom data element for easy access to the element that generated the event. The client does not get to use this data so what it really is doesn’t matter to them. It is merely a means for the server application to reference back to the event or item.

This is the same model that you should follow for all event types that your custom control is going to support. Follow the link in the section above to see a list of all the events Microsoft provides.

Conclusion

This concludes this article. I hope it was of some use to those of you actually looking at implementing MSAA for custom controls or libraries. In my next article I will look at using MSAA from the client perspective. I intend to do while writing a replacement for the event32.exe provided by Microsoft to see the accessible events being pushed through the system.

Points of Interest

History

4/3/2007 — Initial Posting. Version 1

C#控制Windows Messenger和Windows Live Messenger窗口发送消息

前端时间做了Messenger助手,后来发现只支持Windows Messenger,不支持Windows Live Messenger,最近改了一下,用到了Active Accessibility编程,代码如下:

using

Powered by WordPress | Theme: Motion by 85ideas.