Search code examples
.netvb.neteventstextboxwindows-messages

Adapt windows messages in this class


I would like to know if this code can be adapted to use it inside a normal class (Example: Form1 Class) to don't need subclassing a Control.

Code is from BlueMonkMN here Capture the option selected by the user in a windows default contextmenu?

Class MyTextBox : Inherits TextBox
  Public Enum ContextCommands
     WM_CUT = &H300
     WM_COPY = &H301
     WM_PASTE = &H302
  End Enum

  Public Class ContextCommandEventArgs
     Inherits EventArgs
     Public Property Command As ContextCommands
  End Class

  Event OnCut(sender As Object, e As ContextCommandEventArgs)
  Event OnCopy(sender As Object, e As ContextCommandEventArgs)
  Event OnPaste(sender As Object, e As ContextCommandEventArgs)

  Protected Overrides Sub WndProc(ByRef m As Message)
     MyBase.WndProc(m)
     Select Case m.Msg
        Case ContextCommands.WM_CUT
           RaiseEvent OnCut(Me, New ContextCommandEventArgs() With {.Command = ContextCommands.WM_CUT})
        Case ContextCommands.WM_COPY
           RaiseEvent OnCopy(Me, New ContextCommandEventArgs() With {.Command = ContextCommands.WM_COPY})
        Case ContextCommands.WM_PASTE
           RaiseEvent OnPaste(Me, New ContextCommandEventArgs() With {.Command = ContextCommands.WM_PASTE})
     End Select
  End Sub
End Class

To understand better my problem, I'll show you what I've tried to adapt it:

Public Class Form1

    Public Enum ContextCommands
        WM_CUT = &H300
        WM_COPY = &H301
        WM_PASTE = &H302
    End Enum

    Public Class ContextCommandEventArgs
        Inherits EventArgs
        Public Property Command As ContextCommands
    End Class

    Event OnCut(TextBox1 As Object, e As ContextCommandEventArgs)
    Event OnCopy(TextBox1 As Object, e As ContextCommandEventArgs)
    Event OnPaste(TextBox1 As Object, e As ContextCommandEventArgs)

    Protected Overrides Sub WndProc(ByRef m As Message)
        MyBase.WndProc(m)
        Select Case m.Msg
            Case ContextCommands.WM_CUT
                RaiseEvent OnCut(TextBox1, New ContextCommandEventArgs() With {.Command = ContextCommands.WM_CUT})
            Case ContextCommands.WM_COPY
                RaiseEvent OnCopy(TextBox1, New ContextCommandEventArgs() With {.Command = ContextCommands.WM_COPY})
            Case ContextCommands.WM_PASTE
                RaiseEvent OnPaste(TextBox1, New ContextCommandEventArgs() With {.Command = ContextCommands.WM_PASTE})
        End Select
    End Sub

Private Sub TextBox1_OnTextCommand() Handles TextBox1.TextChanged
    If WM_CUT then...
    Elseif WM_COPY then...
    Elseif WM_Paste then...
    End if
End Sub

Private Sub TextBox1_OnTextCommand() Handles Me.OnPaste, Me.OnCopy, Me.OnCut
    MsgBox("Activated")
End Sub

End Class

Solution

  • This is not a simple task for a VB developer, but I hope this helps. The code no longer relates to a specific textbox, but you should be able to identify which textbox caused the event by comparing cwp.hWnd with TextBox.Handle values if you need to. EditMenuHook.Enable should probably only be called once at the beginning of your application to turn on the hook, and once at the end to turn it off.

    Imports System.Runtime.InteropServices
    
    Public Class Form1
       Protected Overrides Sub OnLoad(e As EventArgs)
          MyBase.OnLoad(e)
          EditMenuHook.Enable(True)
       End Sub
    
       Protected Overrides Sub OnClosed(e As EventArgs)
          EditMenuHook.Enable(False)
          MyBase.OnClosed(e)
       End Sub
    End Class
    
    Friend Class EditMenuHook
       Public Structure CWPSTRUCT
          Public lParam As IntPtr
          Public wParam As IntPtr
          Public message As UInt32
          Public hWnd As IntPtr
       End Structure
    
       Public Delegate Function CallBack( _
           ByVal nCode As Integer, _
           ByVal wParam As IntPtr, _
           ByVal lParam As IntPtr) As Integer
    
       Shared hHook As Integer = 0
    
       'Import for the SetWindowsHookEx function.
       <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
       Public Overloads Shared Function SetWindowsHookEx _
              (ByVal idHook As Integer, ByVal HookProc As CallBack, _
               ByVal hInstance As IntPtr, ByVal wParam As Integer) As Integer
       End Function
    
       'Import for the CallNextHookEx function.
       <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
       Public Overloads Shared Function CallNextHookEx _
              (ByVal idHook As Integer, ByVal nCode As Integer, _
               ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
       End Function
    
       'Import for the UnhookWindowsHookEx function.
       <DllImport("User32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
       Public Overloads Shared Function UnhookWindowsHookEx _
                  (ByVal idHook As Integer) As Boolean
       End Function
    
       Private Const WH_CALLWNDPROC = 4
    
       Public Enum TextCommandMessage
          WM_CUT = &H300
          WM_COPY = &H301
          WM_PASTE = &H302
       End Enum
    
       'Keep the reference so that the delegate is not garbage collected.
       Private Shared hookproc As CallBack
    
       Shared Sub Enable(enable As Boolean)
          If hHook = 0 AndAlso enable = True Then
             hookproc = AddressOf EditCommandHook
             hHook = SetWindowsHookEx(WH_CALLWNDPROC, _
                                      hookproc, _
                                      IntPtr.Zero, _
                                      AppDomain.GetCurrentThreadId())
             If hHook.Equals(0) Then
                MsgBox("SetWindowsHookEx Failed")
                Return
             End If
          ElseIf hHook <> 0 AndAlso enable = False Then
             Dim ret As Boolean = UnhookWindowsHookEx(hHook)
    
             If ret.Equals(False) Then
                MsgBox("UnhookWindowsHookEx Failed")
                Return
             Else
                hHook = 0
             End If
          End If
       End Sub
    
       Public Shared Function EditCommandHook( _
          ByVal nCode As Integer, _
          ByVal wParam As IntPtr, _
          ByVal lParam As IntPtr) As Integer
    
          If nCode < 0 Then
             Return CallNextHookEx(hHook, nCode, wParam, lParam)
          End If
    
          Dim cwp = DirectCast(System.Runtime.InteropServices.Marshal.PtrToStructure(lParam, GetType(CWPSTRUCT)), CWPSTRUCT)
    
          Select Case cwp.message
             Case TextCommandMessage.WM_CUT
                System.Diagnostics.Debug.WriteLine(String.Format("Cut {0}", cwp.hWnd))
             Case TextCommandMessage.WM_COPY
                System.Diagnostics.Debug.WriteLine(String.Format("Copy {0}", cwp.hWnd))
             Case TextCommandMessage.WM_PASTE
                System.Diagnostics.Debug.WriteLine(String.Format("Paste {0}", cwp.hWnd))
          End Select
    
          Return CallNextHookEx(hHook, nCode, wParam, lParam)
       End Function
    End Class