I'm making an application that needs to work with the UI of a program which doesn't seem to implement UI Automation elements (Inspect.Exe only shows the main pane and no children).
So I researched about what the best ways to implement the features I need were, and found SendInput(), which apparently is a newer version of keybd_event() and mouse_event().
However, since it requires keyboard focus and since I can't afford to set the target window to foreground (to avoid bothering the user while it runs), I kept searching until I found this answer. I did what Skurmedel said, and joined my application's thread to the target's window thread. But now, even if I SetFocus() to the target and then SendInput(), the target window won't be affected.
My question either is "Why doesn't this work?" or "What am I doing wrong?", but I guess a code example will help sorting this out:
ThreadHandler class
class ThreadHandler
{
#region P/Invoking and constants definition
const uint WM_GETTEXT = 0x000D;
[DllImport("user32.dll")]
static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, uint lpdwProcessId = 0);
delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn,
IntPtr lParam);
[DllImport("user32.dll")]
static extern bool AttachThreadInput(int idAttach, int idAttachTo, bool fAttach);
[DllImport("kernel32.dll")]
static extern int GetCurrentThreadId();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, StringBuilder lParam);
#endregion
public readonly string ProcessName, WindowName;
protected readonly int TargetThreadID, CurrentThreadID;
protected readonly IntPtr TargetWindowHandle;
public ThreadHandler(string processName, string windowName)
{
CurrentThreadID = GetCurrentThreadId();
ProcessName = processName;
WindowName = windowName;
object[] objs = GetWindowThread(processName, windowName);
if (objs == null)
{
throw new ArgumentException("Could not find the specified process/window.");
}
TargetThreadID = (int)objs[0];
TargetWindowHandle = (IntPtr)objs[1];
}
public ThreadHandler(string processName)
{
CurrentThreadID = GetCurrentThreadId();
ProcessName = processName;
var processes = Process.GetProcessesByName(ProcessName);
if (processes.Length == 0)
{
throw new ArgumentException("Could not find the specified process.");
}
var appProc = processes[0];
WindowName = appProc.MainWindowTitle;
TargetThreadID = GetWindowThreadProcessId(appProc.MainWindowHandle);
TargetWindowHandle = appProc.MainWindowHandle;
}
public bool AttachThreadInput()
{
return AttachThreadInput(CurrentThreadID, TargetThreadID, true);
}
public bool DetachThreadInput()
{
return AttachThreadInput(CurrentThreadID, TargetThreadID, false);
}
public void SetFocus()
{
SetFocus(TargetWindowHandle);
}
static object[] GetWindowThread(string processName, string windowName)
{
var processes = Process.GetProcessesByName(processName);
if (processes.Length > 0)
{
//Fill a list of handles
var handles = new List<IntPtr>();
foreach (ProcessThread thread in processes[0].Threads)
EnumThreadWindows(thread.Id,
(hWnd, lParam) => { handles.Add(hWnd); return true; }, IntPtr.Zero);
//Create a stringbuilder to function as storage unit
StringBuilder nameBuffer = new StringBuilder(64);
foreach (var hWnd in handles)
{
//And finally compare the caption of the window with the requested name
nameBuffer.Clear();
SendMessage(hWnd, WM_GETTEXT, nameBuffer.Capacity, nameBuffer);
if (nameBuffer.ToString() == windowName)
{
return new object[2] { GetWindowThreadProcessId(hWnd), hWnd };
}
}
}
return null;
}
}
Main method of the application
static void Main(string[] args)
{
Console.WriteLine("Please input the name of the process to hook: ");
string pName = Console.ReadLine();
Console.WriteLine("Input the name of a specific window, or leave blank: ");
string pWnd = Console.ReadLine();
ThreadHandler threadHandler;
try
{
if(!String.IsNullOrWhiteSpace(pWnd))
threadHandler = new ThreadHandler(pName, pWnd);
else
threadHandler = new ThreadHandler(pName);
}
catch
{
Console.WriteLine("Error: " + pName +" does not seem to be running.");
Console.ReadKey();
return;
}
if (!threadHandler.AttachThreadInput())
{
Console.WriteLine("Error: The application tried to attach its Input Processing Mechanism to " + threadHandler.ProcessName + ", but failed.");
Console.ReadKey();
return;
}
Console.WriteLine("Input Processing Mechanism correctly attached to " + threadHandler.ProcessName + ".");
threadHandler.SetFocus();
InputSimulator.SimulateTextEntry("test"); //InputSimulator is a seemingly famous SendInput wrapper. Replacing this line with the code for a keystroke also doesn't work.
Console.ReadLine();
Console.WriteLine("Detaching Input Processing Mechanism.");
threadHandler.DetachThreadInput();
}
Thanks in advance if you can elucidate me on the arcane arts of SendInput().