I have the Internet Explorer Handle and i have the tab Handle that i want to select.
How can i select this tab?
I know how to select a tab by Index (Using IEAccessible), but i can't get the tabIndex from the handle. The Spy++ doesn't sort them in order.
There's a way hack way to do it. Not supported, and dependent upon the version of IE as well as the language of the OS.
In an IE instance, the control with the name "Tab Row" is the
thing that holds all the various tabs. Given an IAccessible
corresponding to that thing, it's easy to enumerate the children,
and get the tabs. An automation app can inspect the URL on each
tab, and then call IAccessible.accDoDefaultAction(0)
, to activate
a tab by URL. That's the basic approach.
But I couldn't figure how to directly get the "Tab Row" control
of an IE instance, from a SHDocVw.WebBrowser
COM object, which is
what an app gets out of SHDocVw.ShellWindowsClass
. I tried it a
million ways, and finally the simplest way I could find to get it
to work is this: get the WebBrowser COM object, get the HWND from that
object (which is really the HWND numerous levels "up"), then traverse down the OS HWND hierarchy to find the HWND
with the name "DirectUIHWND". From there, walk down the IAccessible
tree, to find the "Tab Row", then select the tab and invoke the
method.
It sounds ugly to describe, and it hurt to code it this way. I could not figure out a way to do the traversal only in HWNDs or only in IAccessible. I have no idea why I needed to do both.
In Code
First, get the HWND of the WebBrowser:
SHDocVw.WebBrowser ietab = ... ?
string urlOfTabToActivate = ietab.LocationURL;
IntPtr hwnd = (IntPtr) ietab.HWND;
Then get the HWND of the DirectUI thing in the IE instance that controls that WebBrowser:
var directUi = GetDirectUIHWND(hwnd);
This is the hacky part. The GetDirectUIHWND
method is defined like this:
private static IntPtr GetDirectUIHWND(IntPtr ieFrame)
{
// try IE 9 first:
IntPtr intptr = FindWindowEx(ieFrame, IntPtr.Zero, "WorkerW", null);
if (intptr == IntPtr.Zero)
{
// IE8 and IE7
intptr = FindWindowEx(ieFrame, IntPtr.Zero, "CommandBarClass", null);
}
intptr = FindWindowEx(intptr, IntPtr.Zero, "ReBarWindow32", null);
intptr = FindWindowEx(intptr, IntPtr.Zero, "TabBandClass", null);
intptr = FindWindowEx(intptr, IntPtr.Zero, "DirectUIHWND", null);
return intptr;
}
...where FindWindowEx is a dllimport:
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr hwndParent,
IntPtr hwndChildAfter,
string lpszClass,
string lpszWindow);
Then, get the IAccessible for that DirectUI thing:
var iacc = AccessibleObjectFromWindow(directUi);
...where, of course, AccessibleObjectFromWindow is a DllImport, with some magic around it:
[DllImport("oleacc.dll")]
internal static extern int AccessibleObjectFromWindow
(IntPtr hwnd, uint id, ref Guid iid,
[In, Out, MarshalAs(UnmanagedType.IUnknown)] ref object ppvObject);
private static IAccessible AccessibleObjectFromWindow(IntPtr hwnd)
{
Guid guid = new Guid("{618736e0-3c3d-11cf-810c-00aa00389b71}"); // IAccessible
object obj = null;
uint id = 0U;
int num = AccessibleObjectFromWindow(hwnd, id, ref guid, ref obj);
var acc = obj as IAccessible;
return acc;
}
Ok, then, you walk down the IAccessible tree, to find the "Tab Row". In IE this is the thing that displays all the tabs for the various browser windows.
var tabRow = FindAccessibleDescendant(iacc, "Tab Row");
...where that method is:
private static IAccessible FindAccessibleDescendant(IAccessible parent, String strName)
{
int c = parent.accChildCount;
if (c == 0)
return null;
var children = AccChildren(parent);
foreach (var child in children)
{
if (child == null) continue;
if (strName.Equals(child.get_accName(0)))
return child;
var x = FindAccessibleDescendant(child, strName);
if (x!=null) return x;
}
return null;
}
}
And...
private static List<IAccessible> AccChildren(IAccessible accessible)
{
object[] res = GetAccessibleChildren(accessible);
var list = new List<IAccessible>();
if (res == null) return list;
foreach (object obj in res)
{
IAccessible acc = obj as IAccessible;
if (acc != null) list.Add(acc);
}
return list;
}
Then, get the IAccessible within THAT, and click it:
var tabs = AccChildren(tabRow);
int tc = tabs.Count;
int k = 0;
// walk through the tabs and tick the chosen one
foreach (var candidateTab in tabs)
{
k++;
// the last tab is "New Tab", which we don't want
if (k == tc) continue;
// the URL on *this* tab
string localUrl = UrlForTab(candidateTab);
// same? if so, tick it. This selects the given tab among all
// the others, if any.
if (urlOfTabToActivate != null
&& localUrl.Equals(urlOfTabToActivate))
{
candidateTab.accDoDefaultAction(0);
return;
}
}
....where UrlForTab
is....
private static string UrlForTab(IAccessible tab)
{
try
{
var desc = tab.get_accDescription(0);
if (desc != null)
{
if (desc.Contains("\n"))
{
string url = desc.Substring(desc.IndexOf("\n")).Trim();
return url;
}
else
{
return desc;
}
}
}
catch { }
return "??";
}
Whew. I could not find a cleaner, simpler way to do this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With