Extended FindWindow

The Win32 API provides the FindWindow function that supports finding top-level windows by their class name and/or title. However, the title search does not work if you are trying to match partial text at the middle or the end of the full window title.

You can however implement support for these extended search features by using another set of Win32 API like EnumWindows and GetWindowText. A possible implementation follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

public class WindowInfo
{
    private IntPtr handle;

    private string className;

    internal WindowInfo(IntPtr handle, string title)
    {
        if (handle == IntPtr.Zero)
            throw new ArgumentException("Invalid handle.", "handle");

        this.Handle = handle;
        this.Title = title ?? string.Empty;
    }

    public string Title { get; private set; }

    public string ClassName
    {
        get
        {
            if (className == null)
            {
                className = GetWindowClassNameByHandle(this.Handle);
            }

            return className;
        }
    }

    public IntPtr Handle
    {
        get
        {
            if (!NativeMethods.IsWindow(this.handle))
                throw new InvalidOperationException("The handle is no longer valid.");

            return this.handle;
        }
        private set { this.handle = value; }
    }

    public static WindowInfo[] EnumerateWindows()
    {
        var windows = new List<WindowInfo>();

        NativeMethods.EnumWindowsProcessor processor = (hwnd, lParam) =>
        {
            windows.Add(new WindowInfo(hwnd, GetWindowTextByHandle(hwnd)));

            return true;
        };

        bool succeeded = NativeMethods.EnumWindows(processor, IntPtr.Zero);

        if (!succeeded)
            return new WindowInfo[] { };

        return windows.ToArray();
    }

    public static WindowInfo FindWindow(Predicate<WindowInfo> predicate)
    {
        WindowInfo target = null;

        NativeMethods.EnumWindowsProcessor processor = (hwnd, lParam) =>
        {
            var current = new WindowInfo(hwnd, GetWindowTextByHandle(hwnd));

            if (predicate(current))
            {
                target = current;

                return false;
            }

            return true;
        };

        NativeMethods.EnumWindows(processor, IntPtr.Zero);

        return target;
    }

    private static string GetWindowTextByHandle(IntPtr handle)
    {
        if (handle == IntPtr.Zero)
            throw new ArgumentException("Invalid handle.", "handle");

        int length = NativeMethods.GetWindowTextLength(handle);

        if (length == 0)
            return string.Empty;

        var buffer = new StringBuilder(length + 1);

        NativeMethods.GetWindowText(handle, buffer, buffer.Capacity);

        return buffer.ToString();
    }

    private static string GetWindowClassNameByHandle(IntPtr handle)
    {
        if (handle == IntPtr.Zero)
            throw new ArgumentException("Invalid handle.", "handle");

        const int WindowClassNameMaxLength = 256;

        var buffer = new StringBuilder(WindowClassNameMaxLength);

        NativeMethods.GetClassName(handle, buffer, buffer.Capacity);

        return buffer.ToString();
    }
}

internal class NativeMethods
{
    public delegate bool EnumWindowsProcessor(IntPtr hwnd, IntPtr lParam);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EnumWindows(
        EnumWindowsProcessor lpEnumFunc,
        IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern int GetWindowText(
        IntPtr hWnd,
        StringBuilder lpString,
        int nMaxCount);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern int GetClassName(
        IntPtr hWnd,
        StringBuilder lpClassName,
        int nMaxCount);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool IsWindow(IntPtr hWnd);
}

The access to the windows handle is preceded by a sanity check to assert if it’s still valid, but if you are dealing with windows out of your control then the window can be destroyed right after the check so it’s not guaranteed that you’ll get a valid handle.

Finally, to wrap this up a usage, example:

static void Main(string[] args)
{
    var w = WindowInfo.FindWindow(wi => wi.Title.Contains("Test.docx"));

    if (w != null)
    {
        Console.Write(w.Title);
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s