web-dev-qa-db-fra.com

Comment puis-je utiliser EnumWindows pour trouver des fenêtres avec une légende / un titre spécifique?

Je travaille sur une application qui finira par être une API pour conduire des tests d'interface utilisateur pour une application WPF.

À un moment du test initial sur lequel nous travaillons, nous obtenons 2 fenêtres contextuelles de sécurité Windows. Nous avons du code qui boucle 10 fois, il obtient la poignée de l'une des fenêtres contextuelles à l'aide de la méthode FindWindowByCaption et entre les informations et clique sur OK.

9 fois sur 10, cela fonctionne très bien, mais nous voyons parfois ce qui semble être une condition de course. Je soupçonne que la boucle démarre lorsqu'une seule des fenêtres est ouverte et pendant qu'elle entre les informations, la seconde s'ouvre et vole le focus; après cela, il se bloque indéfiniment.

Ce que je me demande, c'est s'il existe une méthode pour obtenir tous les descripteurs de fenêtre pour une légende donnée, afin que nous puissions attendre qu'il y en ait 2 avant de démarrer la boucle.

18
user1618236

Réponse originale

Utilisez EnumWindows et énumérez à travers toutes les fenêtres, en utilisant GetWindowText pour obtenir le texte de chaque fenêtre, puis filtrez-le comme vous le souhaitez.

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

Par exemple, pour obtenir toutes les fenêtres avec "Bloc-notes" dans le titre:

var windows = FindWindowsWithText("Notepad");

Win32Interop.WinHandles

Cette réponse s'est avérée assez populaire pour que j'ai créé un projet OSS, Win32Interop.WinHandles pour fournir une abstraction sur IntPtrs pour les fenêtres win32. En utilisant la bibliothèque, pour obtenir toutes les fenêtres qui contiennent "Bloc-notes" dans le titre:

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

Je sais que c'est une vieille question, mais c'est une réponse qui changera au fil du temps à mesure que Visual Studio se déplace vers l'avenir.

Je voudrais partager ma solution qui vous permet de rechercher un titre de fenêtre partiel qui est souvent nécessaire lorsque la légende de titre contient du texte imprévisible. Par exemple, si vous vouliez trouver le descripteur de l'application Windows Mail, le titre contiendra le texte "Boîte de réception - votre nom de compte de messagerie". De toute évidence, vous ne voulez pas coder en dur le nom du compte. Voici mon code bien qu'il soit en Visual Basic .NET, vous pouvez le convertir en C #. Tapez un titre partiel (c'est-à-dire "Boîte de réception -"), cliquez sur le bouton et vous obtiendrez le titre et le titre complet. J'ai essayé d'utiliser Process.GetProcesses () mais c'était beaucoup plus lent que l'API Win.

Cet exemple renverra le handle de fenêtre de votre recherche dans lparm de l'appel EnumWindows (2e paramètre passé byref) et mettra l'application au premier plan même si elle est minimisée.

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
0
Larry Robertson