Navigating the Windows UI
Yesterday I talked a bit about the two different UI hierarchies: Win32 HWNDs and MSAA. I also pointed to tools that let you browse through these two UI hierarchies. So, you may ask, what's the difference between the two UI hierarchies and why would I want to use one versus the other?
You can think of the Win32 hierarchy as a subset of the MSAA hierarchy. The Win32 hierarchy has only one type of objects: Windows windows. The MSAA hierarchy has those but others types of objects as well.
Here's an experiment:
Open Notepad. Then Open Spy++ and find the instance of Notepad you just opened up in the Spy++ Window list. How many windows make up Notepad? 3.
Now open AccExplorer (from the MSAA tools that I pointed to in yesterday's post). Select Notepad and look at its MSAA hierarchy. How many MSAA objects make up notepad? 119. Also notice that in MSAA-land there aren't just windows. There's menu bars, menu items, title bars, scroll bars, push buttons, and, yes, windows. The windows in MSAA-land correspond to the window objects in Win32-land. If you sift through the 119 MSAA objects shown in AccExplorer you'll find that 3 of them are "window"s.
But if everything in the Win32 world is also present in the MSAA world, why not just use MSAA for everything? Two reasons: Simplicity and Speed. Here's another experiment: Go to AccExplorer and from the Options menu select "Use All Windows For Hierarchy". Oh, I forgot to mention, don't do this unless you have some time on your hands. I have about 5 Apps open in Windows and AccExplorer has been chugging away for 4 minutes now. In contrast, if you bring up Spy++ you get a list of all windows in the system almost instantaneously.
The APIs for navigating Win32 windows are also a lot easier to use than the MSAA methods. In Win32 there are two types of windows: top-level windows that are parented by the desktop and all other windows. In addition, there is an API, EnumChildWindows, that lets you search the entire ancestry tree of a given window with one call. MSAA only lets you search for immediate children so you have to implement your own search methods if you want to find ancestors that are not immediate children. (By the way, it's 10 minutes now and AccExplorer is still churning away....)
My Navigation Approach
My approach, then, is to use the Win32 APIs to navigate to the window closest to my target MSAA object and then switch over to MSAA for the final leg of the journey (there are MSAA APIs for converting a Win32 HWND to an MSAA IAccessible pointer and vice versa). It's kind of like taking a long trip in a car. You take the interstate to get to the general vicinity of where you want to go and then take normal streets the rest of the way.
Here's the class declarations of my component so far (implementation of methods (i.e., the contents of the .cpp file) omitted for brevity): namespace aaf { public __value enum ObjectRole // MSAA object role constants { None = 0, // other role values omitted for brevity... };
struct EnumInfo // for enumerating Win32 windows { CStringW wndCaption; CStringW wndClass; HWND hwndFound;
static BOOL CALLBACK EnumProc(HWND hwnd,LPARAM lparam); };
public __gc class GenericObject // the base for all objects { protected: IntPtr m_pAcc; // all objects are defined by their IAccessible & Child ID int m_pChildId; public: GenericObject() : m_pAcc(NULL), m_pChildId(CHILDID_SELF) {} ~GenericObject() { if (NULL != m_pAcc) // release the IAccessible if needed { static_cast<IAccessible*>(m_pAcc.ToPointer())->Release(); } }
__property String* get_Name(); // all generic AA members __property ObjectRole get_Role(); };
public __gc class Navigator : public GenericObject // object for navigating the UI hierarchy { public: Navigator() {} ~Navigator() {}
bool StartNavigation(String* wndCaption, String* wndClass); bool NavigateToWindow(String* wndCaption, String* wndClass); bool NavigateToChildObject(ObjectRole Role); }; } // namespace aaf
With only this much implemented I can navigate to different windows using a combination of Win32 and MSAA. Here's a test I wrote to prove that my approach works: // aaf_tester.cpp : Defines the entry point for the application. // #include "stdafx.h" #using <mscorlib.dll> #using <system.windows.forms.dll> #using <aaf.dll>
#undef MessageBox // ugh - the framework class library conflicts with Win32...
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) { aaf::Navigator* pNavigator = new aaf::Navigator(); if (!pNavigator) { System::Windows::Forms::MessageBox::Show(S"Couldn't create component"); } if (pNavigator->StartNavigation(S"",S"wndclass_desked_gsk") && // Devenv top-level window pNavigator->NavigateToWindow(S"Contents",S"GenericPane") && // The Contents window pane pNavigator->NavigateToWindow(S"",S"SysTreeView32") && // The Contents tree control pNavigator->NavigateToChildObject(aaf::ObjectRole::Outline)) // The MSAA outline (treeview) object { System::Windows::Forms::MessageBox::Show(S"Contents Outline Found"); } else { System::Windows::Forms::MessageBox::Show(S"Didn't find Contents Outline"); } return 0; }
This code attempts to find the Help Contents window in Visual Studio .NET. Actually, it does find it if it's present.
Next up: UI actions...
11:21:43 PM
|
|