Home > Software design >  How to trigger keydown and keyup events from external applications in VB.NET? avoid sendkeys method
How to trigger keydown and keyup events from external applications in VB.NET? avoid sendkeys method

Time:12-26

I am trying to develop a keystroke macro program using vb.net which will record keystrokes like keydown and keyup events and then play it anywhere while the main program is running in the background. As of now I have successfully captured the keystrokes and stored those strokes. But the problem which I m facing, is at the time of playing those stored keystrokes. I can't fire the KeyDown and KeyUp events from any external program. I have tried SendKeys method as well but it cannot differentiate between KeyDown and KeyUp separately. Help in this scenario will highly be appreciated.

The KeyDown event which is accessible in parent program only.

Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
        'MessageBox.Show(e.KeyCode)
        'bla bla bla
End Sub

'Use of SendKeys but it cannot distinguish between KeyDown and KeyUp

Private Function AutoSendKey(ByVal keystroke As String, ByVal delay As Integer)     
        System.Threading.Thread.Sleep(delay)
        My.Computer.Keyboard.SendKeys(keystroke, True)
    End Function

I need an approach to trigger KeyDown and KeyUp events from external applications. Thanks in advance

CodePudding user response:

I just was looking for the same thing and found few resources and then managed to make it into one application:

Basically this will work unless you use some sort of intercept on a driver level, in which case the keyboard that is intercepted by the intercept application wont be visible to windows and thus this script is useless.

Other than that, here is my code. This code is optimized for .NET 5.0

First create KeyboardHook.vb class:

Imports System.Runtime.InteropServices

Public Class KeyboardHook

    <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)>
    Private Overloads Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal HookProc As KBDLLHookProc, ByVal hInstance As IntPtr, ByVal wParam As Integer) As Integer
    End Function
    <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)>
    Private Overloads Shared Function CallNextHookEx(ByVal idHook As Integer, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
    End Function
    <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)>
    Private Overloads Shared Function UnhookWindowsHookEx(ByVal idHook As Integer) As Boolean
    End Function

    <StructLayout(LayoutKind.Sequential)>
    Private Structure KBDLLHOOKSTRUCT
        Public vkCode As UInt32
        Public scanCode As UInt32
        Public flags As KBDLLHOOKSTRUCTFlags
        Public time As UInt32
        Public dwExtraInfo As UIntPtr
    End Structure

    <Flags()>
    Private Enum KBDLLHOOKSTRUCTFlags As UInt32
        LLKHF_EXTENDED = &H1
        LLKHF_INJECTED = &H10
        LLKHF_ALTDOWN = &H20
        LLKHF_UP = &H80
    End Enum

    Public Shared Event KeyDown(ByVal Key As Key)
    Public Shared Event KeyUp(ByVal Key As Key)

    Private Const WH_KEYBOARD_LL As Integer = 13
    Private Const HC_ACTION As Integer = 0
    Private Const WM_KEYDOWN = &H100
    Private Const WM_KEYUP = &H101
    Private Const WM_SYSKEYDOWN = &H104
    Private Const WM_SYSKEYUP = &H105

    Private Delegate Function KBDLLHookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer

    Private KBDLLHookProcDelegate As KBDLLHookProc = New KBDLLHookProc(AddressOf KeyboardProc)
    Private HHookID As IntPtr = IntPtr.Zero

    Private Function KeyboardProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
        If (nCode = HC_ACTION) Then
            Dim struct As KBDLLHOOKSTRUCT
            Select Case wParam
                Case WM_KEYDOWN, WM_SYSKEYDOWN
                    Dim aKey = KeyInterop.KeyFromVirtualKey(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode)
                    RaiseEvent KeyDown(aKey)
                Case WM_KEYUP, WM_SYSKEYUP
                    Dim aKey = KeyInterop.KeyFromVirtualKey(CType(Marshal.PtrToStructure(lParam, struct.GetType()), KBDLLHOOKSTRUCT).vkCode)
                    RaiseEvent KeyUp(aKey)
            End Select
        End If
        Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam)
    End Function

    Public Sub New()
        HHookID = SetWindowsHookEx(WH_KEYBOARD_LL, KBDLLHookProcDelegate, System.Diagnostics.Process.GetCurrentProcess().MainModule.BaseAddress, 0)
        If HHookID = IntPtr.Zero Then
            Throw New Exception("Could not set keyboard hook")
        End If
    End Sub

    Protected Overrides Sub Finalize()
        If Not HHookID = IntPtr.Zero Then
            UnhookWindowsHookEx(HHookID)
        End If
        MyBase.Finalize()
    End Sub

