How can I use EnumWindows to find windows with a specific caption/title?
Asked Answered
L

3

31

I am working on an application that will eventually be an api for driving UI Tests for a WPF application.

At one point of the initial test we are working on, we get 2 Windows security popups. We have some code that loops 10 times, it gets the handle of one of the popups using the FindWindowByCaption method and enters the information and clicks ok.

9 times out of 10 this works just fine, however we are occasionally seeing what looks to be a race condition. My suspicion is that the loop starts when only one of the windows is open and while its entering the information the second one opens and steals focus; after this it just hangs indefinitely.

What I'm wondering is if there is any method to get all of the window handles for a given caption, so that we can wait until there are 2 before starting the loop.

Lautrec answered 8/11, 2013 at 19:48 Comment(4)
why write your own library when tools already exist for doing something like this. Autoit and autohotkeys. They are well tested, work really well.. what else do you need?Jesselton
Unfortunately it is massively more complicated than I have explained. I actually came into this project half way through as well. Suffice it to say there is no product that we have found that covers everything we need.Lautrec
If something is stealing focus, you can use the FocusChanged event to rescan your windows.Pullulate
I have tried using UI Automation to add a focus change handler however it does not differentiate between switching between the two windows and switching between the fields on the windows.Lautrec
W
105

Original Answer

Use EnumWindows and enumerate through all the windows, using GetWindowText to get each window's text, then filter it however you want.

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int GetWindowTextLength(IntPtr hWnd);

[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);

// Delegate to filter which windows to include 
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

/// <summary> Get the text for the window pointed to by hWnd </summary>
public static string GetWindowText(IntPtr hWnd)
{
    int size = GetWindowTextLength(hWnd);
    if (size > 0)
    {
        var builder = new StringBuilder(size + 1);
        GetWindowText(hWnd, builder, builder.Capacity);
        return builder.ToString();
    }

    return String.Empty;
}

/// <summary> Find all windows that match the given filter </summary>
/// <param name="filter"> A delegate that returns true for windows
///    that should be returned and false for windows that should
///    not be returned </param>
public static IEnumerable<IntPtr> FindWindows(EnumWindowsProc filter)
{
  IntPtr found = IntPtr.Zero;
  List<IntPtr> windows = new List<IntPtr>();

  EnumWindows(delegate(IntPtr wnd, IntPtr param)
  {
      if (filter(wnd, param))
      {
          // only add the windows that pass the filter
          windows.Add(wnd);
      }

      // but return true here so that we iterate all windows
      return true;
  }, IntPtr.Zero);

  return windows;
}

/// <summary> Find all windows that contain the given title text </summary>
/// <param name="titleText"> The text that the window title must contain. </param>
public static IEnumerable<IntPtr> FindWindowsWithText(string titleText)
{
    return FindWindows(delegate(IntPtr wnd, IntPtr param)
    {
        return GetWindowText(wnd).Contains(titleText);
    });
} 

For example, to get all of the windows with "Notepad" in the title:

var windows = FindWindowsWithText("Notepad");

Win32Interop.WinHandles

This answer proved popular enough that I created an OSS project, Win32Interop.WinHandles to provide an abstraction over IntPtrs for win32 windows. Using the library, to get all of the windows that contains "Notepad" in the title:

var allNotepadWindows
   = TopLevelWindowUtils.FindWindows(wh => wh.GetWindowText().Contains("Notepad"));
Wall answered 29/11, 2013 at 0:35 Comment(5)
Great answer thanks! Is there any way to use this to get handles on Windows which aren't active? For example, if I open 2 chrome windows, and have multiple tabs on each, this only returns the Hwnd of the active window, and the inactive one is not returned at all.Lello
@Lello you may want to ask another question, but as a quick test I duplicated a tab and moved it to a separate window, and the code in the did return both tabs.Wall
@MackieChan thanks for the response. Do you get the same result if you have one chrome window open, and rather than move an tab to a new window, start a new instance of chrome and open some tabs? Does it then return all tabs on both chrome windows?Lello
@Lello it does return both windows regardless of how the second window is opened. Do note, however, that it does not return all tabs, only the open tab for each window.Wall
8 years later, this came in handy so really appreciate the work @zastrowm. Definitely don't want to necro this only adding a quick note about error checking. You can import SetLastError from kernel32.dll and call SetLastError(0) in the GetWindowText(IntPtr hWnd) function right before the int size = ... line. Following the size assignment with GetWindowTextLength(hWnd), you can check if an error was thrown using Marshal.GetLastWin32Error() and checking if (lastError != 0 && size == 0). I only post this because I was running into a whacky nullptr issue where this helped me debug.Obfuscate
S
0
using HWND = IntPtr;

