Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VB Press and hold keystroke

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.

like image 329
Legion_bre Avatar asked Jan 22 '18 13:01

Legion_bre


1 Answers

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:

  • Simulate keystrokes (internally utilizing SendInput()).
  • Simulate mouse movement and mouse button clicks (also utilizing SendInput() internally).
  • Send virtual keystrokes and mouse clicks to the current/a specific window (internally utilizing Window Messages).
  • Create global, low-level mouse and keyboard hooks.

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 keystroke

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 is False).

  • InputHelper.Keyboard.SetKeyState(Key As Keys, KeyDown As Boolean, Optional HardwareKey As Boolean)

    Sends a single keystroke of the specified key.

    If KeyDown is True 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.


Holding a key down for the specified amount of time

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)
like image 80
Visual Vincent Avatar answered Nov 13 '22 02:11

Visual Vincent