Subclassing a Windows Form

Intercepts WM_KEYDOWN messages


Imports System.Runtime.InteropServices

Public Class Form1
    Inherits Form

    ' Constants
    Private Const GWL_WNDPROC As Integer = -4
    Private Const WM_KEYDOWN As Integer = &H100

    Private oldWndProc As IntPtr
    Private newWndProcDelegate As WndProcDelegate
    Private newWndProcPtr As IntPtr

    ' Delegate definition
    Private Delegate Function WndProcDelegate(hWnd As IntPtr, msg As UInteger, wParam As IntPtr, lParam As IntPtr) As IntPtr

    ' Platform check
    Private Shared ReadOnly is64Bit As Boolean = IntPtr.Size = 8

    ' API declarations
    <DllImport("user32.dll", EntryPoint:="SetWindowLong", SetLastError:=True)>
    Private Shared Function SetWindowLong32(hWnd As IntPtr, nIndex As Integer, dwNewLong As IntPtr) As IntPtr
    End Function

    <DllImport("user32.dll", EntryPoint:="SetWindowLongPtr", SetLastError:=True)>
    Private Shared Function SetWindowLong64(hWnd As IntPtr, nIndex As Integer, dwNewLong As IntPtr) As IntPtr
    End Function

    <DllImport("user32.dll", EntryPoint:="CallWindowProc", SetLastError:=True)>
    Private Shared Function CallWindowProc32(lpPrevWndFunc As IntPtr, hWnd As IntPtr, msg As UInteger, wParam As IntPtr, lParam As IntPtr) As IntPtr
    End Function

    ' Cross-platform wrapper
    Private Shared Function SetWindowProc(hWnd As IntPtr, nIndex As Integer, newProc As IntPtr) As IntPtr
        If is64Bit Then
            Return SetWindowLong64(hWnd, nIndex, newProc)
        Else
            Return SetWindowLong32(hWnd, nIndex, newProc)
        End If
    End Function

    Private Shared Function CallWindowProcWrapper(prevWndFunc As IntPtr, hWnd As IntPtr, msg As UInteger, wParam As IntPtr, lParam As IntPtr) As IntPtr
        Return CallWindowProc32(prevWndFunc, hWnd, msg, wParam, lParam)
    End Function

    ' Setup subclassing
    Protected Overrides Sub OnHandleCreated(e As EventArgs)
        MyBase.OnHandleCreated(e)

        newWndProcDelegate = AddressOf CustomWndProc
        newWndProcPtr = Marshal.GetFunctionPointerForDelegate(newWndProcDelegate)

        oldWndProc = SetWindowProc(Me.Handle, GWL_WNDPROC, newWndProcPtr)
    End Sub

    Protected Overrides Sub OnHandleDestroyed(e As EventArgs)
        SetWindowProc(Me.Handle, GWL_WNDPROC, oldWndProc)
        MyBase.OnHandleDestroyed(e)
    End Sub

    ' Custom WndProc
    Private Function CustomWndProc(hWnd As IntPtr, msg As UInteger, wParam As IntPtr, lParam As IntPtr) As IntPtr
        If msg = WM_KEYDOWN Then
            Dim keyCode As Integer = wParam.ToInt32()
            MessageBox.Show($"Key Pressed: {ChrW(keyCode)}", "DWOKSUBCLASS")
        End If

        Return CallWindowProcWrapper(oldWndProc, hWnd, msg, wParam, lParam)
    End Function
End Class

Download 'Subclassing a Windows Form':

📥 Download subclassing-a-windows-form.vb