c# - ¿Cómo puedo usar EnumWindows para encontrar ventanas con un título o título específico?
ui-automation (3)
Estoy trabajando en una aplicación que finalmente será una api para conducir las pruebas de UI para una aplicación WPF.
En un momento de la prueba inicial en la que estamos trabajando, obtenemos 2 ventanas emergentes de seguridad de Windows. Tenemos algo de código que se repite 10 veces, se maneja una de las ventanas emergentes mediante el método FindWindowByCaption, ingresa la información y hace clic en Aceptar.
9 de cada 10 veces esto funciona bien, sin embargo, ocasionalmente estamos viendo lo que parece ser una condición de carrera. Mi sospecha es que el bucle comienza cuando solo una de las ventanas está abierta y mientras ingresa la información, la segunda se abre y roba el foco; después de esto simplemente cuelga indefinidamente.
Lo que me pregunto es si hay algún método para obtener todos los manejadores de ventana para un título dado, de modo que podamos esperar hasta que haya 2 antes de comenzar el ciclo.
Respuesta original
Use EnumWindows
y enumere a través de todas las ventanas, use GetWindowText
para obtener el texto de cada ventana, luego filtre lo que quiera.
[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);
});
}
Por ejemplo, para obtener todas las ventanas con "Bloc de notas" en el título:
var windows = FindWindowsWithText("Notepad");
Win32Interop.WinHandles
Esta respuesta demostró ser lo suficientemente popular como para crear un proyecto OSS, Win32Interop.WinHandles para proporcionar una abstracción sobre IntPtrs para ventanas win32. Usando la biblioteca, para obtener todas las ventanas que contienen "Bloc de notas" en el título:
var allNotepadWindows
= TopLevelWindowUtils.FindWindows(wh => wh.GetWindowText().Contains("Notepad"));
Sé que esta es una pregunta antigua, pero la respuesta cambiará con el tiempo a medida que Visual Studio avance hacia el futuro.
Me gustaría compartir mi solución que le permite buscar un Título de ventana parcial que a menudo se necesita cuando el Título del título contiene texto impredecible. Por ejemplo, si desea encontrar el identificador de la aplicación de correo de Windows, el título contendrá el texto "Bandeja de entrada - youremailaccountname". Obviamente no quieres codificar el nombre de la cuenta. Aquí está mi código, aunque está en Visual Basic .NET, puede convertirlo a C #. Escribe un título parcial (es decir, "Bandeja de entrada -"), haz clic en el botón y obtendrás el título hwnd y el título completo. Intenté usar Process.GetProcesses () pero era una manera de reducir la velocidad en comparación con la API de Win.
Este ejemplo devolverá el identificador de la ventana de su búsqueda en lparm de la llamada de EnumWindows (2do parámetro pasado por ref) y traerá la aplicación al frente incluso si está minimizada.
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
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();
}
Y aquí hay un código que lo usa:
foreach(KeyValuePair<IntPtr, string> window in OpenWindowGetter.GetOpenWindows())
{
IntPtr handle = window.Key;
string title = window.Value;
Console.WriteLine("{0}: {1}", handle, title);
}
Obtuve este código de http://www.tcx.be/blog/2006/list-open-windows/
Si necesita ayuda sobre cómo usar esto, hágamelo saber, lo descubrí