13

I can successfully send any single key message to an application, but I don't know how to send combinations of keys (like Ctrl+F12, Shift+F1, Ctrl+R, etc..)

Tried doing it this way:

SendMessage(handle, WM_KEYDOWN, Keys.Control, 0);
SendMessage(handle, WM_KEYDOWN, Keys.F12, 0);
SendMessage(handle, WM_KEYUP, Keys.F12, 0);
SendMessage(handle, WM_KEYUP, Keys.Control, 0);

but this does not seems to work (application acts like only F12 is pressed, not Ctrl+F12).

Any ideas how to make this work?

Bridge
  • 29,818
  • 9
  • 60
  • 82
user1792042
  • 261
  • 1
  • 4
  • 11
  • possible duplicate of [PostMessage WM\_KEYDOWN send multiply keys?](http://stackoverflow.com/questions/7732633/postmessage-wm-keydown-send-multiply-keys) – Jon B Nov 02 '12 at 19:52
  • @JohnB there is no anwser for the question in that link. – user1792042 Nov 02 '12 at 19:59
  • The answer is to use `SendInput`. I looked into the key codes that SendMessage uses, and they are actually different from the `Keys` enum, in that they are 8-bit. This means you can't bitwise or to send two keys at once (as I had suggested in my answer). – Jon B Nov 02 '12 at 20:01
  • @JonB the OP did not state his requirements clearly. I edited my answer to show that what he wants is not possible on any version of Microsoft Windows. – Ed Bayiates Nov 02 '12 at 20:54

6 Answers6

4

You would probably find that using SendInput (documentation here) works a lot better. You will need to P/Invoke it from C#, example here. You can provide arrays of data with keys down and up and properly set the other message parameters, for example whether left or right Ctrl/Shift/Alt were pressed.

You can also use the SendKeys class (documentation here). This allows you to specify keys by name, e.g., {^F12} for Ctrl+F12.

Edit: The OP is now saying he needs to send input to minimized applications without activating them. This is not possible to do reliably in any way, including even with specialized hardware. I've worked in automation. It just isn't possible. The OP needs to use FindWindow/SetForegroundWindow to toggle the target app on, and then he can toggle back to his application.

Be Brave Be Like Ukraine
  • 7,596
  • 3
  • 42
  • 66
Ed Bayiates
  • 11,060
  • 4
  • 43
  • 62
  • looks like SendInput requires target window to be active, and my application is needed to work even if the target program is minimalised – user1792042 Nov 02 '12 at 19:34
  • You can activate the target window before beginning the SendInput calls. – Ed Bayiates Nov 02 '12 at 19:40
  • I've also added a link to SendKeys. – Ed Bayiates Nov 02 '12 at 19:47
  • SendKeys works, my target application registers the Control+F12 combination, BUT it requires the target application to be active, and my application needs to work in background, so i can't use it – user1792042 Nov 02 '12 at 19:56
  • 2
    @user1792042, first, you should have added that as a requirement in your question. Second, you can't reliably use SendMessage for simulation of keystrokes. Third, you cannot reliably send input to an application in the background. You can, however, activate the target application and then re-activate yours. – Ed Bayiates Nov 02 '12 at 20:46
4

The OP is now saying he needs to send input to minimized applications without activating them. This is not possible to do reliably in any way, including even with specialized hardware. I've worked in automation. It just isn't possible.

Sending input to minimized applications is, in fact, possible, not sure about mouse input, but keyboard input works just fine. Taking idea in a previous answer here's what I was able to accomplish (code is in Delphi, but it's pretty simple, so you can translate it to your required language):

procedure SendKeys(const Win : HWND; const Key,sKey: Cardinal);
var
  thrID : Cardinal;
  KB : TKeyBoardState;
