C# 如何使用 EnumWindows 查找具有特定标题/标题的窗口?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19867402/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 16:02:24  来源:igfitidea点击:

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

c#ui-automation

提问by

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

我正在开发一个最终将成为驱动 WPF 应用程序 UI 测试的 API 的应用程序。

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.

在我们正在进行的初始测试的某一时刻,我们获得了 2 个 Windows 安全弹出窗口。我们有一些循环 10 次的代码,它使用 FindWindowByCaption 方法获取其中一个弹出窗口的句柄并输入信息并单击确定。

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.

10 次中有 9 次这工作得很好,但是我们偶尔会看到看起来像是竞争条件的东西。我的怀疑是当只有一个窗口打开时循环开始,当它输入信息时,第二个打开并窃取焦点;在此之后,它只是无限期地挂起。

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.

我想知道是否有任何方法可以获取给定标题的所有窗口句柄,以便我们可以在开始循环之前等到有 2 个。

采纳答案by FriendlyGuy

Original Answer

原答案

Use EnumWindowsand enumerate through all the windows, using GetWindowTextto get each window's text, then filter it however you want.

使用EnumWindows并枚举所有窗口,GetWindowText用于获取每个窗口的文本,然后根据需要对其进行过滤。

[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

Win32Interop.WinHandles

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

事实证明,这个答案非常流行,以至于我创建了一个 OSS 项目Win32Interop.WinHandles来为 win32 窗口提供对 IntPtrs 的抽象。使用库,获取标题中包含“记事本”的所有窗口:

var allNotepadWindows
   = TopLevelWindowUtils.FindWindows(wh => wh.GetWindowText().Contains("Notepad"));

回答by Faiz

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/

我从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

如果您需要有关如何使用它的帮助,请告诉我,我想通了

回答by Larry Robertson

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

我知道这是一个老问题,但随着 Visual Studio 进入未来,答案会随着时间的推移而改变。

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.

我想分享我的解决方案,它允许您搜索部分窗口标题,当标题标题包含不可预测的文本时,通常需要该标题。例如,如果您想找到 Windows Mail 应用程序的句柄,标题将包含文本“收件箱 - 您的电子邮件帐户名”。显然您不想对帐户名称进行硬编码。这是我的代码,虽然它在 Visual Basic .NET 中,但您可以将其转换为 C#。输入部分标题(即“收件箱-”),单击按钮,您将获得完整标题和完整标题。我尝试使用 Process.GetProcesses() 但与 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.

此示例将返回您在 EnumWindows 调用的 lparm 中搜索的窗口句柄(通过 byref 传递的第二个参数),即使应用程序已最小化,它也会将应用程序置于最前面。

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