Wednesday, December 17, 2008

[vb6] Subclass forms and controls

Windows continually sends messages to windows, including the forms and controls in a VB application. Visual Basic takes care of responding to all these messages for you. Some messages are passed on to you in the form of control or form events, after VB has done anything it needs to do. Sometimes, this is not good enough. Maybe parameters passed to your events are missing (as in the WM_PAINT) message, maybe the VB response to a message is unwanted or maybe VB does not pass the message on to the developer at all. Then there is a case for subclassing. When Windows "sends a message" what is actually going on is Windows calling a function in the app. For normal VB programs, this function is a function in the VB run-time library. When you subclass a form a control, you tell Windows not to call the function in the VB run-time, but to call a function you supply. In order for this to work you must write a WndProc function with a certain number and type of arguments (as below) and obtain the address of that function. The function has to be in a standard module and you use the VB AddressOf operator to obtain its address. As an example, paste the code below into a standard module. This particular example subclasses the Form1 form and responds to just one message - the WM_GETMINMAXINFO - passing all other messages to the old VB window procedure. If you want to listen to other messages, you must implement code to handle them and get the appropriate WM_ constant from your favourite API viewer or the Win32 documentation.
    Public Declare Function CallWindowProc Lib "User32" Alias _
"CallWindowProcA" (ByVal lpPrevWndFunc As Long, _
ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long
Public Declare Function SetWindowLong Lib "User32" Alias _
"SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
Public Const GWL_WNDPROC = (-4)
Public m_oldProc As Long 'The old VB window procedure for form1
Private Const WM_GETMINMAXINFO = &H24

Public Function WndProc(ByVal hwnd As Long, ByVal uMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long

Dim R As Long

If uMsg = WM_GETMINMAXINFO Then
'Do your processing here ...
'Here: do or don't call the VB window procedure (as below)
Else
'For messages you don't care about, pass it on to VB
R = CallWindowProc(m_oldProc, Form1.hwnd, uMsg, wParam, lParam)
End If

WndProc = R
End Function
In addition, the subclassing needs to be set up and also discontinued at some point - here, in the Form_Load and Form_Unload procedures of Form1, respectively:
    Private Sub Form_Load()
m_oldProc = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WndProc)
End Sub

Private Sub Form_Unload(Cancel As Integer)
SetWindowLong hwnd, GWL_WNDPROC, m_oldProc
End Sub
The above subclassing method works just fine and is probably the most efficient one you're likely to get. However, it is suited only for debugged programs being compiled for release. That is because VB will crash when it hits a breakpoint in your code, essentially making it impossible to debug a program (!). The solution is to use a dll or ocx subclassing component. You can buy one or get one for free.

No comments:

Post a Comment