/// <summary>Contains functionality to get all the open windows.</summary>
public static class OpenWindowGetter
{
/// <summary>Returns a dictionary that contains the handle and title of all the open windows.</summary>
/// <returns>A dictionary that contains the handle and title of all the open windows.</returns>
public static IDictionary<HWND, string> GetOpenWindows()
{
HWND shellWindow = GetShellWindow();
Dictionary<HWND, string> windows = new Dictionary<HWND, string>();

EnumWindows(delegate(HWND hWnd, int lParam)
{
  if (hWnd == shellWindow) return true;
  if (!IsWindowVisible(hWnd)) return true;

  int length = GetWindowTextLength(hWnd);
  if (length == 0) return true;

  StringBuilder builder = new StringBuilder(length);
  GetWindowText(hWnd, builder, length + 1);

  windows[hWnd] = builder.ToString();
  return true;

}, 0);

return windows;
}

private delegate bool EnumWindowsProc(HWND hWnd, int lParam);

[DllImport("USER32.DLL")]
private static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);

[DllImport("USER32.DLL")]
private static extern int GetWindowText(HWND hWnd, StringBuilder lpString, int nMaxCount);

[DllImport("USER32.DLL")]
private static extern int GetWindowTextLength(HWND hWnd);

[DllImport("USER32.DLL")]
private static extern bool IsWindowVisible(HWND hWnd);

[DllImport("USER32.DLL")]
private static extern IntPtr GetShellWindow();
}

And here’s some code that uses it:

foreach(KeyValuePair<IntPtr, string> window in OpenWindowGetter.GetOpenWindows())
{
IntPtr handle = window.Key;
string title = window.Value;

Console.WriteLine("{0}: {1}", handle, title);
}

I got this code from http://www.tcx.be/blog/2006/list-open-windows/

If you need help on how to use this, let me know, I figured it out

Styria answered 9/10, 2015 at 13:51 Comment(6)
Given that you can't explain how to use the borrowed code, a comment with the link would have been more appropriate. I know... you can't do that yet: Users earn the privilege of commenting by participating through questions, answers and editing activities. As for slipping in your own question as well... If you have a NEW question, please ask it by clicking the Ask Question button. Include a link to this question if it helps provide context.Vinnie
Well, i just figured out how to use it, so anyone needs to know then let me knowStyria
@Styria I think you are lacking basic understanding of the way Stack Overflow is supposed to work. We are not a social network or a forum. Please check the help center.Airtight
Stack Overflow is not a place for discussion. It is a place for Questions and Answers, nothing more and nothing less. If you have an answer to a question, or a question that needs answering, there are specific forms for those things. The only place on this site for discussion is chat.Astereognosis
Apologies. I am just new to this therefore didnt realise this at first.Styria
At least the poster provided something of value unlike the other nitpicking and faultfinding comments posted here.Litigate
J
0

I know this is an old question but it is one that answer will change over time as Visual Studio moves into the future.

I would like to share my solution which allows you to search for a partial Window Title which is often needed when the Title Caption contains unpredictable text. For example if you wanted to find the handle to the Windows Mail Application the Title will contain the text "Inbox - youremailaccountname". Obviously you don't want to hard code the account name. Here is my code although it is in Visual Basic .NET you can convert it to C#. Type in a partial title (i.e. "Inbox - "), click the button and you will get the hwnd and full title back. I tried using Process.GetProcesses() but it was way to slow compared to the Win API.

This Example will return the window handle of your search in lparm of the EnumWindows call (2nd parameter passed byref) and will bring the application to the front even if it is minimized.

