سؤال

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().

هل كانت مفيدة؟

المحلول

Make sure the specific control you are sending the keystrokes to is properly focused.

You should be able to use SetFocus to give focus to the control you are sending the keystrokes to.

SendMessage and PostMessage can also be used to send keystrokes, but it's BAD PRACTICE and should be avoided.

Check out System.Windows.Forms.SendKeys for information on sending keystrokes though the Forms class in .NET.

In a lot of cases, if you don't need the keystrokes themselves, you can just change the text on a window using SendMessage with WM_SETTEXT if this is what you're looking to do.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top