47

曾经有一种方法可以从谷歌浏览器获取活动标签的 URL,方法FindWindowEx是结合SendMessage调用来获取当前在多功能框中的文本。最近的(?)更新似乎打破了这种方法,因为 Chrome 似乎现在正在渲染所有内容。(您可以使用 Spy++、AHK Window Spy 或 Window Detective 进行检查)

要在 Firefox 和 Opera 上获取当前 URL,您可以使用 DDE 和WWW_GetWindowInfo. 这在 Chrome 上似乎是不可能的(不再?)。

这个问题有一个答案,其中包含有关它过去如何工作的更多信息,即这段代码(正如我所解释的,它不再起作用 -hAddressBox0):

var hAddressBox = FindWindowEx(
    intPtr,
    IntPtr.Zero,
    "Chrome_OmniboxView",
    IntPtr.Zero);

var sb = new StringBuilder(256);
SendMessage(hAddressBox, 0x000D, (IntPtr)256, sb);
temp = sb.ToString();

所以我的问题是:有没有一种的方法来获取当前焦点选项卡的 URL?(只是标题是不够的)

4

9 回答 9

43

编辑:似乎我的答案中的代码对于更高版本的 Chrome 版本不再有效(尽管使用的想法AutomationElement仍然有效),因此请查看不同版本的其他答案。例如,这是 Chrome 54 的一个:https ://stackoverflow.com/a/40638519/377618

以下代码似乎有效(感谢icemanind 的评论),但资源密集。找到 elmUrlBar 大约需要 350 毫秒……有点慢。

更不用说我们遇到chrome了同时运行多个进程的问题。

// 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);
    }
  }
}

编辑:我对上面的慢速方法不满意,所以我让它更快(现在是 50 毫秒)并添加了一些 URL 验证,以确保我们得到正确的 URL,而不是用户可能在网络上搜索的东西,或者仍然正忙于输入 URL。这是代码:

// 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;
  }
}
于 2013-09-24T13:56:52.467 回答
12

从 Chrome 54 开始,以下代码适用于我:

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;
}
于 2016-11-16T17:28:23.537 回答
10

Chrome V53 及更高版本对我来说上述所有方法都失败了。

这是有效的:

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;
}

在这里找到它:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/93001bf5-440b-4a3a-ad6c-478a4f618e32/how-can-i-get-urls-of-open-pages-from-chrome-和-firefox?forum=csharpgeneral

于 2016-09-12T06:29:56.113 回答
7

我用下一个代码得到了 Chrome 38.0.2125.10 的结果(“try”块内的代码必须用这个替换)

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));
于 2014-10-23T02:24:07.757 回答
5

我采用了 Angelo 的解决方案并对其进行了一些清理......我对 LINQ 很感兴趣 :)

这是主要的方法;它使用了几种扩展方法:

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;
}

请注意,评论具有误导性,因为评论往往是 - 它实际上并不查看单个 AutomationElement。我把它留在那里是因为 Angelo 的代码有它。

这是扩展类:

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;
  }
}
于 2013-11-28T21:00:10.313 回答
5

我发现了这篇文章,并能够使用这些方法成功地从 C# 中的 chrome 中提取 URL,谢谢大家!

不幸的是,随着最近的Chrome 69更新,AutomationElement树遍历再次中断。

我找到了 Microsoft 的这篇文章:使用 TreeWalker 在 UI 自动化元素中导航

并用它来创建一个简单的函数来搜索我们正在寻找AutomationElement的控件类型,而不是遍历一个总是在变化的树层次结构,然后从中提取 url 值。"edit"AutomationElement

我写了一个简单的类来包装这一切:Google-Chrome-URL-Check-C-Sharp

自述文件解释了如何使用它。

总而言之,它可能只是更可靠一点,希望你们中的一些人觉得它有用。

于 2018-10-25T18:34:05.250 回答
4

参考 Angelo Geels 的解决方案,这里是版本 35 的补丁 - “try”块内的代码必须替换为:

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));  

我从这里拿了它: http ://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WinBatch/dotNet/System_CodeDom+Grab~URL~from~Chrome.txt

于 2014-09-13T20:40:50.107 回答
4

对我来说,只有活动的 chrome 窗口有一个 MainWindowHandle。我通过查看所有窗口的 chrome 窗口,然后使用这些句柄来解决这个问题。例如:

    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;
    }

然后获取所有 chrome 窗口:

    [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();
    }

最后:

GetAllWindows()
    .Select(GetTitle)
    .Where(x => x.Contains("Google Chrome"))
    .ToList()
    .ForEach(Console.WriteLine);

希望这可以节省其他人一些时间来弄清楚如何实际获取所有 chrome 窗口的句柄。

于 2015-06-03T18:42:18.063 回答
1

对于 53.0.2785 版本,它可以使用:

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));
于 2016-09-28T09:48:28.570 回答