|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis 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 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 PuzzleThere are two main mechanisms to be aware of when in comes to MSAA; the MSAA
event model and the The IAccessible InterfaceThe first thing to cover is going to be the 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
Below you can see a view of an MSAA DOM I retrieved by inspecting a folder view through Windows Explorer.
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 Before we dive headfirst into how the 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 Elements in the MSAA DOM that implement the Accessible DataThe
MSAA EventsAs 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
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
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 TogetherSo far we have talked about the Information Put to the TestNow 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 ApplicationWe 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 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 There is something very important to note here. We keep a
Reviewing the Proxy ObjectAt this point you may want to take a moment to review the code for the
Working With Child IDsIt is important to notice that almost every single function in the
You can see the structure of our button control is the image of the MSAA DOM of our sample program below.
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, 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. 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
Retrieving the InterfaceNow 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 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
After a MSAA event is processed or one of the Win32 API function is used on our
window a Generating MSAA EventsLast 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 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. ConclusionThis 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 InterestHistory4/3/2007 -- Initial Posting. Version 1
| ||||||||||||||||||||||