There used to be a way to get the active tab's URL from Google Chrome by using FindWindowEx in combination with a SendMessage call to get the text currently in the omnibox. A recent (?) update seems to have broken this method, since Chrome seems to be rendering everything itself now. (You can check with Spy++, AHK Window Spy or Window Detective)
To get the current URL on Firefox and Opera, you can use DDE and WWW_GetWindowInfo. This doesn't seem to be possible on Chrome (anymore?).
This question has an answer with more info about how it used to work, which is this piece of code (which, as I explained, doesn't work anymore - hAddressBox is 0):
var hAddressBox = FindWindowEx(
intPtr,
IntPtr.Zero,
"Chrome_OmniboxView",
IntPtr.Zero);
var sb = new StringBuilder(256);
SendMessage(hAddressBox, 0x000D, (IntPtr)256, sb);
temp = sb.ToString();
So my question is: Is there a new way to get the currently focused tab's URL? (Just the title is not enough)
Edit: Seems like the code in my answer here does not work anymore (though the idea of using AutomationElement does still work) for the later Chrome versions, so look through the other answers for different versions. For example, here's one for Chrome 54: https://stackoverflow.com/a/40638519/377618
The following code seems to work, (thanks to icemanind's comment) but is however resource intensive. It takes about 350ms to find elmUrlBar... a little slow.
Not to mention that we have the problem of working with multiple chrome processes running at the same time.
// there are always multiple chrome processes, so we have to loop through all of them to find the
// process with a Window Handle and an automation element of name "Address and search bar"
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome) {
// the chrome process must have a window
if (chrome.MainWindowHandle == IntPtr.Zero) {
continue;
}
// find the automation element
AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle);
AutomationElement elmUrlBar = elm.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
// if it can be found, get the value from the URL bar
if (elmUrlBar != null) {
AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns();
if (patterns.Length > 0) {
ValuePattern val = (ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0]);
Console.WriteLine("Chrome URL found: " + val.Current.Value);
}
}
}
Edit: I wasn't happy with the slow method above, so I made it faster (now 50ms) and added some URL validation to make sure we got the correct URL instead of something the user might be searching for on the web, or still being busy typing in the URL. Here's the code:
// there are always multiple chrome processes, so we have to loop through all of them to find the
// process with a Window Handle and an automation element of name "Address and search bar"
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome) {
// the chrome process must have a window
if (chrome.MainWindowHandle == IntPtr.Zero) {
continue;
}
// find the automation element
AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle);
// manually walk through the tree, searching using TreeScope.Descendants is too slow (even if it's more reliable)
AutomationElement elmUrlBar = null;
try {
// walking path found using inspect.exe (Windows SDK) for Chrome 31.0.1650.63 m (currently the latest stable)
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; } // not the right chrome.exe
// here, you can optionally check if Incognito is enabled:
//bool bIncognito = TreeWalker.RawViewWalker.GetFirstChild(TreeWalker.RawViewWalker.GetFirstChild(elm1)) != null;
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :(
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
elmUrlBar = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));
} catch {
// Chrome has probably changed something, and above walking needs to be modified. :(
// put an assertion here or something to make sure you don't miss it
continue;
}
// make sure it's valid
if (elmUrlBar == null) {
// it's not..
continue;
}
// elmUrlBar is now the URL bar element. we have to make sure that it's out of keyboard focus if we want to get a valid URL
if ((bool)elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty)) {
continue;
}
// there might not be a valid pattern to use, so we have to make sure we have one
AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns();
if (patterns.Length == 1) {
string ret = "";
try {
ret = ((ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0])).Current.Value;
} catch { }
if (ret != "") {
// must match a domain name (and possibly "https://" in front)
if (Regex.IsMatch(ret, #"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$")) {
// prepend http:// to the url, because Chrome hides it if it's not SSL
if (!ret.StartsWith("http")) {
ret = "http://" + ret;
}
Console.WriteLine("Open Chrome URL found: '" + ret + "'");
}
}
continue;
}
}
As of Chrome 54, the following code is working for me:
public static string GetActiveTabUrl()
{
Process[] procsChrome = Process.GetProcessesByName("chrome");
if (procsChrome.Length <= 0)
return null;
foreach (Process proc in procsChrome)
{
// the chrome process must have a window
if (proc.MainWindowHandle == IntPtr.Zero)
continue;
// to find the tabs we first need to locate something reliable - the 'New Tab' button
AutomationElement root = AutomationElement.FromHandle(proc.MainWindowHandle);
var SearchBar = root.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
if (SearchBar != null)
return (string)SearchBar.GetCurrentPropertyValue(ValuePatternIdentifiers.ValueProperty);
}
return null;
}
All the methods above are failing for me with Chrome V53 and above.
Here's what is working:
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome)
{
if (chrome.MainWindowHandle == IntPtr.Zero)
continue;
AutomationElement element = AutomationElement.FromHandle(chrome.MainWindowHandle);
if (element == null)
return null;
Condition conditions = new AndCondition(
new PropertyCondition(AutomationElement.ProcessIdProperty, chrome.Id),
new PropertyCondition(AutomationElement.IsControlElementProperty, true),
new PropertyCondition(AutomationElement.IsContentElementProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
AutomationElement elementx = element.FindFirst(TreeScope.Descendants, conditions);
return ((ValuePattern)elementx.GetCurrentPattern(ValuePattern.Pattern)).Current.Value as string;
}
Found it here:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/93001bf5-440b-4a3a-ad6c-478a4f618e32/how-can-i-get-urls-of-open-pages-from-chrome-and-firefox?forum=csharpgeneral
I got results for Chrome 38.0.2125.10 with the next code (the code
inside the 'try' block has to be replaced with this)
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; } // not the right chrome.exe
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1);
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.HelpTextProperty, "TopContainerView"));
var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
var elm5 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.HelpTextProperty, "LocationBarView"));
elmUrlBar = elm5.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
I took Angelo's solution and cleaned it up a bit... I have a fixation with LINQ :)
This is the main method as it were; it uses a couple of extension methods:
public IEnumerable<string> GetTabs()
{
// there are always multiple chrome processes, so we have to loop through all of them to find the
// process with a Window Handle and an automation element of name "Address and search bar"
var processes = Process.GetProcessesByName("chrome");
var automationElements = from chrome in processes
where chrome.MainWindowHandle != IntPtr.Zero
select AutomationElement.FromHandle(chrome.MainWindowHandle);
return from element in automationElements
select element.GetUrlBar()
into elmUrlBar
where elmUrlBar != null
where !((bool) elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty))
let patterns = elmUrlBar.GetSupportedPatterns()
where patterns.Length == 1
select elmUrlBar.TryGetValue(patterns)
into ret
where ret != ""
where Regex.IsMatch(ret, #"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$")
select ret.StartsWith("http") ? ret : "http://" + ret;
}
Note that the comment is misleading, as comments tend to be - it doesn't actually look at a single AutomationElement. I left it there because Angelo's code had it.
Here's the extension class:
public static class AutomationElementExtensions
{
public static AutomationElement GetUrlBar(this AutomationElement element)
{
try
{
return InternalGetUrlBar(element);
}
catch
{
// Chrome has probably changed something, and above walking needs to be modified. :(
// put an assertion here or something to make sure you don't miss it
return null;
}
}
public static string TryGetValue(this AutomationElement urlBar, AutomationPattern[] patterns)
{
try
{
return ((ValuePattern) urlBar.GetCurrentPattern(patterns[0])).Current.Value;
}
catch
{
return "";
}
}
//
private static AutomationElement InternalGetUrlBar(AutomationElement element)
{
// walking path found using inspect.exe (Windows SDK) for Chrome 29.0.1547.76 m (currently the latest stable)
var elm1 = element.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :(
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
var elm4 = elm3.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
var result = elm4.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));
return result;
}
}
I discovered this post and was able to successfully pull the URL from chrome in C# using these methods, thank you everyone!
Unfortunately with the recent Chrome 69 update, the AutomationElement tree traversal broke again.
I found this article by Microsoft: Navigate Among UI Automation Elements with TreeWalker
And used it to whip up a simple function that searches for the AutomationElement with the "edit" control type we're looking for, instead of traversing a tree heirarchy that is always changing, and from there extract the url Value from that AutomationElement.
I wrote a simple class that wraps this all up: Google-Chrome-URL-Check-C-Sharp.
The readme explains a bit on how to use it.
All in all it might just be a little more reliable, and hope some of you find it useful.
Refering to the solution of Angelo Geels, here is a patch for version 35 - the code inside the "try" block has to be replaced with this:
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; } // not the right chrome.exe
var elm2 = TreeWalker.RawViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
var elm4 = TreeWalker.RawViewWalker.GetNextSibling(elm3); // I don't know a Condition for this for finding
var elm7 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
elmUrlBar = elm7.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom));
I took it from here:
http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WinBatch/dotNet/System_CodeDom+Grab~URL~from~Chrome.txt
For me only the active chrome window has a MainWindowHandle. I got around this by looking through all windows for chrome windows, and then using those handles instead. For example:
public delegate bool Win32Callback(IntPtr hwnd, IntPtr lParam);
[DllImport("user32.dll")]
protected static extern bool EnumWindows(Win32Callback enumProc, IntPtr lParam);
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
List<IntPtr> pointers = GCHandle.FromIntPtr(pointer).Target as List<IntPtr>;
pointers.Add(handle);
return true;
}
private static List<IntPtr> GetAllWindows()
{
Win32Callback enumCallback = new Win32Callback(EnumWindow);
List<IntPtr> pointers = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(pointers);
try
{
EnumWindows(enumCallback, GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated) listHandle.Free();
}
return pointers;
}
And then to get all chrome windows:
[DllImport("User32", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr windowHandle, StringBuilder stringBuilder, int nMaxCount);
[DllImport("user32.dll", EntryPoint = "GetWindowTextLength", SetLastError = true)]
internal static extern int GetWindowTextLength(IntPtr hwnd);
private static string GetTitle(IntPtr handle)
{
int length = GetWindowTextLength(handle);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(handle, sb, sb.Capacity);
return sb.ToString();
}
and finally:
GetAllWindows()
.Select(GetTitle)
.Where(x => x.Contains("Google Chrome"))
.ToList()
.ForEach(Console.WriteLine);
Hopefully this saves someone else some time in figuring out how to actually get the handles of all the chrome windows.
For version 53.0.2785 got it working with this:
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
if (elm1 == null) { continue; } // not the right chrome.exe
var elm2 = elm1.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""))[1];
var elm3 = elm2.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""))[1];
var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "principal"));
var elm5 = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
elmUrlBar = elm5.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
Related
From below code I can get current Chrome URL but it takes time. Sometime it takes more than 2 Min and sometime it did not return any value.
Please guide me to solve this problem.
public string GetActiveTabUrl_Google_Chrome()
{
Process[] procsChrome = Process.GetProcessesByName("chrome");
if (procsChrome.Length <= 0)
return null;
foreach (Process proc in procsChrome)
{
// the chrome process must have a window
if (proc.MainWindowHandle == IntPtr.Zero)
{
continue;
}
// to find the tabs we first need to locate something reliable - the 'New Tab' button
AutomationElement root = AutomationElement.FromHandle(proc.MainWindowHandle);
var SearchBar = root.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
if (SearchBar != null)
{
return (string)SearchBar.GetCurrentPropertyValue(ValuePatternIdentifiers.ValueProperty);
}
}
}
I've googled a lot but did not get the exact solution. I've tried multiple PC but found the same problem.
Thanks
The key to reducing the time required is to use more conditions and also not to use the NameProperty, which seems to take a lot of time. Using the following code, I was able to get the URL in about 4 seconds.
static string GetURL()
{
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome)
{
if (chrome.MainWindowHandle == IntPtr.Zero)
continue;
AutomationElement root = AutomationElement.FromHandle(chrome.MainWindowHandle);
if (root == null)
return null;
Condition conditions = new AndCondition( // using multiple conditions which seems to be faster and descibe URL bar
new PropertyCondition(AutomationElement.ProcessIdProperty, chrome.Id),
new PropertyCondition(AutomationElement.IsControlElementProperty, true),
new PropertyCondition(AutomationElement.IsContentElementProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
AutomationElement urlbar = root.FindFirst(TreeScope.Descendants, conditions);
return (string)urlbar.GetCurrentPropertyValue(ValuePatternIdentifiers.ValueProperty);
}
return null;
}
Screenshot:
hi i want to get URL from browsers and for chrome i used these and the is not working getting null exception i think chrome has changed something.. getting error on elm4 == null.
using UIAutomation i searched more and all the example are not working ...
refrences:- https://stackoverflow.com/a/21799588/5096993
https://social.msdn.microsoft.com/Forums/vstudio/en-US/39bf60a8-2bdc-4aa0-96fb-08dca49cdb06/c-get-all-chrome-urls-opened?forum=csharpgeneral
else if (browser == BrowserType.Chrome)
{
//"Chrome_WidgetWin_1"
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome)
{
// the chrome process must have a window
if (chrome.MainWindowHandle == IntPtr.Zero)
{
continue;
}
//AutomationElement elm = AutomationElement.RootElement.FindFirst(TreeScope.Children,
// new PropertyCondition(AutomationElement.ClassNameProperty, "Chrome_WidgetWin_1"));
// find the automation element
AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle);
// manually walk through the tree, searching using TreeScope.Descendants is too slow (even if it's more reliable)
AutomationElement elmUrlBar = null;
try
{
// walking path found using inspect.exe (Windows SDK) for Chrome 29.0.1547.76 m (currently the latest stable)
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
var elm2 = TreeWalker.ControlViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :(
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
elmUrlBar = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
}
catch
{
// Chrome has probably changed something, and above walking needs to be modified. :(
// put an assertion here or something to make sure you don't miss it
continue;
}
// make sure it's valid
if (elmUrlBar == null)
{
// it's not..
continue;
}
// elmUrlBar is now the URL bar element. we have to make sure that it's out of keyboard focus if we want to get a valid URL
if ((bool)elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty))
{
continue;
}
// there might not be a valid pattern to use, so we have to make sure we have one
AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns();
if (patterns.Length == 1)
{
string ret = "";
try
{
ret = ((ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0])).Current.Value;
}
catch { }
if (ret != "")
{
// must match a domain name (and possibly "https://" in front)
if (Regex.IsMatch(ret, #"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$"))
{
// prepend http:// to the url, because Chrome hides it if it's not SSL
if (!ret.StartsWith("http"))
{
ret = "http://" + ret;
}
return ret;
}
}
continue;
}
}
}
this code is working for me and get URL of active tab of chrome
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome)
{
// the chrome process must have a window
if (chrome.MainWindowHandle == IntPtr.Zero)
{
continue;
}
// find the automation element
AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle);
AutomationElement elmUrlBar = elm.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
// if it can be found, get the value from the URL bar
if (elmUrlBar != null)
{
AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns();
if (patterns.Length > 0)
{
ValuePattern val = (ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0]);
Console.WriteLine("Chrome URL found: " + val.Current.Value);
listbox.Items.Add(val.Current.Value);
}
}
}
I solved the same problem recently with System.Windows.Forms.SendKeys
Compared to your result - it's 4 times faster, but doesn't work with websites which use ctrl+l hotkey (for example StackOwerflow in edit mode).
Depends on your needs :)
public void WithSendkeys()
{
AutomationElement.RootElement
.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, "Chrome_WidgetWin_1"))
.SetFocus();
SendKeys.SendWait("^l");
var elmUrlBar = AutomationElement.FocusedElement;
var valuePattern = (ValuePattern) elmUrlBar.GetCurrentPattern(ValuePattern.Pattern);
Console.WriteLine(valuePattern.Current.Value);
}
Here is the code:
AutomationElement root = AutomationElement.FromHandle(proc.MainWindowHandle);
Condition condNewTab = new PropertyCondition(AutomationElement.NameProperty, "Novo separador");
AutomationElement elmNewTab = root.FindFirst(TreeScope.Descendants, condNewTab);
TreeWalker treewalker = TreeWalker.ControlViewWalker;
// IF THROWS A ERROR HERE, LOOK AT THE AutomationElement.NameProperty ("Novo separador") - PUT THIS TEXT IN APROPRIATE LANGUAGE
AutomationElement elmTabStrip = treewalker.GetParent(elmNewTab);
Condition condTabItem = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.TabItem);
foreach (AutomationElement tabitem in elmTabStrip.FindAll(TreeScope.Children, condTabItem))
{
// HERE IS YOUR TABS!!!!
ret.Add(tabitem.Current.Name);
}
I need to get the URL from the active tab from chrome (v47.0.2526.106 m (64-bit)).
I have a method that does work greatly for Firefox / IE, but my method for Chrome is VERY slow and 60% of the time will crash chrome :
public static string GetURL(Process process, string programName, out string url)
{
string temp = null;
if (programName.Equals("chrome"))
{
AutomationElement element = AutomationElement.FromHandle(process.MainWindowHandle);
if (element != null)
{
AutomationElement edit = element.FindFirst(TreeScope.Subtree,
new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "address and search bar", PropertyConditionFlags.IgnoreCase),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit)));
temp = ((ValuePattern)edit.GetCurrentPattern(ValuePattern.Pattern)).Current.Value as string;
}
}
url = temp;
return url;
}
So, to my question:
Is there another way to do this efficiently?
I'm working on an application that scans my active processes, and after selection, passes key commands to that process. The problem I'm having is that not all my processes seem to have a MainWindowTitle. Below is the code snippit where I immediately recognized this problem:
Dictionary<int, string> procInfo = new Dictionary<int, string>();
Process[] processes = Process.GetProcesses();
foreach (Process procData in processes)
{
if (procData.MainWindowTitle.Length > 0)// || procData.ProcessName == "notepad++")
{
procInfo.Add(procData.Id, procData.ProcessName);
}
}
foreach (KeyValuePair<int, string> proc in procInfo)
{
lstProcesses.Items.Add(proc.Value + " (" + proc.Key.ToString() + ")");
}
If you look at line 5, you'll see where I was forcing Notepad++ into the list to test against my results from WinSpy++ (alternative to Spy++) and without the force it refuses to display as it's MainWindowTitle property is blank.
Without the MainWindowTitle I can't get the class name for the application:
//procID set earlier and shown to be working
Process proc = Process.GetProcessById(procID);
string winTitle = proc.MainWindowTitle;
IntPtr hWnd = proc.MainWindowHandle;
StringBuilder buffer = new StringBuilder(1024);
GetClassName(hWnd, buffer, buffer.Capacity);
string winCaption = buffer.ToString();
And thus I can't target the application to pass in key commands:
//winCaption currently blank, winTitle tested and working
IntPtr appHandler = FindWindow(winCaption, winTitle);
SetForegroundWindow(appHandler);
SendKeys.SendWait("things and junk");
I have the proper DllImports setup for my project so the problem isn't there and I haven't found an answer here or anything reliable cruising the rest of the interwebs. Am I missing something in my code, or is this just bad and should I feel bad?
I had to completely scrap the options above in favor of a P/Invoke solution: http://pinvoke.net/default.aspx/user32.EnumDesktopWindows
Taking advantage of GetWindowText, GetWindowThreadProcessID and GetClassName in a new function:
//the following was jacked from: http://pinvoke.net/default.aspx/user32.EnumDesktopWindows
var procCollection = new List<string>();
//Dictionary<string, int> procCollection = new Dictionary<string, int>();
EnumDelegate filter = delegate(IntPtr hWnd, int lParam)
{
//return window titles
StringBuilder strbTitle = new StringBuilder(255);
int nLength = GetWindowText(hWnd, strbTitle, strbTitle.Capacity + 1);
string winTitle = strbTitle.ToString();
//return thread process id
uint getID = 0;
GetWindowThreadProcessId(hWnd, ref getID);
int winID = Convert.ToInt32(getID);
//return class names
StringBuilder strbClass = new StringBuilder(255);
GetClassName(hWnd, strbClass, strbClass.Capacity+1);
string winClass = strbClass.ToString();
if (IsWindowVisible(hWnd) && string.IsNullOrEmpty(winTitle) == false)
{
procCollection.Add(winTitle+" -- "+winID+" -- "+winClass);
}
return true;
};
if (EnumDesktopWindows(IntPtr.Zero, filter, IntPtr.Zero))
{
//foreach (KeyValuePair<string, int> procInfo in procCollection)
foreach(string procData in procCollection)
{
//if (procInfo.Key != "Start" && procInfo.Key != "Program Manager")
if (procData.Contains("Start") == false && procData.Contains("Program Manager") == false)
{
lstProcesses.Items.Add(procData);
}
}
}
Allows me to get everything I need to build my list of open Windows. This doesn't give me the list of processes like WinSpy++ (Spy++), but it's exactly what I need.
I'm writing a system tray app that needs to check if an internal web based app is open.
I can check IE using the following:
SHDocVw.ShellWindows shellWindows = new SHDocVw.ShellWindows();
string filename;
bool sdOpen = false;
foreach (SHDocVw.InternetExplorer ie in shellWindows)
{
filename = Path.GetFileNameWithoutExtension(ie.FullName).ToLower();
if (filename.Equals("iexplore"))
{
string[] urlParts = (ie.LocationURL.ToString()).Split('/');
string website = urlParts[2];
if (website == "myApp:8080") { sdOpen = true; };
}
}
if (sdOpen) { Console.WriteLine("App is open"); } else { Console.WriteLine("App is not open"); };
Console.ReadKey(true);
However, some of the users using the system prefer Chrome or Firefox.
How can I do the same as above (i.e. get the urls of any open tabs in the browser) for Chrome and Firefox? (I'm not going to bother with other browsers as these are the only ones in use in our organisation.)
It's specific for every browser. That's for the major ones:
Internet Explorer - You can use SHDocVw (like you did)
Firefox - You can get the URL using DDE (source below)
Chrome - You can get the URL while enumerating all the child windows untill you get to the control with class "Chrome_OmniboxView" and then get the text using GetWindowText
Opera - You can use the same thing as Firefox, but with "opera"
Safari - There is no known method since it uses custom drawn controls
EDIT: Since 2014, Chrome has changed and you need to get the URL with Acessibility.
Code to get the URL from Firefox/Opera using DDE (which used NDDE - the only good DDE wrapper for .NET):
//
// usage: GetBrowserURL("opera") or GetBrowserURL("firefox")
//
private string GetBrowserURL(string browser) {
try {
DdeClient dde = new DdeClient(browser, "WWW_GetWindowInfo");
dde.Connect();
string url = dde.Request("URL", int.MaxValue);
string[] text = url.Split(new string[] { "\",\"" }, StringSplitOptions.RemoveEmptyEntries);
dde.Disconnect();
return text[0].Substring(1);
} catch {
return null;
}
}
Using UIAutomation - get urls for FireFox and Chrome:
else if (browser == BrowserType.Chrome)
{
//"Chrome_WidgetWin_1"
Process[] procsChrome = Process.GetProcessesByName("chrome");
foreach (Process chrome in procsChrome)
{
// the chrome process must have a window
if (chrome.MainWindowHandle == IntPtr.Zero)
{
continue;
}
//AutomationElement elm = AutomationElement.RootElement.FindFirst(TreeScope.Children,
// new PropertyCondition(AutomationElement.ClassNameProperty, "Chrome_WidgetWin_1"));
// find the automation element
AutomationElement elm = AutomationElement.FromHandle(chrome.MainWindowHandle);
// manually walk through the tree, searching using TreeScope.Descendants is too slow (even if it's more reliable)
AutomationElement elmUrlBar = null;
try
{
// walking path found using inspect.exe (Windows SDK) for Chrome 29.0.1547.76 m (currently the latest stable)
var elm1 = elm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Google Chrome"));
var elm2 = TreeWalker.ControlViewWalker.GetLastChild(elm1); // I don't know a Condition for this for finding :(
var elm3 = elm2.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, ""));
var elm4 = elm3.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar));
elmUrlBar = elm4.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
}
catch
{
// Chrome has probably changed something, and above walking needs to be modified. :(
// put an assertion here or something to make sure you don't miss it
continue;
}
// make sure it's valid
if (elmUrlBar == null)
{
// it's not..
continue;
}
// elmUrlBar is now the URL bar element. we have to make sure that it's out of keyboard focus if we want to get a valid URL
if ((bool)elmUrlBar.GetCurrentPropertyValue(AutomationElement.HasKeyboardFocusProperty))
{
continue;
}
// there might not be a valid pattern to use, so we have to make sure we have one
AutomationPattern[] patterns = elmUrlBar.GetSupportedPatterns();
if (patterns.Length == 1)
{
string ret = "";
try
{
ret = ((ValuePattern)elmUrlBar.GetCurrentPattern(patterns[0])).Current.Value;
}
catch { }
if (ret != "")
{
// must match a domain name (and possibly "https://" in front)
if (Regex.IsMatch(ret, #"^(https:\/\/)?[a-zA-Z0-9\-\.]+(\.[a-zA-Z]{2,4}).*$"))
{
// prepend http:// to the url, because Chrome hides it if it's not SSL
if (!ret.StartsWith("http"))
{
ret = "http://" + ret;
}
return ret;
}
}
continue;
}
}
}
else if (browser == BrowserType.Firefox)
{
AutomationElement root = AutomationElement.RootElement.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.ClassNameProperty, "MozillaWindowClass"));
Condition toolBar = new AndCondition(
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar),
new PropertyCondition(AutomationElement.NameProperty, "Browser tabs"));
var tool = root.FindFirst(TreeScope.Children, toolBar);
var tool2 = TreeWalker.ControlViewWalker.GetNextSibling(tool);
var children = tool2.FindAll(TreeScope.Children, Condition.TrueCondition);
foreach (AutomationElement item in children)
{
foreach (AutomationElement i in item.FindAll(TreeScope.Children, Condition.TrueCondition))
{
foreach (AutomationElement ii in i.FindAll(TreeScope.Element, Condition.TrueCondition))
{
if (ii.Current.LocalizedControlType == "edit")
{
if (!ii.Current.BoundingRectangle.X.ToString().Contains("empty"))
{
ValuePattern activeTab = ii.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
var activeUrl = activeTab.Current.Value;
return activeUrl;
}
}
}
}
}
}
Maybe this code can help something;
Thanks to BLEZ for share this code. I use this code to capture unique addresses from firefox and add them to a listbox. But I think this is not for Chrome right?
(you Should add NDde.dll to your project, to do this go to solution explorer right click to References-> add Reference->Browse-> find that DLL (http://ndde.codeplex.com/ from binary folder.))
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using NDde.Client;
namespace WindowsFormsApplication9
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
timer1.Enabled = true;
}
private string GetBrowserURL(string browser)
{
try
{
DdeClient dde = new DdeClient(browser, "WWW_GetWindowInfo");
dde.Connect();
string url = dde.Request("URL", int.MaxValue);
string[] text = url.Split(new string[] { "\",\"" }, StringSplitOptions.RemoveEmptyEntries);
dde.Disconnect();
return text[0].Substring(1);
}
catch
{
return null;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
int j=0;
for (int i = 0; i < listBox1.Items.Count; i++)
{
if (listBox1.Items[i].ToString() == GetBrowserURL("Firefox"))
{
break;
}
else
{
j++;
}
}
if (j == listBox1.Items.Count)
{
listBox1.Items.Add(GetBrowserURL("Firefox"));
}
}
}
}
Below code work pretty well with Chrome Version 58.0.3029.110:
Please add reference of UIAutomationClient and UIAutomationProvider from Assembly provided by .NET.
foreach (Process proc in procsChrome)
{
// the chrome process must have a window
if (proc.MainWindowHandle == IntPtr.Zero)
continue;
// to find the tabs we first need to locate something reliable - the 'New Tab' button
AutomationElement root = AutomationElement.FromHandle(proc.MainWindowHandle);
var SearchBar = root.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "Address and search bar"));
if (SearchBar != null)
return (string)SearchBar.GetCurrentPropertyValue(ValuePatternIdentifiers.ValueProperty);
}