How can i select a IE tab from its handle - c#

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.

Related

Why "FindWindowEx" can't find RichTextBox component

I'm doing an auto program (C#,not C++), and I need to get a RichTextBox in a form. I have used the Spy++ to get the title and class name, but FindWindowEx always does not find RichTextBox, and GetLastError gets the word 0. And then this is a simple example.
IntPtr parent = FindWindow(null, "Form1");
if (parent!=IntPtr.Zero) {
//find test1 textbox
IntPtr child = FindWindowEx(parent, 0,null, "test1");
if (child!=IntPtr.Zero) {
SendMessage(child, 0x000c, 0, lParam: "test");
} else {
Console.WriteLine("textbox can't be found");
}
//find test2 richtextbox
IntPtr childRich = FindWindowEx(parent, 0, null, "test2");
if (childRich != IntPtr.Zero) {
SendMessage(child, 0x000c, 0, lParam: "test");
} else {
Console.WriteLine("richtextbox can't be found");
}
} else {
Console.WriteLine("Form1 can't be found");
}
But result is richtextbox can't find. Help me.
I don't really think this is the best approach but it's something.
For this specific case you can search for all the handlers in the Form and then change the one you want.
var iHandle = Win32.FindWindow(null, "Form1");
var allItems = Win32.GetAllChildrenWindowHandles((IntPtr)iHandle, int.MaxValue);
Win32.SendMessage(allItems[1], 0x000c, 0, lParam: "Now you can change the text!");
I've tested and the allItems[1] will always be the same item, I think It's the way the items are ordered in the winForm top to bottom.
I'm using a second class for the Win Methods:
public class Win32
{
public const int WM_SETTEXT = 0X000C;
public static List<IntPtr> GetAllChildrenWindowHandles(IntPtr hParent, int maxCount)
{
var result = new List<IntPtr>();
int ct = 0;
IntPtr prevChild = IntPtr.Zero;
IntPtr currChild = IntPtr.Zero;
while (true && ct < maxCount)
{
currChild = FindWindowEx(hParent, prevChild, null, null);
if (currChild == IntPtr.Zero) break;
result.Add(currChild);
prevChild = currChild;
++ct;
}
return result;
}
[DllImport("user32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll")]
public static extern int FindWindow(string strClassName, string strWindowName);
}
Edit: Method to get all children windows handles taken from: https://jamesmccaffrey.wordpress.com/2013/02/03/getting-all-child-window-handles-using-c-pinvoke-findwindowex/

Is there a way of finding out the MDI child window at the back of the z-order? [duplicate]

I'm making an application where I interact with each running application. Right now, I need a way of getting the window's z-order. For instance, if Firefox and notepad are running, I need to know which one is in front.
Any ideas? Besides doing this for each application's main window I also need to do it for its child and sister windows (windows belonging to the same process).
You can use the GetTopWindow function to search all child windows of a parent window and return a handle to the child window that is highest in z-order. The GetNextWindow function retrieves a handle to the next or previous window in z-order.
GetTopWindow: http://msdn.microsoft.com/en-us/library/ms633514(VS.85).aspx
GetNextWindow: http://msdn.microsoft.com/en-us/library/ms633509(VS.85).aspx
Nice and terse:
int GetZOrder(IntPtr hWnd)
{
var z = 0;
for (var h = hWnd; h != IntPtr.Zero; h = GetWindow(h, GW.HWNDPREV)) z++;
return z;
}
If you need more reliability:
/// <summary>
/// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1.
/// </summary>
int[] GetZOrder(params IntPtr[] hWnds)
{
var z = new int[hWnds.Length];
for (var i = 0; i < hWnds.Length; i++) z[i] = -1;
var index = 0;
var numRemaining = hWnds.Length;
EnumWindows((wnd, param) =>
{
var searchIndex = Array.IndexOf(hWnds, wnd);
if (searchIndex != -1)
{
z[searchIndex] = index;
numRemaining--;
if (numRemaining == 0) return false;
}
index++;
return true;
}, IntPtr.Zero);
return z;
}
(According to the Remarks section on GetWindow, EnumChildWindows is safer than calling GetWindow in a loop because your GetWindow loop is not atomic to outside changes. According to the Parameters section for EnumChildWindows, calling with a null parent is equivalent to EnumWindows.)
Then instead of a separate call to EnumWindows for each window, which would also be not be atomic and safe from concurrent changes, you send each window you want to compare in a params array so their z-orders can all be retrieved at the same time.
Here is my C# solution:
The function returns the zIndex among the siblings of the given HWND, starting at 0 for the lowest zOrder.
using System;
using System.Runtime.InteropServices;
namespace Win32
{
public static class HwndHelper
{
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
public static bool GetWindowZOrder(IntPtr hwnd, out int zOrder)
{
const uint GW_HWNDPREV = 3;
const uint GW_HWNDLAST = 1;
var lowestHwnd = GetWindow(hwnd, GW_HWNDLAST);
var z = 0;
var hwndTmp = lowestHwnd;
while (hwndTmp != IntPtr.Zero)
{
if (hwnd == hwndTmp)
{
zOrder = z;
return true;
}
hwndTmp = GetWindow(hwndTmp, GW_HWNDPREV);
z++;
}
zOrder = int.MinValue;
return false;
}
}
}
// Find z-order for window.
Process[] procs = Process.GetProcessesByName("notepad");
Process top = null;
int topz = int.MaxValue;
foreach (Process p in procs)
{
IntPtr handle = p.MainWindowHandle;
int z = 0;
do
{
z++;
handle = GetWindow(handle, 3);
} while(handle != IntPtr.Zero);
if (z < topz)
{
top = p;
topz = z;
}
}
if(top != null)
Debug.WriteLine(top.MainWindowTitle);
For get Z-Order implement this function (by using GetWindow Windows API function)
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
static int GetWindowZOrder(IntPtr hWnd)
{
var zOrder = -1;
while ((hWnd = GetWindow(hWnd, 2 /* GW_HWNDNEXT */)) != IntPtr.Zero) zOrder++;
return zOrder;
}
The return value is a zero-based index. Returning -1 indicates an invalid hWnd.
The approach is to go up through the windows to the top. The total number of climbs is the value of the Z-Order.

