7

I am trying to do one of the following 1. open desired program and press a key programmatically 2. find open window of program and press a key programmatically (either is fine)

I have tried numerous implementations of SendKeys.SendWait(), PostMessage(), and SendMessage() unsuccessfully. Below are my code snippets

    //included all these for attempts 
    [DllImport("User32.dll")] 
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);  

    [DllImport("User32.dll")] 
    static extern int SetForegroundWindow(IntPtr hWnd);

    [DllImport("User32.Dll", EntryPoint = "PostMessageA")]
    static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);

    [DllImport("user32.dll")]
    static extern byte VkKeyScan(char ch);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

Get handle of window, variables used by sendmessage/postmessage/sendkeys

IntPtr ptrOBS = proc.Handle;//this works properly, proc is instantiated properly
//IntPtr ptrOBS = FindWindow(null, "Open Broadcaster Software v0.472b");
SetForegroundWindow(ptrOBS);
const UInt32 WM_CHAR = 0x0102;
const uint WM_KEYDOWN = 0x100;
const int VK_R = 0x52; // taken from http://msdn.microsoft.com/en-us/library/dd375731(v=vs.85).aspx
const int VK_S = 0x53;

SendMessage attempt:

SendMessage(ptrOBS, WM_KEYDOWN, (IntPtr)VK_R, (IntPtr)1);//tried both WM_CHAR and WM_KEYDOWN

PostMessage attempt:

string message = "rs";
bool sent = PostMessage(ptrOBS, WM_KEYDOWN, VkKeyScan(message[0]), 0);

SendKeys attempt:

SendKeys.SendWait("{r}");

Tried SetFocus on the parent window (application) and child window (button triggered by keypress im trying to send):

static void SetFocus(IntPtr hwndTarget, string childClassName)
    {
        // hwndTarget is the other app's main window 
        // ...
        IntPtr targetThreadID = GetWindowThreadProcessId(hwndTarget, IntPtr.Zero); //target thread id
        IntPtr myThreadID = GetCurrentThread(); // calling thread id, our thread id
        try
        {
            bool lRet = AttachThreadInput(myThreadID, targetThreadID, -1); // attach current thread id to target window

            // if it's not already in the foreground...
            lRet = BringWindowToTop(hwndTarget);
            SetForegroundWindow(hwndTarget);

            // if you know the child win class name do something like this (enumerate windows using Win API again)...
            IntPtr hwndChild = (IntPtr)1183492;//(IntPtr)EnumAllWindows(hwndTarget, childClassName).FirstOrDefault();

            if (hwndChild == IntPtr.Zero)
            {
                // or use keyboard etc. to focus, i.e. send keys/input...
                // SendInput (...);
                return;
            }

            // you can use also the edit control's hwnd or some child window (of target) here
            SetFocus(hwndChild); // hwndTarget);
            SendKeys.SendWait("{r}");
        }
        finally
        {
            SendKeys.SendWait("{r}");
            bool lRet = AttachThreadInput(myThreadID, targetThreadID, 0); //detach from foreground window
            SendKeys.SendWait("{r}");
        }
    }

For NSGaga:

    string windowName = "Open Broadcaster Software v0.472b";
IntPtr outerPtr = FindWindow(null, windowName);
IntPtr ptrOBS = (IntPtr)527814;//button that im trying to trigger keypress on
SetForegroundWindow(outerPtr);
SetForegroundWindow(ptrOBS);
SetFocus(outerPtr, "OBSWindowClass");//SetFocus(ptrOBS, "Button");
const UInt32 WM_CHAR = 0x0102;
const int VK_R = 0x52; // taken from http://msdn.microsoft.com/en-us/library/dd375731(v=vs.85).aspx
const int VK_S = 0x53;

//SetForegroundWindow(ptrOBS);
System.Threading.Thread.Sleep(3000);
SendKeys.SendWait("{r}");
SendMessage(outerPtr, WM_KEYDOWN, (IntPtr)VK_R, (IntPtr)1);
PostMessage(outerPtr, WM_KEYDOWN, VkKeyScan('r'), 0);
SPillai
  • 177
  • 3
  • 7
  • 20

2 Answers2

6

You cannot reliably use SendMessage and PostMessage for synthesizing keyboard input. They are just not designed for this. These messages (WM_CHAR, WM_KEYDOWN, etc.) are notifications raised by lower-level subsystems when keyboard input has been received, processed, and forwarded on to the appropriate recipient. Sending or posting these messages yourself is like prank-calling someone.

SendKeys (like all other input synthesizer methods, including the SendInput function which was explicitly designed for synthesizing keyboard input and in at least some implementation is what SendKeys actually uses under the hood) works only when the window you wish to receive the keyboard input has the focus. In Windows, only focused (active) windows receive input events.

So SendKeys is probably the way to go if you're ever going to get this to work (either that or P/Invoking SendInput and all of its associated structures), but you do need to respect the caveat that the recipient window must have the focus. Otherwise, it's not going to get anything.