Imports System.Runtime.InteropServices
Imports System.Text
Public Class Form1
    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> Private Shared Function EnumWindows(ByVal lpEnumFunc As EnumWindowsProcDelegate, ByRef lParam As IntPtr) As Boolean
    End Function
    Private Delegate Function EnumWindowsProcDelegate(ByVal hWnd As IntPtr, ByRef lParam As IntPtr) As Integer

    <DllImport("user32.dll")>
    Private Shared Function GetWindowTextLength(ByVal hWnd As IntPtr) As Integer
    End Function

    <DllImport("user32.dll")>
    Private Shared Function GetWindowText(ByVal hWnd As IntPtr, ByVal lpString As StringBuilder, ByVal nMaxCount As Integer) As Integer
    End Function

    <DllImport("user32", EntryPoint:="SendMessageA", CharSet:=CharSet.Ansi, SetLastError:=True, ExactSpelling:=True)> Public Shared Function SendMessage(ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByRef lParam As Integer) As Integer
    End Function

    <DllImport("user32.dll")>
    Private Shared Function SetForegroundWindow(ByVal hWnd As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function SetActiveWindow(ByVal hWnd As IntPtr) As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function SetWindowPos(ByVal hWnd As IntPtr, hWndInsertAfter As IntPtr, x As Integer, y As Integer, cx As Integer, cy As Integer, uFlags As UInt32) As Boolean
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function RedrawWindow(ByVal hWnd As IntPtr, lprcUpdate As Integer, hrgnUpdate As Integer, uFlags As UInt32) As Boolean
    End Function

    Public Const WM_SYSCOMMAND As Integer = &H112
    Public Const SC_RESTORE = &HF120
    Public Const SWP_SHOWWINDOW As Integer = &H40
    Public Const SWP_NOSIZE As Integer = &H1
    Public Const SWP_NOMOVE As Integer = &H2
    Public Const RDW_FRAME As Int32 = 1024 'Updates the nonclient area if included in the redraw area. RDW_INVALIDATE must also be specified.
    Public Const RDW_INVALIDATE As Int32 = 1 'Invalidates the redraw area.
    Public Const RDW_ALLCHILDREN As Int32 = 128 'Redraw operation includes child windows if present in the redraw area.

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim strPartialTitle As String = TextBox1.Text
        Dim intptrByRefFoundHwnd As IntPtr = Marshal.StringToHGlobalAnsi(strPartialTitle)
        Dim delegateEnumWindowsProcDelegate As EnumWindowsProcDelegate
        delegateEnumWindowsProcDelegate = New EnumWindowsProcDelegate(AddressOf EnumWindowsProc)
        EnumWindows(delegateEnumWindowsProcDelegate, intptrByRefFoundHwnd)
        LabelHwndAndWindowTitle.Text = intptrByRefFoundHwnd
        BringWindowToFront(intptrByRefFoundHwnd)
    End Sub

    Function EnumWindowsProc(ByVal hWnd As IntPtr, ByRef lParam As IntPtr) As Integer
        Dim strPartialTitle As String = Marshal.PtrToStringAnsi(lParam)
        Dim length As Integer = GetWindowTextLength(hWnd)
        Dim stringBuilder As New StringBuilder(length)
        GetWindowText(hWnd, stringBuilder, (length + 1))
        If stringBuilder.ToString.Trim.Length > 2 Then
            If stringBuilder.ToString.ToLower.Contains(strPartialTitle.ToLower) Then
                Debug.WriteLine(hWnd.ToString & ": " & stringBuilder.ToString)
                lParam = hWnd ' Pop hwnd to top, returns in lParm of EnumWindows Call (2nd parameter)
                Return False
            End If
        End If
        Return True
    End Function

    Private Sub BringWindowToFront(hwnd As IntPtr)
        SendMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0) ' restore the minimize window
        SetForegroundWindow(hwnd)
        SetActiveWindow(hwnd)
        SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_SHOWWINDOW Or SWP_NOMOVE Or SWP_NOSIZE)
        'redraw to prevent the window blank.
        RedrawWindow(hwnd, IntPtr.Zero, 0, RDW_FRAME Or RDW_INVALIDATE Or RDW_ALLCHILDREN)
    End Sub

End Class
January answered 26/1, 2019 at 3:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.