使用 C# 从 Google Chrome 获取当前标签的 URL
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18897070/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Getting the current tab's URL from Google Chrome using C#
提问by Codecat
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)
曾经有一种方法可以通过FindWindowEx
结合SendMessage
调用来从 Google Chrome 获取活动标签的 URL,以获取当前在多功能框中的文本。最近的 (?) 更新似乎破坏了这种方法,因为 Chrome 现在似乎正在渲染所有内容。(您可以使用 Spy++、AHK Window Spy 或 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?).
要在 Firefox 和 Opera 上获取当前 URL,您可以使用 DDE 和WWW_GetWindowInfo
. 这在 Chrome 上似乎是不可能的(不再?)。
This questionhas 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
):
这个问题有一个答案,其中包含有关它过去如何工作的更多信息,这是一段代码(正如我所解释的,它不再起作用 -hAddressBox
是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 newway to get the currently focused tab's URL? (Just the title is not enough)
所以我的问题是:有没有一种新方法可以获取当前聚焦的选项卡的 URL?(光是标题是不够的)
采纳答案by Codecat
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
编辑:似乎我在这里的答案中的代码不再AutomationElement
适用于更高版本的 Chrome 版本(尽管使用的想法仍然有效),因此请查看不同版本的其他答案。例如,这是 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.
下面的代码似乎工作,(感谢 icemanind 的评论)但资源密集。找到 elmUrlBar 大约需要 350 毫秒……有点慢。
Not to mention that we have the problem of working with multiple chrome
processes running at the same time.
更不用说我们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);
}
}
}
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:
编辑:我对上面的慢速方法不满意,所以我让它更快(现在是 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;
}
}
回答by Marcel Popescu
I took Angelo's solution and cleaned it up a bit... I have a fixation with LINQ :)
我采用了 Angelo 的解决方案并将其清理了一下......我对 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.
请注意,注释具有误导性,因为注释往往具有误导性 - 它实际上并不查看单个 AutomationElement。我把它留在那里是因为安吉洛的代码有它。
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;
}
}
回答by Michal Czardybon
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:
参考 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));
I took it from here: http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WinBatch/dotNet/System_CodeDom+Grab~URL~from~Chrome.txt
我从这里拿了它:http: //techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/nftechsupt.web+WinBatch/dotNet/System_CodeDom+Grab~URL~from~ Chrome.txt
回答by Paul3k
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)
我用下一个代码得到了 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));
回答by yeerk
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:
对我来说,只有活动的 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;
}
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.
希望这可以为其他人节省一些时间来弄清楚如何实际获取所有 chrome 窗口的句柄。
回答by Randall Deetz
All the methods above are failing for me with Chrome V53 and above.
对于 Chrome V53 及更高版本,上述所有方法对我来说都失败了。
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:
在这里找到它:
回答by Yind
For version 53.0.2785 got it working with this:
对于 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));
回答by dotNET
As of Chrome 54, the following code is working for me:
从 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;
}
回答by progietheus
I discovered this post and was able to successfully pull the URL from chrome in C# using these methods, thank you everyone!
我发现了这篇文章,并且能够使用这些方法成功地从 C# 中的 chrome 中提取 URL,谢谢大家!
Unfortunatelywith the recent Chrome 69update, the AutomationElement
tree traversal broke again.
不幸的是,随着最近的Chrome 69更新,AutomationElement
树遍历再次中断。
I found this article by Microsoft: Navigate Among UI Automation Elements with TreeWalker
我找到了 Microsoft 的这篇文章:使用 TreeWalker 在 UI 自动化元素中导航
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
.
并使用它来创建一个简单的函数,该函数搜索AutomationElement
具有"edit"
我们正在寻找的控件类型的 ,而不是遍历始终在变化的树层次结构,并从中提取 url 值AutomationElement
。
I wrote a simple class that wraps this all up: Google-Chrome-URL-Check-C-Sharp.
我写了一个简单的类来包装这一切: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.
总而言之,它可能更可靠一点,希望你们中的一些人觉得它有用。