It looks like from your sample code that you're trying to use the SetForegroundWindow function to meet this precondition. Unfortunately, you're passing it an invalid value, and not doing any error checking that might alert you to this mistake. Specifically, this code is wrong:

IntPtr ptrOBS = proc.Handle;//this works properly, proc is instantiated properly
SetForegroundWindow(ptrOBS); // WRONG, ptrOBS is not a window handle

Even if I trust you on ptrOBS being initialized correctly, that makes it a valid handle to a process, which is a very different thing than a valid handle to a window. Aside from the obvious nominal differences, processes can have multiple windows and only a single window can have the focus (i.e., be "in the foreground").

You will need to obtain the handle to a particular window before calling SetForegroundWindow, and given that we know a process can have multiple windows, that can be tricky. You need some reliable way of determining which window you want. Lots of people accomplish this by hard-coding the name of the window as a string, which works great until the target app is recompiled and this implementation detail changes. The only bulletproof way that I can think of is to have the user click the target window and your code to retrieve the handle of the window that is currently under the mouse pointer.

And of course all of this assumes that you've observed the restrictions on the use of SetForegroundWindow, enumerated in the "Remarks" section of the linked SDK documentation.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • IntPtr ptrOBS = FindWindow(null, "Open Broadcaster Software v0.472b"); doesn't work either, and im assuming FindWindow does return the actual window handle, not the process handle? But thank you for pointing out the things i need to be looking at and changing. I will now keep hammering at the SendKeys method with a real window handle. – SPillai Mar 21 '13 at 00:22
  • @AHeinen Have you verified that `FindWindow` actually returns a handle? The correct handle? Use Spy++ to verify this by comparing it to the actual handle of the window you're interested in. – Cody Gray - on strike Mar 21 '13 at 02:50
  • it does return the actual parent window of the application. I also tried hardcoding the IntPtr hex=>decimal value for the child window housing the action i want to run with keypress (the button that hotkey presses). using either/both windows didn't work. I tried implementing SetFocus (will update OP with my attempt) – SPillai Mar 26 '13 at 18:36
3

There is lot of trial and error with that, to get it working
Here is a bit of code I posted before, you might wanna give a try (and there is some more info attached)...

Pinvoke SetFocus to a particular control

Try setting focus first (using the mechanism mentioned) - and then using SendKeys or SendInput.

Here is some detailed code for SendInput...

How to send a string to other application including Microsoft Word

Community
  • 1
  • 1
NSGaga-mostly-inactive
  • 14,052
  • 3
  • 41
  • 51
  • did you manage to `SetFocus` on anything in the target window - using what I proposed (that has to be followed to the letter) - that's the first step. I had that working many times over. However, some apps are specific. Also try elevating your app (as admin) if it helps. – NSGaga-mostly-inactive Mar 26 '13 at 18:48
  • or try posting your final code you have made (with that approach) - I'll copy paste and run here (adjust) and try to take a look if time allows – NSGaga-mostly-inactive Mar 26 '13 at 18:49
  • setfocus implementation i tried was above; do you want me to post my win32 api function declarations or just calling setfocus then sendkeys? – SPillai Mar 26 '13 at 19:15
  • best full repro (just cut down to min - e.g. try with notepad or something) - that's easiest to work with (common sample) – NSGaga-mostly-inactive Mar 26 '13 at 19:18
  • I see :) I'll take a look for sure (when I find some time, later on or tomorrow) - **tell me just did you try on any other program - like w/ Notepad** I mentioned? I think you're doing something wrong with keys sending - that's a tricky part – NSGaga-mostly-inactive Mar 26 '13 at 20:11
  • ok ill try on notepad. is sending a keystroke to notepad different than triggering a one key hotkey (i doubt it)? – SPillai Mar 26 '13 at 20:25
  • yep :) that's good and bad news - how sure are you that `OBSWindowClass` is the actual `target` window you need to receive the strokes. That's usually the core. Try to go down the hierarchy from the app's main window, into child windows - and list them all out (dump etc.). Analyze and find the best candidate (dump window/control long names I think (you'll find that info/method if not in my code) - and you can also 'draw' the rectangles (getwinrect or so), getwindowsunderpoint etc. (there're many of such usefull functions you'd need for this). Your goal is to locate the actual control to focus. – NSGaga-mostly-inactive Mar 26 '13 at 21:27
  • i found the control for the button with hotkey that im trying to send (as keystroke) and tried, that didn't work either (its commented out in my example, since i tried it first) – SPillai Mar 27 '13 at 01:36
  • I am feeling generous :) but it's out of my hand and scope for this - the general mechanism is working, and it's hard for me to do the coding/debugging. I'd suggest if you'd like to continue resolving this further - you can award/upvote this one - and open a new question with a specific assignment (i.e. how to work this out for your specific application - which is very spec and narrow) - that way I could devote some time maybe. Basically this is a 'bounty type' question (for few 100 pts) - otherwise I can only point you like I did = and more, I hope you learned something. Cheers – NSGaga-mostly-inactive Mar 27 '13 at 01:41