How to SendText to specific field in another application by Win32 API.
I.
First of all you must understand the name of application, the window on which you want to send message and name of input fields in that window. There are some application to help you obtain needed information.
There are two main tools to navigate between opened windows in windows desktop. First of it is Microsoft Spy++, which installed by default with Visual Studio.
And second main tools is WinSpy.
This tools help you recognize input fields name.
Programmatically the same operations start from class System.Diagnostics.Process.
72: Sub Main()
73: Dim Wnd_name As String
74: Dim nWnd2 As IntPtr
75: Dim Processes As System.Diagnostics.Process() = Process.GetProcesses()
76: Dim PokerProcess = (From Y In Processes Select Y Where Y.ProcessName.Contains("Poker")).ToList
77: For Each one In PokerProcess
78: If one.MainWindowHandle <> IntPtr.Zero Then
79: Wnd_name = one.MainWindowTitle
80: Console.WriteLine(Wnd_name)
81: ObjectDumper.Write(one, 1)
82: nWnd2 = one.MainWindowHandle
83: End If
84: Next
For visual understanding information of process I create simple reflection object dumper.
Next step is to search all daughter windows from top windows and send the message to needed field.
85: Dim allChildWindows2 = New WindowHandleInfo(nWnd2).GetAllChildHandles()
86:
87: For Each One As IntPtr In allChildWindows
88: Dim Name As String = GetClassNameOfWindow(One)
89: Console.WriteLine(Name)
90: If Name = "PokerStarsChatEditorClass" Then
91: Dim Res As IntPtr = SendMessage(One, WM_SETTEXT, New IntPtr(0), Wnd_name)
92: End If
93: Next
94:
95: Private Function GetClassNameOfWindow(ByVal hwnd As IntPtr) As String
96: Dim className As String = ""
97: Dim classText As Text.StringBuilder = Nothing
98:
99: Try
100: Dim cls_max_length As Integer = 1000
101: classText = New Text.StringBuilder("", cls_max_length + 5)
102: GetClassName(hwnd, classText, cls_max_length + 2)
103: If Not String.IsNullOrEmpty(classText.ToString()) AndAlso Not String.IsNullOrWhiteSpace(classText.ToString()) Then className = classText.ToString()
104: Catch ex As Exception
105: className = ex.Message
106: Finally
107: classText = Nothing
108: End Try
109:
110: Return className
111: End Function
This code use some definition of Win32 API (full definition available in https://pinvoke.net/
15: Public Const WM_CHAR = &H102
16: Public Const WM_SETTEXT = &HC
17: Public Const WM_KEYDOWN = &H100
18: Public Const WM_KEYUP = &H101
19: Public Const WM_LBUTTONDOWN = &H201
20: Public Const WM_LBUTTONUP = &H202
21: Public Const WM_CLOSE = &H10
22: Public Const WM_COMMAND = &H111
23: Public Const WM_CLEAR = &H303
24: Public Const WM_DESTROY = &H2
25: Public Const WM_GETTEXT = &HD
26: Public Const WM_GETTEXTLENGTH = &HE
27: Public Const WM_LBUTTONDBLCLK = &H203
28:
29:
30: <DllImport("User32.dll")>
31: Private Function ShowWindow(hWnd As Integer, nCmdShow As Integer) As Boolean
32: End Function
33:
34: <DllImport("user32.dll", CharSet:=CharSet.Auto)>
35: Public Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As IntPtr, ByVal lParam As Text.StringBuilder) As IntPtr
36: End Function
37:
38: <DllImport("user32.dll", CharSet:=CharSet.Auto)>
39: Public Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As IntPtr
40: End Function
41:
42: <DllImport("user32.dll", CharSet:=CharSet.Auto)>
43: Public Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As IntPtr
44: End Function
45:
46:
47: <DllImport("user32.dll", CharSet:=CharSet.Auto)>
48: Public Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, ByVal lParam As IntPtr) As IntPtr
49: End Function
:
67: <DllImport("user32.dll", CharSet:=CharSet.Auto)>
68: Private Function GetClassName(ByVal hWnd As IntPtr, ByVal lpClassName As Text.StringBuilder, ByVal nMaxCount As Integer) As Integer
69: End Function
And this is simple additional wrapper over Win32 EnumChildWindows function.
1: Imports System.Runtime.InteropServices
2:
3: Public Class WindowHandleInfo
4: Private Delegate Function EnumWindowProc(ByVal hwnd As IntPtr, ByVal lParam As IntPtr) As Boolean
5:
6: <DllImport("user32")>
7: Private Shared Function EnumChildWindows(ByVal window As IntPtr, ByVal callback As EnumWindowProc, ByVal lParam As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
8: End Function
9:
10: Private _MainHandle As IntPtr
11:
12: Public Sub New(ByVal handle As IntPtr)
13: Me._MainHandle = handle
14: End Sub
15:
16: Public Function GetAllChildHandles() As List(Of IntPtr)
17: Dim childHandles As List(Of IntPtr) = New List(Of IntPtr)()
18: Dim gcChildhandlesList As GCHandle = GCHandle.Alloc(childHandles)
19: Dim pointerChildHandlesList As IntPtr = GCHandle.ToIntPtr(gcChildhandlesList)
20:
21: Try
22: Dim childProc As EnumWindowProc = New EnumWindowProc(AddressOf EnumWindow)
23: EnumChildWindows(Me._MainHandle, childProc, pointerChildHandlesList)
24: Finally
25: gcChildhandlesList.Free()
26: End Try
27:
28: Return childHandles
29: End Function
30:
31: Private Function EnumWindow(ByVal hWnd As IntPtr, ByVal lParam As IntPtr) As Boolean
32: Dim gcChildhandlesList As GCHandle = GCHandle.FromIntPtr(lParam)
33:
34: Dim childHandles As List(Of IntPtr) = TryCast(gcChildhandlesList.Target, List(Of IntPtr))
35: childHandles.Add(hWnd)
36: Return True
37: End Function
38: End Class
And this is a result of this test.
II.
But not all windows program has controls derived from Windows Control class, therefore some program is show only main window.
In this case we can use UI Automation. Below you can see a small test to use this framework with firefox.
1: Imports System.Windows.Automation
2:
3: Module Module1
4:
5: Sub Main()
6: Dim Firefox As Process() = Process.GetProcessesByName("firefox")
7: If Firefox.Length > 0 Then
8: Dim Parent = Firefox(0).Parent
9: Dim RootElement As AutomationElement = AutomationElement.FromHandle(Parent.MainWindowHandle)
10: Dim Controls As Condition = New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Document)
11: Console.WriteLine(RootElement.FindFirst(TreeScope.Descendants, Controls))
12: Dim DocElement As AutomationElement = RootElement.FindFirst(TreeScope.Descendants, Controls)
13: For Each pattern As AutomationPattern In DocElement.GetSupportedPatterns()
14: Console.WriteLine(DocElement.GetSupportedPatterns(0).GetType.FullName)
15: If TypeOf DocElement.GetCurrentPattern(pattern) Is ValuePattern Then
16: Console.WriteLine((TryCast(DocElement.GetCurrentPattern(pattern), ValuePattern)).Current.Value.ToString())
17: End If
18: Next
19: 'freeze
20: Dim DocElements As AutomationElementCollection = RootElement.FindAll(TreeScope.Descendants, Controls)
21: For Each One As AutomationElement In DocElements
22: For Each pattern As AutomationPattern In One.GetSupportedPatterns()
23: If TypeOf One.GetCurrentPattern(pattern) Is ValuePattern Then Console.WriteLine((TryCast(One.GetCurrentPattern(pattern), ValuePattern)).Current.Value.ToString() + Environment.NewLine)
24: Next
25: Next
26: Console.ReadLine()
27: End If
28: End Sub
29:
30: End Module
Pay attention than first part of code (FindFirst URL) working quick and fine but second part of this test (FindAll) is working extremely slow. Little bit later, after some experiments, I found more faster solution How to Read URLs from Firefox tabs by UI Automation.
|