I am creating a macro program to record and play back mouse and keyboard input. Recording works fine, as does mouse playback, but I am having trouble in playing back the keyboard input - specifically pressing and holding a key for several seconds before releasing. This is not equivalent to repetitively pressing the key. This is what I have tried:
Technique 1: Me.KeyDown
Private Sub keyboard_pressed() Handles Me.KeyDown
Dim keypress = e.KeyData
MsgBox(keypress)
End Sub
Only works when window is in focus.
Technique 2: SendKeys
Private Sub timer_keyboardplayback_Tick() Handles timer_playback.Tick
SendKeys.Send("{LEFT}")
timer_playback.Interval = 30
End Sub
Works out of focus, but repetitively presses left arrow rather than press and hold arrow
Technique 3: keybd_event
Public Declare Sub mouse_event Lib "user32" Alias "mouse_event" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Private Sub timer_keyboardplayback_Tick() Handles timer_playback.Tick
Const keydown = &H1
Const keyup = &H2
Dim VK_LEFT = 37
keybd_event(VK_LEFT, 0, keydown, 0)
End Sub
Works out of focus, but still fails to press hold arrow
Can someone please show me how I can achieve a press & hold of the Left Arrow Key for several seconds, and then release.
The keybd_event
and mouse_event
functions are deprecated as of a few years ago. Instead you should use the SendInput()
function.
Simulating input with it from .NET can sometimes be a bit tricky, fortunately though I've written a library called InputHelper (Download from GitHub) which is a wrapper around SendInput()
. I've customized it so that it covers some of the many different ways of input handling and input simulation, mainly:
SendInput()
).SendInput()
internally).Unfortunately I've not yet had the time to write a proper documentation/wiki about this (apart from the XML documentation on each member of the library, which is shown by Visual Studio's IntelliSense), but so far you can find a little info about creating hooks on the project's wiki.
A short description of what this library consist of:
InputHelper.Hooks
For creating global, low-level mouse/keyboard hooks (utilizes SetWindowsHookEx()
and other related methods). This is partially covered in the wiki.
InputHelper.Keyboard
For handling/simulating physical keyboard input (utilizes SendInput()
and GetAsyncKeyState()
).
InputHelper.Mouse
For handling/simulating physical mouse input (utilizes SendInput()
).
InputHelper.WindowMessages
For handling/simulating virtual mouse/keyboard input, for instance to a specific window (utilizes SendMessage()
and PostMessage()
).
Sending a "physical" keystroke can be done via two functions:
InputHelper.Keyboard.PressKey(Key As Keys, Optional HardwareKey As Boolean)
Sends two keystrokes (down and up) of the specified key.
If
HardwareKey
is set, the function will send the key's Scan Code instead of its Virtual Key Code (default isFalse
).
InputHelper.Keyboard.SetKeyState(Key As Keys, KeyDown As Boolean, Optional HardwareKey As Boolean)
Sends a single keystroke of the specified key.
If
KeyDown
isTrue
the key will be sent as a KEYDOWN event, otherwise KEYUP.
HardwareKey
is the same as above.
You'd use the latter, since you want to control for how long you want the key to be held down.
In order to do this you need to use some sort of timer, like you already do. However to make things a bit more dynamic I've written a function that'll let you specify which key to hold down, and also for how long.
'Lookup table for the currently held keys.
Private HeldKeys As New Dictionary(Of Keys, Tuple(Of Timer, Timer))
''' <summary>
''' Holds down (and repeats, if specified) the specified key for a certain amount of time.
''' Returns False if the specified key is already being held down.
''' </summary>
''' <param name="Key">The key to hold down.</param>
''' <param name="Time">The amount of time (in milliseconds) to hold the key down for.</param>
''' <param name="RepeatInterval">How often to repeat the key press (in milliseconds, -1 = do not repeat).</param>
''' <remarks></remarks>
Public Function HoldKeyFor(ByVal Key As Keys, ByVal Time As Integer, Optional ByVal RepeatInterval As Integer = -1) As Boolean
If HeldKeys.ContainsKey(Key) = True Then Return False
Dim WaitTimer As New Timer With {.Interval = Time}
Dim RepeatTimer As Timer = Nothing
If RepeatInterval > 0 Then
RepeatTimer = New Timer With {.Interval = RepeatInterval}
'Handler for the repeat timer's tick event.
AddHandler RepeatTimer.Tick, _
Sub(tsender As Object, te As EventArgs)
InputHelper.Keyboard.SetKeyState(Key, True) 'True = Key down.
End Sub
End If
'Handler for the wait timer's tick event.
AddHandler WaitTimer.Tick, _
Sub(tsender As Object, te As EventArgs)
InputHelper.Keyboard.SetKeyState(Key, False) 'False = Key up.
WaitTimer.Stop()
WaitTimer.Dispose()
If RepeatTimer IsNot Nothing Then
RepeatTimer.Stop()
RepeatTimer.Dispose()
End If
HeldKeys.Remove(Key)
End Sub
'Add the current key to our lookup table.
HeldKeys.Add(Key, New Tuple(Of Timer, Timer)(WaitTimer, RepeatTimer))
WaitTimer.Start()
If RepeatTimer IsNot Nothing Then RepeatTimer.Start()
'Initial key press.
InputHelper.Keyboard.SetKeyState(Key, True)
Return True
End Function
Example usage:
'Holds down 'A' for 5 seconds, repeating it every 50 milliseconds.
HoldKeyFor(Keys.A, 5000, 50)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With