SubClassing
Each window class (windows, dialogs, and common controls) has a default message handling function called the default window procedure. Subclassing is a way of intercepting the messages that would normally go to the default message handler.

The purpose of intercepting those messages is to allow the programmer to customize the response of the window to the messages - customizing both the visual and functional aspects of the window.

Subclassing works by temporarily changing the address of the default window class procedure to the address of a function within the PowerBASIC application (not a Callback function).

If any messages are received that the programmer does not wish to handle, the message can then be forwarded to the original default window procedure for handling.

Note that the need to subclass does not apply when a custom window class is created, since part of the creation is to write a custom window procedure. It is only in the predefined windows classes, such as dialogs and common controls, that the programmer does not have access to the default window procedure.

Step-by-Step
There are two API involved in subclassing, SetWindowLong and CallWindowProc. Additionally, one PowerBASIC function (CodePtr) is needed to provide the address of the substitute PowerBASIC application function. Here is some sample code for each of the steps, followed by examples of how to put all of the steps together into a complete PowerBASIC subclassing application.

The New Window Procedure
The new window procedure declaration must exactly match that of the default window procedure. Fortunately, all window procedures have exactly the same declaration.

Here's the declaration code for a new window procedure.

    Function NewProc(ByVal hWnd As Long, ByVal Msg As Long, _
                    ByVal wParam As Long, ByVal lParam As Long) As Long
    End Function

There's a lot of flexibility as to what goes inside the new window procedure, but in general the approach is to watch for a particular incoming message, take some action, then pass/not pass the message to the original window procedure for additional handling.

Here's sample code of what might be placed in the new window procedure, in this case to be able to respond to a %WM_LButtonUp message.

    Function NewProc(ByVal hWnd As Long, ByVal Msg As Long, _
                    ByVal wParam As Long, ByVal lParam As Long) As Long
       Select Case Msg
          Case %WM_LButtonUp   'any mouse message might be here
             'respond to left button up 
          Case Else
             CallWindowProc(OldProc&, hWnd, Msg, wParam, lParam)
       End Select
    End Function

The previous example assumes that the response to %WM_LButtonUp does not require any action to be taken by the default window procedure (OldProc&). In many cases, the action a programmer takes is in addition to default processing. In that case the code might look like this next example, which the incoming message is always sent to the original window procedure.

    Function NewProc(ByVal hWnd As Long, ByVal Msg As Long, _
                    ByVal wParam As Long, ByVal lParam As Long) As Long
       Select Case Msg
          Case %WM_LButtonUp   'any mouse message might be here
             'respond to left button up 
             'but additional response is needed
       End Select
       CallWindowProc(OldProc&, hWnd, Msg, wParam, lParam)
    End Function

Finally, the CallWindowProc could be selectively used, such as in this example where two message types are intercepted - one which requires not further response, and one which does.

    Function NewProc(ByVal hWnd As Long, ByVal Msg As Long, _
                    ByVal wParam As Long, ByVal lParam As Long) As Long
       Select Case Msg
          Case %WM_LButtonDown
             'respond to left button down - do not forward to OldProc&
          Case %WM_LButtonUp
             'respond to left button up - then forward to OldProc&
             CallWindowProc(OldProc&, hWnd, Msg, wParam, lParam)
          Case Else
             CallWindowProc(OldProc&, hWnd, Msg, wParam, lParam)
       End Select
    End Function

Complete Example
Here's a simple application showing how a label might be subclassed to respond to a left mouse down message (%WM_LButtonDown).

Normally, that message is sent directly to the default label window procedure and is not available to a PowerBASIC application. But with subclassing, the message can be intercepted and action taken. In this example, the message response is all that is required, so the message is not forwarded on to the default window procedure.

    #Compile Exe
    #Dim All
    #Include "win32api.inc"

    Global hDlg As Dword, OrigProc&, hLabel As Dword

    Function PBMain()
        Dialog New Pixels, 0, "Subclassing",300,300,150,100, _
                                  %WS_OverlappedWindow To hDlg
        Control Add Label, hDlg, 100, "Subclassed Label", _
                         10,20,120,20, %WS_Border Or %SS_Notify
        Control Add Label, hDlg, 102, "Not Subclassed Label", _
                  10,50,120,20, %WS_Border Or %SS_Notify Call LabelProc
        SubClassLabels
        Dialog Show Modal hdlg
    End Function

    Sub SubClassLabels
        Local NewProc&
        Control Handle hDlg, 100 To hLabel
        NewProc& = CodePtr(NewLabelProc)
        OrigProc& = SetWindowLong(hLabel, %GWL_WNDPROC, NewProc&)
    End Sub

    CallBack Function LabelProc() As Long
        If Cb.Msg = %WM_Command And Cb.CtlMsg = %WM_LButtonDown Then
           Control Set Text hDlg, 102, "Left Button Down"
        End If
    End Function

    Function NewLabelProc(ByVal hWnd As Long, ByVal Msg As Long, _
               ByVal wParam As Long, ByVal lParam As Long) As Long
        Control Set Text hDlg, 100, ""
        Select Case Msg
            Case %WM_LButtonDown
                Control Set Text hDlg, 100, "Left Button Down"
            Case Else
                CallWindowProc(OrigProc&, hWnd, Msg, wParam, lParam)
        End Select
    End Function  

By clicking on both labels in the example, you'll see that the subclassed label can respond to a %WM_LButtonDown, whereas the non-subclassed label cannot.

Restoring the Default Window Procedure
At some point in the program it may be desirable to allow the default window procedure to handle all future messages. To do this, simply use the SetWindowLong API again, this time using the saved address of the original default window process. Here's the code.

    SetWindowLong(hWnd, %GWL_WNDPROC, OrigProc&)

Many programmers always use this code when their program ends, to specifically end subclassing. However, it is not a requirement since the window that is being subclassed will end along with the application. Not resetting the default window procedure will not an application error on shutdown.

If you have any suggestions or corrections, please let me know.