|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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 |
Related: |
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 |
Prerequisites | |||||||||||||||||||||||||||||||||
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 Private Type MSGBOX_HOOK_PARAMS 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 MSGHOOK As MSGBOX_HOOK_PARAMS 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 With MSGHOOK .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), _ MB_ABORTRETRYIGNORE Or MB_ICONINFORMATION) 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 |
|||||||||||||||||||||||||||||||||
Comments | |||||||||||||||||||||||||||||||||
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:
|
|||||||||||||||||||||||||||||||||
|
|
|
|||||
|
|||||
|
|||||
Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved. |