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.
OldProc& = SetWindowLong(hWnd&, %GWL_WNDPROC, NewProc&)
OldProc& and NewProc& are the addresses of the old and new window procedures, respectively. See the next paragraph on how to get the new address (NewProc&) to use in the code.
NewProc& = CodePtr(MyFunction)
Typically, a programmer selectively responds to incoming messages. Then, if a message is received for which a response is not made, the message is forwarded on to the original window procedure for taking the normal default action. Here's the code.
CallWindowProc(OldProc&, hWnd, Msg, wParam, lParam)
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.