Visual Basic Hook Routines

SetWindowsHookEx: Customize the API Message Box
Posted:   Tuesday February 06, 2001
Updated:   Monday December 26, 2011
Applies to:   VB5, VB6
Developed with:   VB6, Windows XP
OS restrictions:   None
Author:   VBnet - Randy Birch


SetWindowsHookEx: 'Self-Closing' Message Box using a VB Timer
SetWindowsHookEx: 'Self-Closing' Message Box using SetTimer
SetWindowsHookEx: Detect Caps/Numlock/Scrollock via System-wide Keyboard Hook
SetWindowsHookEx: Customize the API Message Box
SetWindowsHookEx: Trapping Special Key Events using Low Level Hooks
MessageBoxEx: Displaying an API-created Message Box
VB5 / VB6.

Windows provides a series of hooks designed for specific purposes. Each hook type provides access to a different aspect of Windows message-handling mechanism. For example, an application can use the WH_MOUSE hook to monitor the message traffic for mouse messages. The system also maintains a separate hook chain for each type of hook. A hook chain is a list of pointers to special, application-defined callback functions called hook procedures. When a message occurs that is associated with a particular type of hook, the system passes the message to each hook procedure referenced in the hook chain, one after the other.

To take advantage of a particular type of hook, the developer provides a hook procedure using the SetWindowsHookEx function to install it into the chain associated with the hook. In this demo we will construct a standard message box using the MessageBox API, then create a hook for the message box so we can catch the ACTIVATE message sent just prior to the display of the dialog. When the message is received, we'll use SetDlgItemText to change the button captions, message prompt and dialog title to customize the appearance of the dialog.

What this code does is change the existing elements of a Windows MessageBox dialog. What it does not do is change the orientation, size or position of those elements. Therefore, a few preliminary words are necessary.

When a message box is normally created, its size is set to accommodate the wider of the message or title passed, and the buttons are sized corresponding to the size required for the MB_xxx parameter specified. Because, by the time we intercept the HCBT_ACTIVATE message the dialog's dimensions have already been established, subsequent code to change any aspect of the dialog needs to assure that the change fits within the dialog's real estate, including that of the buttons. This means that, without adding significant code to retrieve the size and resize/reposition the dialog and buttons, you are restricted to displaying button text no longer than the original captions. With prudent thought to the captions desired, this shouldn't be too difficult a proposal.

The demo begins in a wrapper function for the MessageBox and SetWindowsHookEx APIs. You may extend the capabilities of the wrapper to include passing the captions and styles. For demo purposes, I chose to use the "Abort, Retry, Ignore" flag for the buttons. One interesting thing that you can do is pass a Space$(x) as the prompt and title parameters. By increasing the string passed, the dialog will grow wider. Similarly, if you pass a series of shorter strings with imbedded vbCrLf's, you can increase the height to accommodate a multi-line prompt.

The public Hook function traps the HCBT_ACTIVATE message, changes the captions and unhooks itself, as no further processing is required.

A message box has pre-defined constants representing the return values from the dialog. In VB we know these as vbOk, vbCancel, vbAbort, etc. In the API their values are the same, but are known as IDOK, IDCANCEL and IDABORT, respectively. Interestingly, and handily, these same return values are also the message box button control ID's, which allows us to use the SetDlgItemText API to change the captions. SetDlgItemText takes the hwnd of the messagebox (returned in the hook as wParam), and the control ID upon which to act. The ID for the prompt text is &HFFFF&.

The thing to remember here is that the values you receive back from the dialog will be the same values as if the dialog was displaying its default button text. So for this example, the "Search C:\" button will return IDABORT, the "Search D:\" button will return IDRETRY, and the the "Cancel" button will return IDIGNORE, because the dialog was created with the MB_ABORTRETRYIGNORE style. Similarly, if you passed vbYesNoCancel as the style, the return values would be vbYes, vbNo and vbCancel (IDYES, IDNO, IDCANCEL in API-speak). You may find it practical, in an application that routinely used different meanings for the buttons, to define your own constants using Const ID_SCAN_C = IDABORT, ID_SCAN_D = IDRETRY, and ID_SCAN_CANCEL = IDIGNORE. Your select case statements would then make more sense.

In order to demonstrate how little code is actually needed to create a hooked message box I have only provided the declares for functions and constants actually used in this demo. There are other constants you might want to review -- you can grab the complete set from the API viewer, or from the MessageBoxEx example page in the demo MessageBoxEx: Displaying an API-created Message Box.

 BAS Module Code