begin
  if sKey <> 0 then
  begin
    thrID := GetWindowThreadProcessId(win,nil);
    GetKeyboardState(KB);
    AttachThreadInput(GetCurrentThreadID, thrID, True);
    KB[sKey] := KB[sKey] or $80;
    SetKeyboardState(KB);
  end;
  SendMessage(Win,WM_KEYDOWN,Key,0);
  SendMessage(Win,WM_KEYUP,Key,0);
  if sKey <> 0 then
  begin
    KB[sKey] := 0;
    SetKeyBoardState(KB);
    AttachThreadInput(GetCurrentThreadId, thrID, False);
  end;
end;

[Win] must be the control to receive the input, not its parent form etc. [Key] is a key to be pressed; [sKey] is an alternative key to be pressed while pressing [Key] such as CTRL/SHIFT (ALT is transferred through message itself, see MSDN WM_KEYDOWN reference for details).

Sending a sole keystroke is fairly simple, you just do the sendmessage and it's done, but if you need to something like CTRL+SPACE here's where it gets complicated. Each thread has its own KeyboardState, altering KeyboardState in your own app will not affect another unless you join their thread inputs by AttachThreadInput function. When application processes WM_KEYDOWN message it also tests current shift states (CTRL/SHIFT) by calling GetKeyboardState function (ALT key can be sent through the additional parameter of WM_KEYDOWN message) and that's when attached thread input comes into play.

Rusty
  • 904
  • 4
  • 10
3

I already tried the method with GetKeyboardState and SetKeyboardState (preceded by attaching the window thread and ended with detaching from window thread). I doesn't work for combinations of keys like Ctrl+Something or combinations using Alt or Shift also. Control, Alt and Shift keys are not seen as pressed. It seems like the maximum you can get when the window is minimized is pressing individual keys using PostMessage with WM_KEYDOWN messages. Another thing I noticed is that if you Post WM_KEYDOWN and WM_KEYUP (for the same key) the key will be pressed twice. So only use WM_KEYDOWN one time. This is not an 100% accurate method but when window is minimized there are some limitations.

The same situation happens when screen is locked.

daniel
  • 157
  • 1
  • 1
0

Maybe you are looking something like that:

procedure GoCursorUp(Obj: TControl);
var   KeyState : TKeyboardState;
begin
  GetKeyboardState(KeyState);
  KeyState[VK_CONTROL] := KeyState[VK_CONTROL] or $80;
  SetKeyboardState(KeyState);// control down
  Obj.Perform(WM_KEYDOWN,VK_HOME,0); //ex. with HOME key
  KeyState[VK_CONTROL] := $0;
  SetKeyboardState(KeyState);// control up
end;

...

GoCursorUp(Self);

Or something like this:

  //for example: SHIFT + TAB
  keybd_event(VK_SHIFT, 0, 0, 0);
  keybd_event(VK_TAB, 0, 0, 0);
  keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
0

If you want to simulate keystrokes to minimized windows you can do something like this:

uint windowThreadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero);
uint myThreadId = GetCurrentThreadId();
AttachThreadInput(myThreadId, windowThreadId, true);

Next step is to use GetKeyboardState function to retrieve an array of all keys states of the window thread. Modify the state of SHIFT or CTRL or ALT keys to being pressed using their virtual key codes. Then call SetKeyboardState to apply these states. Press the F12 key:

SendMessage(hwnd, WM_KEYDOWN, Keys.F12, 0);
SendMessage(hwnd, WM_KEYUP, Keys.F12, 0);

Modify back the states of SHIFT, CTRL or ALT keys as being released. Call SetKeyboardState again. Finally, detach from window thread:

AttachThreadInput(myThreadId, windowThreadId, false);
daniel
  • 157
  • 1
  • 1
0

If you don't want to add a dependency to System.Windows.Forms, you could also use Microsoft.Test.Input.Keyboard (documentation here).

// Ctrl+F12
Keyboard.Press(Key.Ctrl);
Keyboard.Type(Key.F12);
Keyboard.Release(Key.Ctrl);

// Shift+F1
Keyboard.Press(Key.Shift);
Keyboard.Type(Key.F1);
Keyboard.Release(Key.Shift);

// Ctrl+R
Keyboard.Press(Key.Ctrl);
Keyboard.Type(Key.R);
Keyboard.Release(Key.Ctrl);