Retrieving name and/or text from a WinForms control using the WinAPI (CE)

I am trying to traverse the controls of a WinForms form (Windows CE) and retrieve/match their textual contents and names (that is, the Name property assigned to the WinForms Control object).
I do this from within a different process, thus WinAPI.
For traversing and for retrieving the text I wrote the following code. TextPresent uses DescendantControls for the traversing and Text to retrieve the text. I am fairly confident that the traversing code works correctly already - if not, please let me know. However, all of the controls that are returned have an empty text.
The code can be used as follows:
var foregroundWindow = new WinCEControl(GetForegroundWindow()); // working properly
Assert.That("The Window Title", foregroundWindow.Text); // working properly
Assert.That(manorMenu.TextPresent("Text to find")); // not working although /
// "Text to find" is a value within the control tree of the window.
As for the name, I do not even know how to retrieve it at all. So far my searching was unsuccessful.
public class WinCEControl
{
[DllImport("coredll.dll", SetLastError = true)]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("coredll.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern bool SendMessage(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);
private const int WM_GETTEXT = 0x000D;
private const uint GW_CHILD = 5;
private const uint GW_HWNDLAST = 1;
private const uint GW_HWNDNEXT = 2;
public WinCEControl(IntPtr handle)
{
this.Handle = handle;
}
public IntPtr Handle { get; private set; }
public string Text
{
get
{
var stringBuilder = new StringBuilder(256);
SendMessage(this.Handle, WM_GETTEXT, stringBuilder.Capacity, stringBuilder);
return stringBuilder.ToString();
}
}
public IEnumerable<WinCEControl> Children()
{
var childHandle = GetWindow(Handle, GW_CHILD);
if (childHandle == IntPtr.Zero)
{
yield break;
}
var lastHandle = GetWindow(childHandle, GW_HWNDLAST);
while (childHandle != lastHandle)
{
yield return new WinCEWindow(childHandle);
childHandle = GetWindow(childHandle, GW_HWNDNEXT);
}
yield return new WinCEWindow(lastHandle);
}
public IEnumerable<WinCEControl> DescendantControls()
{
var nodes = new Queue<WinCEControl>(new[] { this });
while (nodes.Any())
{
var node = nodes.Dequeue();
if (node != this)
{
yield return node;
}
foreach (var n in node.Children()) nodes.Enqueue(n);
}
}
public bool TextPresent(string text)
{
foreach (var descendantControl in DescendantControls())
{
if (descendantControl.Text == text)
{
return true;
}
}
return false;
}
}
My goal is to be able to perform UI tests against a running application on the CE device. If there is a solid C# library to achieve the same which works on Windows CE I am very hear.
Update: As mentioned in my comment I have failed to find a library or framework for automating foreign WinForms processes on the Win CE. I tried the "simple-wince-gui-automation" and it fails to retrieve the texts, too.

EnumWindows doesn't work properly

I'm trying to take process names as a string from a listBox in a for loop and search for all windows of those applications. When I add items manually to the listBox, it works fine; but when I use an embedded text file to store and load process names to the listBox, it searches for all items but finds only the last one. For the other ones, Process.GetProcessesByName() throws an exception: Sequence contains no elements.
[DllImport("user32.dll")]
static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
static IEnumerable<IntPtr> EnumerateProcessWindowHandles(int processId)
{
var handles = new List<IntPtr>();
foreach (ProcessThread thread in Process.GetProcessById(processId).Threads)
EnumThreadWindows(thread.Id, (hWnd, lParam) => { handles.Add(hWnd); return true; }, IntPtr.Zero);
return handles;
}
Searching algorithm:
public void searchForApplications()
{
for (int i = 0; i < listBox1.Items.Count; i++)
{
try
{
foreach (var handle in EnumerateProcessWindowHandles
(Process.GetProcessesByName(listBox1.Items[i].ToString()).First().Id))
{
StringBuilder message = new StringBuilder(1000);
SendMessage(handle, WM_GETTEXT, message.Capacity, message);
if (message.ToString().Length > 0)
{
addNewApplication(new Applications(message.ToString(), message.ToString(),
int.Parse(handle.ToString())));
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
Thank you.
If GetProcessesByName doesn't find any processes matching the name you passed in (check your list), then it will return an empty array and First() will throw an InvalidOperationException. You should probably use FirstOrDefault() and check for null before getting the Id:
// ...
var process = Process.GetProcessesByName(listBox1.Items[i].ToString()).FirstOrDefault();
if (process != null)
{
foreach (var handle in EnumerateProcessWindowHandles(process.Id))
{
// ...
}
}
// ...

How to get the z-order in windows?

I'm making an application where I interact with each running application. Right now, I need a way of getting the window's z-order. For instance, if Firefox and notepad are running, I need to know which one is in front.
Any ideas? Besides doing this for each application's main window I also need to do it for its child and sister windows (windows belonging to the same process).
You can use the GetTopWindow function to search all child windows of a parent window and return a handle to the child window that is highest in z-order. The GetNextWindow function retrieves a handle to the next or previous window in z-order.
GetTopWindow: http://msdn.microsoft.com/en-us/library/ms633514(VS.85).aspx
GetNextWindow: http://msdn.microsoft.com/en-us/library/ms633509(VS.85).aspx
Nice and terse:
int GetZOrder(IntPtr hWnd)
{
var z = 0;
for (var h = hWnd; h != IntPtr.Zero; h = GetWindow(h, GW.HWNDPREV)) z++;
return z;
}
If you need more reliability:
/// <summary>
/// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1.
/// </summary>
int[] GetZOrder(params IntPtr[] hWnds)
{
var z = new int[hWnds.Length];
for (var i = 0; i < hWnds.Length; i++) z[i] = -1;
var index = 0;
var numRemaining = hWnds.Length;
EnumWindows((wnd, param) =>
{
var searchIndex = Array.IndexOf(hWnds, wnd);
if (searchIndex != -1)
{
z[searchIndex] = index;
numRemaining--;
if (numRemaining == 0) return false;
}
index++;
return true;
}, IntPtr.Zero);
return z;
}
(According to the Remarks section on GetWindow, EnumChildWindows is safer than calling GetWindow in a loop because your GetWindow loop is not atomic to outside changes. According to the Parameters section for EnumChildWindows, calling with a null parent is equivalent to EnumWindows.)
Then instead of a separate call to EnumWindows for each window, which would also be not be atomic and safe from concurrent changes, you send each window you want to compare in a params array so their z-orders can all be retrieved at the same time.
Here is my C# solution:
The function returns the zIndex among the siblings of the given HWND, starting at 0 for the lowest zOrder.
using System;
using System.Runtime.InteropServices;
namespace Win32
{
public static class HwndHelper
{
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
public static bool GetWindowZOrder(IntPtr hwnd, out int zOrder)
{
const uint GW_HWNDPREV = 3;
const uint GW_HWNDLAST = 1;
var lowestHwnd = GetWindow(hwnd, GW_HWNDLAST);
var z = 0;
var hwndTmp = lowestHwnd;
while (hwndTmp != IntPtr.Zero)
{
if (hwnd == hwndTmp)
{
zOrder = z;
return true;
}
hwndTmp = GetWindow(hwndTmp, GW_HWNDPREV);
z++;
}
zOrder = int.MinValue;
return false;
}
}
}
// Find z-order for window.
Process[] procs = Process.GetProcessesByName("notepad");
Process top = null;
int topz = int.MaxValue;
foreach (Process p in procs)
{
IntPtr handle = p.MainWindowHandle;
int z = 0;
do
{
z++;
handle = GetWindow(handle, 3);
} while(handle != IntPtr.Zero);
if (z < topz)
{
top = p;
topz = z;
}
}
if(top != null)
Debug.WriteLine(top.MainWindowTitle);
For get Z-Order implement this function (by using GetWindow Windows API function)
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
static int GetWindowZOrder(IntPtr hWnd)
{
var zOrder = -1;
while ((hWnd = GetWindow(hWnd, 2 /* GW_HWNDNEXT */)) != IntPtr.Zero) zOrder++;
return zOrder;
}
The return value is a zero-based index. Returning -1 indicates an invalid hWnd.
The approach is to go up through the windows to the top. The total number of climbs is the value of the Z-Order.

Categories

Resources