Подтвердить что ты не робот

Как я могу использовать EnumWindows для поиска окон с определенным титром/заголовком?

Я работаю над приложением, которое в конечном итоге станет api для запуска тестов пользовательского интерфейса для приложения WPF.

В какой-то момент начального теста, над которым мы работаем, мы получаем 2 всплывающих окна безопасности Windows. У нас есть код, который повторяется в 10 раз, он получает дескриптор одного из всплывающих окон, используя метод FindWindowByCaption, и вводит информацию и щелкает нормально.

9 раз из 10 это работает отлично, однако мы иногда видим, что выглядит гоночным. Мое подозрение в том, что цикл начинается, когда открыто только одно из окон, а при вводе информации открывается вторая и крадет фокус; после этого он просто висит бесконечно.

Мне интересно, есть ли какой-либо метод для получения всех оконных дескрипторов для данного заголовка, так что мы можем подождать до тех пор, пока не будет два цикла.

4b9b3361

Ответ 1

Оригинальный ответ

Используйте 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);
    });
} 

Например, чтобы получить все окна с "Блокнотом" в заголовке:

var windows = FindWindowsWithText("Notepad");

Win32Interop.WinHandles

Этот ответ оказался достаточно популярным, что я создал проект OSS, Win32Interop.WinHandles, чтобы обеспечить абстракцию над IntPtrs для окон win32. Используя библиотеку, чтобы получить все окна, содержащие "Блокнот" в заголовке:

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

Ответ 2

Я знаю, что это старый вопрос, но ответ на него со временем изменится, когда Visual Studio перейдет в будущее.

Я хотел бы поделиться своим решением, которое позволяет вам искать частичный заголовок окна, который часто необходим, когда заголовок заголовка содержит непредсказуемый текст. Например, если вы хотите найти дескриптор почтового приложения Windows, заголовок будет содержать текст "Входящие - имя_почты_почты". Очевидно, вы не хотите жестко кодировать имя учетной записи. Вот мой код, хотя в Visual Basic.NET его можно преобразовать в С#. Введите частичный заголовок (т.е. "Входящие -"), нажмите кнопку, и вы вернете hwnd и полный заголовок обратно. Я пытался использовать Process.GetProcesses(), но это был медленный путь по сравнению с Win API.

Этот пример вернет дескриптор окна вашего поиска в lparm вызова EnumWindows (2-й параметр, переданный 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

Ответ 3

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

И есть какой-то код, который его использует:

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

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

Я получил этот код из http://www.tcx.be/blog/2006/list-open-windows/

Если вам нужна помощь в том, как это использовать, сообщите мне, я понял, что