Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i select a IE tab from its handle

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.

like image 744
Gaby Avatar asked Oct 13 '10 14:10

Gaby


1 Answers

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.

like image 200
Cheeso Avatar answered Nov 15 '22 03:11

Cheeso