最終的にはWPFアプリケーションのUIテストを実行するためのAPIになるアプリケーションに取り組んでいます。
私たちが取り組んでいる最初のテストのある時点で、2つのWindowsセキュリティポップアップが表示されます。 10回ループするコードがあり、FindWindowByCaptionメソッドを使用してポップアップの1つのハンドルを取得し、情報を入力して[OK]をクリックします。
10回のうち9回は問題なく動作しますが、競合状態のように見えるものを時々見かけます。私の疑いは、ウィンドウの1つだけが開いているときにループが開始し、情報を入力している間に2番目のウィンドウが開いてフォーカスを奪うことです。この後、無期限にハングします。
特定のキャプションのすべてのウィンドウハンドルを取得するメソッドがあり、ループが開始する前に2が存在するまで待機できるようになっているのでしょうか。
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");
この回答は、私がOSSプロジェクト Win32Interop.WinHandles を作成して、Win32ウィンドウのIntPtrsを抽象化するのに十分人気があることを証明しました。ライブラリを使用して、タイトルに「メモ帳」を含むすべてのウィンドウを取得するには:
var allNotepadWindows
= TopLevelWindowUtils.FindWindows(wh => wh.GetWindowText().Contains("Notepad"));
私はこれが古い質問であることを知っていますが、Visual Studioが将来に移行するにつれて、答えは時間とともに変化するものです。
タイトルキャプションに予測できないテキストが含まれている場合によく必要となる部分的なウィンドウタイトルを検索できるようにする私のソリューションを共有したいと思います。たとえば、Windowsメールアプリケーションのハンドルを検索する場合、タイトルには「受信トレイ-youremailaccountname」というテキストが含まれます。明らかに、アカウント名をハードコーディングしたくないでしょう。これは私のコードですが、Visual Basic .NETの場合はC#に変換できます。タイトルの一部(「受信トレイ-」など)を入力してボタンをクリックすると、hwndと完全なタイトルが表示されます。 Process.GetProcesses()を使用してみましたが、Win APIに比べて遅くなっていました。
この例では、EnumWindows呼び出し(2番目のパラメーターbyrefで渡される)のlparmで検索のウィンドウハンドルを返し、最小化されている場合でもアプリケーションを前面に表示します。
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