Place the following code into the general declarations area of a bas module:

Option Explicit
' Copyright 1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
'uType parameters
Private Const MB_ICONINFORMATION As Long = &H40&
Private Const MB_ABORTRETRYIGNORE As Long = &H2&
Private Const MB_TASKMODAL As Long = &H2000&

'Windows-defined Return values. The return
'values and control IDs are identical.
Public Const IDOK = 1
Public Const IDCANCEL = 2
Public Const IDABORT = 3
Public Const IDRETRY = 4
Public Const IDIGNORE = 5
Public Const IDYES = 6
Public Const IDNO = 7

'VBnet-defined control ID for the message prompt
Private Const IDPROMPT = &HFFFF&

'misc constants
Private Const WH_CBT = 5
Private Const GWL_HINSTANCE = (-6)
Private Const HCBT_ACTIVATE = 5

'UDT for passing data through the hook
   hwndOwner   As Long
   hHook       As Long
End Type

'need this declared at module level as
'it is used in the call and the hook proc

Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long

Public Declare Function GetDesktopWindow Lib "user32" () As Long

Private Declare Function GetWindowLong Lib "user32" _
   Alias "GetWindowLongA" _
  (ByVal hwnd As Long, _
   ByVal nIndex As Long) As Long

Private Declare Function MessageBox Lib "user32" _
   Alias "MessageBoxA" _
  (ByVal hwnd As Long, _
   ByVal lpText As String, _
   ByVal lpCaption As String, _
   ByVal wType As Long) As Long
Private Declare Function SetDlgItemText Lib "user32" _
   Alias "SetDlgItemTextA" _
  (ByVal hDlg As Long, _
   ByVal nIDDlgItem As Long, _
   ByVal lpString As String) As Long
Private Declare Function SetWindowsHookEx Lib "user32" _
   Alias "SetWindowsHookExA" _
  (ByVal idHook As Long, _
   ByVal lpfn As Long, _
   ByVal hmod As Long, _
   ByVal dwThreadId As Long) As Long
Private Declare Function SetWindowText Lib "user32" _
   Alias "SetWindowTextA" _
  (ByVal hwnd As Long, _
   ByVal lpString As String) As Long

Private Declare Function UnhookWindowsHookEx Lib "user32" _
   (ByVal hHook As Long) As Long

