SubClassingThe 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.