5

Let's say I have multiple chrome windows open (not tabs),
how can I check the browser title?

I tried the following:

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

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

but it return me only the last open window name and all other are blanks..

Dor Cohen
  • 16,769
  • 23
  • 93
  • 161

4 Answers4

7

I had to do something like this, but it was amazingly fiddly involving calling Windows API functions. The problem was that Chrome seems to use a single process for multiple windows or some other weirdness that meant the simple approach didn't work for me.

Anyway, try this and see if it works. Basically it uses the Chrome window class name (which might be Chrome_WidgetWin_0 or Chrome_WidgetWin_1) to enumerate all windows with that class name, and returns the window titles for those which are not blank.

Note that this also always returns a windows title called "Chrome App Launcher" for some reason, so you might need to filter that out.

Note: you can also do this for Firefox by using "MozillaWindowClass" and for IE by using "IEFrame" (although any of those are likely to change with different versions).

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);
        }
    }
}
Dor Cohen
  • 16,769
  • 23
  • 93
  • 161
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 1
    I don't why but this code isn't working for me, the following line: *GetWindowText(windowHandle, sb, 8192); * returns me the following error: *The runtime has encountered a fatal error. The address of the error was at 0x546d548e, on thread 0x20d0. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack. An unhandled exception of type 'System.ExecutionEngineException' occurred in Unknown Module.* – Dor Cohen Jun 06 '13 at 10:22
  • @DorCohen Hmm I think there might be some cases where you're not allowed to access the Window text. Give me a moment to update the code with some exception handling. – Matthew Watson Jun 06 '13 at 10:46
  • @DorCohen Should be fixed now. I wasn't preallocating the StringBuilder buffer size properly. Now I call `GetWindowTextLength()` first so I can pre-size it correctly. This was working on my original code, but I had to remove large chunks of it when making this sample program and that bit got lost. – Matthew Watson Jun 06 '13 at 10:51
  • Can I do the same for firefox? and can you please take a look at my other question - http://stackoverflow.com/questions/16959482/change-browser-size-using-c-sharp – Dor Cohen Jun 06 '13 at 11:25
  • @DorCohen Yep, for Firefox use `"MozillaWindowClass"` and for IE use `"IEFrame"` (although any of those are likely to change with different versions). I'm not sure if it would work via process ID because that's basically what you already tried with `Process.MainWindowTitle` – Matthew Watson Jun 06 '13 at 11:29
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31329/discussion-between-dor-cohen-and-matthew-watson) – Dor Cohen Jun 06 '13 at 11:34
7

I know this is already answered, but I also have made a solution, which enumerates all Windows within a thread.

It was built from Matthew Watson's solution, hence some similarities.

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;
        }   
    }
}
cheekibreeki
  • 113
  • 1
  • 8
0

I know this is an old thread, but I have found the answer to this, at least for my use case anyway. I wanted to find all the open chrome windows/tabs by title as well, but in my case I wanted to close the ones I found containing x Title. After reading icbytes and dor-cohen's post above I realized I could achieve what I needed by calling Process.GetProcessesByName() more than once. When making this call you do get an array of all the running chrome processes, but only one instance will contain a value for MainWindowTitle. This is a bit annoying for several reasons. You can have multiple chrome sessions open with and "active" "displayed tab", but still the call only ever returns an array of chrome proc's with just one instance in that array having an value for MainWindowTitle. Again, my solution is not necessarily the OP's intention as he states just wanting to list the titles. My solution wants to close each found title.

What I have done is as follows:

Once I find the first chrome process with the title I am looking for I call CloseMainWindow() on that process. Do not call Kill() as it will crash the browser altogether. I am just closing the active or top level window here. I am posting my code below. I hope this will help someone else! Thanks!

        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);
Mike Autry
  • 11
  • 2
-2

You must get a list of processes.

Iterate through the list and only where name is "chrome".

This will allow You to get all titles.

Because if You have more then one chrome process , Your call will give You only one, because You call it only once.

Which it returns is perhaps another question. In Your case it is the last.

icbytes
  • 1,831
  • 1
  • 17
  • 27