Extended FindWindow

Posted by João Angelo on Exceptional Code See other posts from Exceptional Code or by João Angelo
Published on Sun, 22 May 2011 19:21:41 +0000 Indexed on 2011/06/20 16:37 UTC
Read the original article Hit count: 303

Filed under:
|

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

© Exceptional Code or respective owner

Related posts about .NET

Related posts about c#