【问题标题】:Get chrome browser title using c#使用 c# 获取 chrome 浏览器标题
【发布时间】:2013-06-02 05:21:08
【问题描述】:

假设我打开了多个 chrome 窗口(不是标签),
如何查看浏览器标题?

我尝试了以下方法:

Process[] p = Process.GetProcessesByName("chrome");

foreach (Process item in p)
{
    Console.WriteLine(item.MainWindowTitle);
}

但它只返回最后一个打开的窗口名称,所有其他都是空白..

【问题讨论】:

    标签: c# c#-4.0 browser title


    【解决方案1】:

    我不得不做这样的事情,但是涉及调用 Windows API 函数的过程非常繁琐。问题是 Chrome 似乎对多个窗口使用单个进程或其他一些奇怪的东西,这意味着简单的方法对我不起作用。

    无论如何,试试这个,看看它是否有效。基本上,它使用 Chrome 窗口类名(可能是 Chrome_WidgetWin_0Chrome_WidgetWin_1)来枚举具有该类名的所有窗口,并返回非空白窗口的标题。

    请注意,由于某种原因,这也总是返回一个名为 "Chrome App Launcher" 的窗口标题,因此您可能需要将其过滤掉。

    注意:您也可以在 Firefox 上使用“MozillaWindowClass”,在 IE 上使用“IEFrame”(尽管其中任何一个都可能随不同版本而改变)。

    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Text;
    
    namespace Demo
    {
        class WindowsByClassFinder
        {
            public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam);
    
            [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
    
            [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lparam);
    
            [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
            [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);
    
    
            /// <summary>Find the windows matching the specified class name.</summary>
    
            public static IEnumerable<IntPtr> WindowsMatching(string className)
            {
                return new WindowsByClassFinder(className)._result;
            }
    
            private WindowsByClassFinder(string className)
            {
                _className = className;
                EnumWindows(callback, IntPtr.Zero);
            }
    
            private bool callback(IntPtr hWnd, IntPtr lparam)
            {
                if (GetClassName(hWnd, _apiResult, _apiResult.Capacity) != 0)
                {
                    if (string.CompareOrdinal(_apiResult.ToString(), _className) == 0)
                    {
                        _result.Add(hWnd);
                    }
                }
    
                return true; // Keep enumerating.
            }
    
            public static IEnumerable<string> WindowTitlesForClass(string className)
            {
                foreach (var windowHandle in WindowsMatchingClassName(className))
                {
                    int length = GetWindowTextLength(windowHandle);
                    StringBuilder sb = new StringBuilder(length + 1);
                    GetWindowText(windowHandle, sb, sb.Capacity);
                    yield return sb.ToString();
                }
            }
    
            public static IEnumerable<IntPtr> WindowsMatchingClassName(string className)
            {
                if (string.IsNullOrWhiteSpace(className))
                    throw new ArgumentOutOfRangeException("className", className, "className can't be null or blank.");
    
                return WindowsMatching(className);
            }
    
            private readonly string _className;
            private readonly List<IntPtr> _result = new List<IntPtr>();
            private readonly StringBuilder _apiResult = new StringBuilder(1024);
        }
    
        class Program
        {
            void run()
            {
                ChromeWindowTitles().Print();
            }
    
            public IEnumerable<string> ChromeWindowTitles()
            {
                foreach (var title in WindowsByClassFinder.WindowTitlesForClass("Chrome_WidgetWin_0"))
                    if (!string.IsNullOrWhiteSpace(title))
                        yield return title;
    
                foreach (var title in WindowsByClassFinder.WindowTitlesForClass("Chrome_WidgetWin_1"))
                    if (!string.IsNullOrWhiteSpace(title))
                        yield return title;
            }
    
            static void Main()
            {
                new Program().run();
            }
        }
    
        static class DemoUtil
        {
            public static void Print(this object self)
            {
                Console.WriteLine(self);
            }
    
            public static void Print(this string self)
            {
                Console.WriteLine(self);
            }
    
            public static void Print<T>(this IEnumerable<T> self)
            {
                foreach (var item in self)
                    Console.WriteLine(item);
            }
        }
    }
    

    【讨论】:

    • 我不知道为什么,但这段代码对我不起作用,以下行: GetWindowText(windowHandle, sb, 8192); * 向我返回以下错误:*运行时遇到致命错误。错误地址位于线程 0x20d0 上的 0x546d548e。错误代码为 0xc0000005。此错误可能是 CLR 或用户代码的不安全或不可验证部分中的错误。此错误的常见来源包括 COM 互操作或 PInvoke 的用户封送错误,这可能会损坏堆栈。未知模块中发生了“System.ExecutionEngineException”类型的未处理异常。
    • @DorCohen 嗯,我认为在某些情况下您可能不允许访问窗口文本。给我一点时间,用一些异常处理来更新代码。
    • @DorCohen 现在应该修复。我没有正确预分配 StringBuilder 缓冲区大小。现在我先打电话给GetWindowTextLength(),这样我就可以正确地调整它的大小。这是在我的原始代码上工作的,但是在制作这个示例程序时我不得不删除它的大部分内容,并且那个部分丢失了。
    • 我可以为 Firefox 做同样的事情吗?你能看看我的另一个问题 - stackoverflow.com/questions/16959482/…
    • @DorCohen 是的,对于 Firefox 使用 "MozillaWindowClass",对于 IE 使用 "IEFrame"(尽管其中任何一个都可能随着不同的版本而改变)。我不确定它是否可以通过进程 ID 工作,因为这基本上是你已经尝试过的 Process.MainWindowTitle
    【解决方案2】:

    我知道这已经得到解答,但我也提出了一个解决方案,它枚举了一个线程中的所有 Windows。

    它是根据 Matthew Watson 的解决方案构建的,因此有一些相似之处。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    
    namespace Chrome_Windows
    {
        class Program
        {
            [DllImport("user32.dll")]
            private static extern bool EnumThreadWindows(uint dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
    
            [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
    
            [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 List<IntPtr> windowList;
            private static string _className;
            private static StringBuilder apiResult = new StringBuilder(256); //256 Is max class name length.
            private delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
    
            static void Main(string[] args) 
            {
                List<IntPtr> ChromeWindows = WindowsFinder("Chrome_WidgetWin_1", "chrome");
                foreach (IntPtr windowHandle in ChromeWindows) 
                {
                    int length = GetWindowTextLength(windowHandle);
                    StringBuilder sb = new StringBuilder(length + 1);
                    GetWindowText(windowHandle, sb, sb.Capacity);
                    Console.WriteLine(sb.ToString());
                }
            }
    
            private static List<IntPtr> WindowsFinder(string className, string process)
            {
                _className = className;
                windowList = new List<IntPtr>();
    
                Process[] chromeList = Process.GetProcessesByName(process);
    
                if (chromeList.Length > 0)
                {
                    foreach (Process chrome in chromeList)
                    {
                        if (chrome.MainWindowHandle != IntPtr.Zero)
                        {
                            foreach (ProcessThread thread in chrome.Threads)
                            {
                                EnumThreadWindows((uint)thread.Id, new EnumThreadDelegate(EnumThreadCallback), IntPtr.Zero);
                            }
                        }
                    }
                }
    
                return windowList;
            }
    
            static bool EnumThreadCallback(IntPtr hWnd, IntPtr lParam)
            {
                if (GetClassName(hWnd, apiResult, apiResult.Capacity) != 0)
                {
                    if (string.CompareOrdinal(apiResult.ToString(), _className) == 0)
                    {
                        windowList.Add(hWnd);
                    }
                }
                return true;
            }   
        }
    }
    

    【讨论】:

      【解决方案3】:

      我知道这是一个旧线程,但我已经找到了答案,至少对于我的用例来说。我也想按标题查找所有打开的 chrome 窗口/标签,但在我的情况下,我想关闭我找到的包含 x Title 的那些。在阅读了上面icbytesdor-cohen 的帖子后,我意识到我可以通过多次调用 Process.GetProcessesByName() 来实现我所需要的。进行此调用时,您确实会获得所有正在运行的 chrome 进程的数组,但只有一个实例将包含 MainWindowTitle 的值。由于几个原因,这有点烦人。您可以使用“活动”“显示的选项卡”打开多个 chrome 会话,但该调用仍然只返回一个 chrome proc 数组,该数组中只有一个实例具有 MainWindowTitle 的值。同样,我的解决方案不一定是 OP 的意图,因为他只是想列出标题。我的解决方案想要关闭每个找到的标题。

      我所做的如下:

      一旦我找到第一个带有我正在寻找的标题的 chrome 进程,我就会在该进程上调用 CloseMainWindow()。不要调用 Kill() ,因为它会使浏览器完全崩溃。我只是在这里关闭活动或顶级窗口。我在下面发布我的代码。我希望这对其他人有帮助!谢谢!

              bool foundAll = false;
              do
              {
                  bool foundOne = false;
                  procs = Process.GetProcessesByName("chrome");
      
                  foreach (Process p in procs)
                  {
                      if (p.MainWindowTitle.Length > 0)
                      {
                          string t = p.MainWindowTitle.Replace(" - Google Chrome", "");
                          if (t.ToLower().Contains(this.BrowserTabText.ToLower()))
                          {
                              foundOne = true;
                              this.WriteEventLogEntry($"Found Tab Title: {this.BrowserTabText} with PID: {p.Id}.  \r\nWe will close it.", EventLogEntryType.Information);
                              p.CloseMainWindow();
                              break;
                          }
                      }
                  }
                  if (!foundOne)
                  {
                      foundAll = true;
                  }
              } while (!foundAll);
      

      【讨论】:

        【解决方案4】:

        你必须得到一个进程列表。

        遍历列表,仅在名称为“chrome”的地方。

        这将允许您获得所有头衔。

        因为如果你有多个 chrome 进程,你的调用只会给你一个,因为你只调用一次。

        返回哪个可能是另一个问题。在你的情况下,它是最后一个。

        【讨论】:

        • 这不正是 OP 正在做的事情吗? Process[] p = Process.GetProcessesByName("chrome");foreach (Process item in p)...
        • 这正是我所做的,但问题是它只返回最后一个打开的标题,其他的呢?
        • 对不起,我已经监督了。太快了。
        猜你喜欢
        • 2023-03-28
        • 2012-05-21
        • 2014-04-16
        • 1970-01-01
        • 1970-01-01
        • 2011-01-09
        • 2014-12-10
        • 1970-01-01
        相关资源
        最近更新 更多