Public Function MessageBoxH(hwndThreadOwner As Long, hwndOwner As Long) As Long

  'Wrapper function for the MessageBox API

   Dim hInstance As Long
   Dim hThreadId As Long
  'Set up the CBT (computer-based training) hook
   hInstance = GetWindowLong(hwndThreadOwner, GWL_HINSTANCE)
   hThreadId = GetCurrentThreadId()

  'set up the MSGBOX_HOOK_PARAMS values
  'By specifying a Windows hook as one
  'of the params, we can intercept messages
  'sent by Windows and thereby manipulate
  'the dialog
      .hwndOwner = hwndOwner
      .hHook = SetWindowsHookEx(WH_CBT, _
                                AddressOf MsgBoxHookProc, _
                                hInstance, hThreadId)
   End With

  'call the MessageBox API and return the
  'value as the result of the function. The 
  'Space$(120) statements assures the messagebox 
  'is wide enough for the message that will 
  'be set in the hook.
  'NOTE: I am setting the text in the hook only 
  'for demo purposes, to show how its done. You 
  'certainly can pass the title and prompt text 
  'right in the API call instead.
   MessageBoxH = MessageBox(hwndOwner, _
                Space$(120), _
                Space$(120), _

End Function

Public Function MsgBoxHookProc(ByVal uMsg As Long, _
                               ByVal wParam As Long, _
                               ByVal lParam As Long) As Long
  'When the message box is about to be shown,
  'we'll change the titlebar text, prompt message
  'and button captions
   If uMsg = HCBT_ACTIVATE Then
     'in a HCBT_ACTIVATE message, wParam holds
     'the handle to the messagebox
      SetWindowText wParam, "VBnet MessageBox Hook Demo"
     'the ID's of the buttons on the message box
     'correspond exactly to the values they return,
     'so the same values can be used to identify
     'specific buttons in a SetDlgItemText call.
      SetDlgItemText wParam, IDABORT, "Search C:\"
      SetDlgItemText wParam, IDRETRY, "Search D:\"
      SetDlgItemText wParam, IDIGNORE, "Cancel"
     'Change the dialog prompt text ...
      SetDlgItemText wParam, IDPROMPT, "MyApp will now locate the application." & _
                                       "Please select the drive to search."
     'we're done with the dialog, so release the hook
      UnhookWindowsHookEx MSGHOOK.hHook
   End If
  'return False to let normal processing continue
   MsgBoxHookProc = False

End Function
 Form Code
Add a text box (Text1) and a command button (Command1) to a form, along with the following code:

Option Explicit

Private Sub Command1_Click()
  'Display our wrapper message box.
  'The first parameter passed is
  'required for GetWindowLong, and
  'specifies the window who's instance
  'will be passed to SetWindowsHookEx.
  'The second parameter is used in the
  'hook and MessageBox call, and indicates
  'who receives error messages and where
  'the dialog will appear.
   Select Case MessageBoxH(Me.hwnd, GetDesktopWindow())
      Case IDABORT:  Text1.Text = "Scan C selected"
      Case IDRETRY:  Text1.Text = "Scan D selected"
      Case IDIGNORE: Text1.Text = "Cancel selected"
   End Select
End Sub
The hook type being used is called a "WH_CBT" hook. The system calls a WH_CBT hook procedure before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the input focus; or before synchronizing with the system message queue. The value the hook procedure returns determines whether the system allows or prevents one of these operations. The WH_CBT hook is intended primarily for computer-based training (CBT) applications.

The CBTProc hook procedure is an application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event from the system message queue; before setting the keyboard focus; or before synchronizing with the system message queue. A computer-based training (CBT) application uses this hook procedure to receive useful notifications from the system.

Other messages sent to a CBTHook procedure include:

Value wParam lParam
HCBT_ACTIVATE Specifies the handle to the window about to be activated. Specifies a long pointer to a CBTACTIVATESTRUCT structure containing the handle to the active window and specifies whether the activation is changing because of a mouse click.
HCBT_CLICKSKIPPED Specifies the mouse message removed from the system message queue. Specifies a long pointer to a MOUSEHOOKSTRUCT structure containing the hit-test code and the handle to the window for which the mouse message is intended.

The HCBT_CLICKSKIPPED value is sent to a CBTProc hook procedure only if a WH_MOUSE hook is installed. For a list of hit-test codes, see WM_NCHITTEST.

HCBT_CREATEWND Specifies the handle to the new window. Specifies a long pointer to a CBT_CREATEWND structure containing initialization parameters for the window. The parameters include the coordinates and dimensions of the window. By changing these parameters, a CBTProc hook procedure can set the initial size and position of the window.
HCBT_DESTROYWND Specifies the handle to the window about to be destroyed. Is undefined and must be set to zero.
HCBT_KEYSKIPPED Specifies the virtual-key code. Specifies the repeat count, scan code, key-transition code, previous key state, and context code. The HCBT_KEYSKIPPED value is sent to a CBTProc hook procedure only if a WH_KEYBOARD hook is installed. For more information, see WM_KEYUP or WM_KEYDOWN.
HCBT_MINMAX Specifies the handle to the window being minimized or maximized. Specifies, in the low-order word, a show-window value (SW_) specifying the operation. For a list of show-window values, see the ShowWindow. The high-order word is undefined.
HCBT_MOVESIZE Specifies the handle to the window to be moved or sized. Specifies a long pointer to a RECT structure containing the coordinates of the window. By changing the values in the structure, a CBTProc hook procedure can set the final coordinates of the window.
HCBT_QS Is undefined and must be zero. Is undefined and must be zero.
HCBT_SETFOCUS Specifies the handle to the window gaining the keyboard focus. Specifies the handle to the window losing the keyboard focus.
HCBT_SYSCOMMAND Specifies a system-command value (SC_) specifying the system command. For more information about system-command values, see WM_SYSCOMMAND. Contains the same data as the lParam value of a WM_SYSCOMMAND message: If a system menu command is chosen with the mouse, the low-order word contains the x-coordinate of the cursor, in screen coordinates, and the high-order word contains the y-coordinate; otherwise, the parameter is not used.


PayPal Link
Make payments with PayPal - it's fast, free and secure!


Copyright 1996-2011 VBnet and Randy Birch. All Rights Reserved.
Terms of Use  |  Your Privacy


Hit Counter