You need to install a global keyboardhook if you want to capture keyboardinput outside of your own form.
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Public Class Form1
Dim kh As New KeyboardHook
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
kh.Hook()
End Sub
Private Sub Form1_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
kh.Unhook()
End Sub
End Class
Public Class KeyboardHook : Implements IDisposable
Public Delegate Function HookCallBack(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As Integer
<DllImport("Kernel32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)>
Public Shared Function GetModuleHandle(ByVal ModuleName As String) As IntPtr : End Function
<DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)>
Public Shared Function SetWindowsHookEx(idHook As Integer, HookProc As HookCallBack,
hInstance As IntPtr, ThreadId As Integer) As IntPtr : End Function
<DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)>
Public Shared Function CallNextHookEx(hHook As IntPtr, nCode As Integer,
wParam As IntPtr, lParam As IntPtr) As Integer : End Function
<DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)>
Public Shared Function UnhookWindowsHookEx(hHook As IntPtr) 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 HookHandle As IntPtr = IntPtr.Zero
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 Function KeyProc(
ByVal nCode As Integer,
ByVal wParam As IntPtr,
ByVal lParam As IntPtr) As Integer
If nCode < 0 OrElse nCode <> HC_ACTION Then Return CallNextHookEx(HookHandle, nCode, wParam, lParam)
Dim hs As KBDLLHOOKSTRUCT = Marshal.PtrToStructure(Of KBDLLHOOKSTRUCT)(lParam)
Dim key As Keys = CType(hs.vkCode, Keys)
Dim scan = hs.scanCode
Select Case wParam.ToInt32
Case WM_KEYDOWN
Debug.Print($"{key} keydown {scan}")
Case WM_KEYUP
Debug.Print($"{key} keyup {scan}")
Case WM_SYSKEYDOWN 'when alt is down it will use these instead
Debug.Print($"{key} syskeydown {scan}")
Case WM_SYSKEYUP
Debug.Print($"{key} syskeyup {scan}")
End Select
Return CallNextHookEx(HookHandle, nCode, wParam, lParam)
End Function
Private mhCallBack As HookCallBack = New HookCallBack(AddressOf KeyProc)
Private disposedValue As Boolean
Public Sub Hook()
If HookHandle <> IntPtr.Zero Then Exit Sub
HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, mhCallBack,
GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0)
If HookHandle = IntPtr.Zero Then Throw New System.Exception("KeyboardHook failed")
End Sub
Public Sub Unhook()
If HookHandle <> IntPtr.Zero Then
UnhookWindowsHookEx(HookHandle)
HookHandle = IntPtr.Zero
End If
End Sub
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects)
End If
' TODO: free unmanaged resources (unmanaged objects) and override finalizer
Unhook()
' TODO: set large fields to null
disposedValue = True
End If
End Sub
' TODO: override finalizer only if 'Dispose(disposing As Boolean)' has code to free unmanaged resources
Protected Overrides Sub Finalize()
' Do not change this code. Put cleanup code in 'Dispose(disposing As Boolean)' method
Dispose(disposing:=False)
MyBase.Finalize()
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in 'Dispose(disposing As Boolean)' method
Dispose(disposing:=True)
GC.SuppressFinalize(Me)
End Sub
End Class
Note that installing a global keyboardhook will be flagged as suspicious by many (if not all) virusscanners.
Read https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-kbdllhookstruct if you need to get more info than just simple up/down.