End Class

Now create your own class/usercontrol with KeyboardHook event handlers.

For me this was simple OnScreenKBView.xaml with textblocks that had text property bound to a viewmodel class properties, which shows what keys are pressed down and released up and how many keys are pressed at the same time:

 <Grid>
        <Border Background="Red" >
            <StackPanel Orientation="Vertical">
                <TextBlock Text="KeyDown:"/>
                <TextBlock Text="{Binding keyDown, Converter={StaticResource KeyToStringVC}}"/>
                <TextBlock Text="KeyUp:"/>
                <TextBlock Text="{Binding keyUp, Converter={StaticResource KeyToStringVC}}"/>
                <TextBlock Text="TotalKeysPressed at the same time:"/>
                <TextBlock Text="{Binding keysPressed, Converter={StaticResource ListToCountVC}}"/>
                <TextBlock Text="KeysPressed:"/>
                <TextBlock Text="{Binding keysPressed, Converter={StaticResource ListToStringVC}}" TextWrapping="Wrap"/>
            </StackPanel>
           
        </Border> 
    </Grid>

Notice I have few value converters KeyToStringVC, ListToStringVC and ListToCountVC.

KeyToStringVC:

 Return value.ToString

ListToCountVC:

        If Not value Is Nothing Then
            Return value.count
        Else
            Return Nothing
        End If

ListToStringVC :

        Dim kl As ObservableCollection(Of Key) = value
        Dim str As String = Nothing
        If Not kl Is Nothing Then
            For Each item In kl
                str  = item.ToString & "; "
            Next
        End If
        Return str

You will have to look up how to use/create value converters if you dont know how.

VB code behind the OnScreenKBView.xaml:

Imports System.Collections.ObjectModel

Public Class OnScreenKBView


    Private WithEvents kbHook As New KeyboardHook

    Private viewModel As OnScreenKBViewModel



    Private Sub kbHook_KeyDown(ByVal Key As Key) Handles kbHook.KeyDown
        viewModel.keyDown = Key


        'check if list already has the key in it

        Dim hasKey As Boolean = False

        If Me.viewModel.keysPressed.Contains(Key) Then
            hasKey = True
        End If
        If Not hasKey Then
            Me.viewModel.keysPressed.Add(Key)

            Dim localCol = Me.viewModel.keysPressed
            Dim newCol = New ObservableCollection(Of Key)(From i In localCol Select i)
            Me.viewModel.keysPressed = newCol
        End If

    End Sub
    Private Sub kbHook_KeyUp(ByVal Key As Key) Handles kbHook.KeyUp
        viewModel.keyUp = Key
        Me.viewModel.keysPressed.Remove(Key)

        Dim localCol = Me.viewModel.keysPressed
        Dim newCol = New ObservableCollection(Of Key)(From i In localCol Select i)
        Me.viewModel.keysPressed = newCol

    End Sub

    Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.viewModel = Application.Current.MainWindow.DataContext
    End Sub
End Class

This should give you some ideas i hope. If ya need whole project i have it on github but its private, i could zip it for ya.

Lil demo: GIF here

CodePudding user response:

Imports System.Runtime.InteropServices
Imports System.Windows.Forms 'for the keys. enumeration

Public Module SendWinKey
    Const KEYEVENTF_KEYDOWN As Integer = &H0
    Const KEYEVENTF_KEYUP As Integer = &H2

    
       Declare Sub keybd_event Lib "User32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As UInteger, ByVal dwExtraInfo As UInteger)

Public Sub Main()    
        keybd_event(CByte(Keys.LWin), 0, KEYEVENTF_KEYDOWN, 0) 'press the left Win key down
        keybd_event(CByte(Keys.R), 0, KEYEVENTF_KEYDOWN, 0) 'press the R key down
        keybd_event(CByte(Keys.R), 0, KEYEVENTF_KEYUP, 0) 'release the R key
        keybd_event(CByte(Keys.LWin), 0, KEYEVENTF_KEYUP, 0) 'release the left Win key
    End Sub
End Module

As you can see it is really simple.

  • Related