However, with a dialog window class, all messages for the dialog and for its child windows (common controls) are first passed to the dialog for action.
In PowerBASIC, a dialog can contains a special "Callback" functions to process the incoming messages. Use of the callback function is optional, and in the absence of a callback function messages will be forwarded to the default dialog class window procedure for handling.
In normal (non-dialog) windows, Windows send messages directly to the child control window procedure. With dialog windows, Windows sends the message only to the dialog, which must handle the message.
In support of those messsages which would normally be sent to a child control window procedure, PowerBASIC has chosen to allow programmers to optionally create control level callback functions. However, the dialog only sends %WM_COMMAND and %WM_NOTIFY notification messages to the control callback function. No other messages are sent to the control callback function.
Callback functions provide a return value, indicating whether further message handling is needed. If a control callback asks for further action, the dialog callback receives the message. If the dialog callback asks for further action, the message is passed to the default dialog class window procedure for final processing.
Callback functions are optional. A programmer can chose to respond, or not, to incoming messages. To create a callback function, all that is required is to place the keyword "Callback" in the declaration line of a normal PowerBASIC function, as in this example.
Callback Function DlgProc() As Long ... statement to respond to messages go here End Function
Just like a window procedure a callback function must support four Long arguments. However, you'll note that the example above does not show those four arguments (hWnd, Msg, wParam, lParam). That is because PowerBASIC inserts the arguments in the background, relieving the need for the programmer to include the arguments in the callback declaration.
As was noted in earlier tutorials, Windows provides default window procedures for dialogs and common controls. If no callback functions are provided in a dialog, or if existing callback messages do not take action, messages are sent to the default window procedure for the dialog or child control class depending on which was the intended recipient of the message.
Declaring a Callback Function
When a dialog or control is created, a callback function may be designated to receive the message for that window. Here's a code outline, showing just the code for creating a new dialog with a single textbox control. The code shows how the callback functions for the dialog and child control are assigned. The code also gives an example of the declaration of each callback function.
Function PBMain() Dialog New 0, "",,,200,200 TO hDlg Control Add Textbox, hDlg, %ID_Txt Call TxtProc 'callback is TxtProc Dialog Show Modal Call DlgProc 'callback is DlgProc End Function Callback Function DlgProc() As Long 'dialog callback ... response to messages goes here. End Function Callback Function TxtProc() As Long 'control callback ... response to messages goes here. End Function
As shown in the example above, callback functions are assigned using Control Add and Dialog Show statements.
The callback functions are written just like any other PowerBASIC function, except that they must start with the Callback keyword.
Any valid function name may be used, just as any valid PowerBASIC function code may be placed in a callback function.
As was stated earlier, callback functions are optional. But even when a callback function is used, a programmer may decide which messages to respond to. There is no requirement that a callback function respond to all messages.
When a control callback function completes its response, it should return a value of True (non-zero) to indicate that no further processing is needed. In that case, the message will not be processed by the dialog callback nor will the message be sent to the default common control window procedure for additional processing.
Or, a control callback can return a value of False (zero), to indicate that the message should be passed on to the dialog callback (if one exists) for additional processing. Both the control callback and the dialog callback may be used to respond to a message. If no dialog callback exists, the message will be sent to the default common control window procedure.
When a dialog callback function completes its response to a message, it also should return a True (non-zero) to indicate that no further processing is needed. In that case, the message will not be sent to the default dialog window procedure for additional processing.
Or, a dialog callback can return a value of False (zero) to indicate that the message should be passed on to the default dialog window procedure for additional processing.
Using a Single Callback Function
It is common for PowerBASIC programmers to simply use the dialog callback function to process all messages - those for the dialog and those for child controls. Many programmers consider it easier to manage a single callback function, particularly when the number of child controls is large.
On the other hand, using a child control callback function avoids having to parse incoming messages to determine which child control is involved.
It's a personal choice, with most programmers choosing the single dialog callback.
CB Function - Accessing Content of Windows Messages
As a convenience to the programmer, PowerBASIC reads the Windows OS messages and makes their content available to the programmer via a function called CB, which is only available in a dialog or control callback function.
As shown in the table below, there are CB functions which provide the four Long numbers which make up the Windows message.
CB.Hndl - handle of the parent dialog CB.Msg - message value (such as %WM_COMMAND, %WM_NOTIFY, etc.) CB.wParam - parameter whose value depends on value of CB.MSG CB.lParam - parameter whose value depends on value of CB.MSG
Because the %WM_COMMAND and %WM_NOTIFY messages are so important to PowerBASIC applications (they contain messages from the common controls to the parent dialog), PowerBASIC provides additional CB functions for working with each of those messages.
CB Functions for %WM_COMMAND
The %WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated.
For most PowerBASIC application, particularly those with several child controls, the %WM_COMMAND message is received more often than any other message, so familiarity with it is crucial to writing PowerBASIC applications.
Two CB functions, CB.Ctl and CB.CtlMsg, are provided by PowerBASIC to make working with the %WM_COMMAND messages easier.
Regardless of the message received, the CB functions CB.Ctl and CB.CtlMsg are always available. Their content comes from the wParam message parameter as follows:
CB.Ctl = Lo(Word, CB.wParam) 'user defined Control ID # CB.CtlMsg = Hi(Word, Cb.wParam) 'such as %BN_CLICKED
These equations take advantage of the common practice of Windows messages to place two integers (2 bytes each) into one of the Long (4 bytes) arguments of a message. The Lo and Hi PowerBASIC functions are available to extract the integer values from the message argument. Both wParam and lParam message values are used in this way. Some messages use this technique to pass extra values, and some do not.
The %WM_COMMAND message uses the technique. It places the control ID in the lo word of wParam and the control message notification code in the hi word of wParam.
So, in the case of the %WM_COMMAND messages, both CB.Ctl and CB.CtlMsg simply provide a convenient means of extracting the two Integer values from the Long message argument wParam.
The lParam argument of a %WM_COMMAND message contains the handle of the control windows. So the CB.lParam values can be use to get the control handle when needed.
Here's a simple example of a callback function which examines the CB function values when %WM_COMMAND is received. In this case a textbox with control ID of %ID_Txt is assumed.
Callback Function DlgProc() As Long If CB.Msg = %WM_COMMAND Then If CB.Ctl = %ID_Txt Then Is CB.CtlMsg = %BN_CLICKED Then MsgBox "Clicked!" End if End If End Function
In this example, once the CB.Msg, CB.Ctl, and CB.CtlMsg values are checked to confirm that a specific button was clicked, a MsgBox to that effect was displayed.
CB Functions for %WM_NOTIFY
The %WM_NOTIFY messages is sent by a common control to its parent window when an event has occurred or the control requires some information.
The %WM_COMMAND message was defined early in Windows' development and most common controls use that message. More recent controls, especially the more complicated ones, use the %WM_NOTIFY message. In particular, the statusbar, tab, listview, toolbar and treeview controls use %WM_NOTIFY.
A %WM_NOTIFY message stores its information differently than does the %WM_COMMAND message. In particular, the wParam value of %WM_NOTIFY contains just a single value, the control ID. The lParam contains a pointer to a NMHDR structure, which is defined as follows:
TYPE NMHDR DWORD hwndFrom AS DWORD 'control handle idFrom AS DWORD 'control ID code AS DWORD 'notification code, such as %NM_SETFOCUS END TYPE
To simplify access to the NMHDR data, PowerBASIC provides the following additional CB function values:
CB.NMHWND - handle of control that sent the message CB.NMID - control id (as assigned by PowerBASIC with CONTROL ADD) CB.NMCode - notification code (such as %NM_SETFOCUS) CB.NMHDR - pointer to NMHDR UDT structure CB.NMHDR$ - contents of NMHDR UDT as a string
Note: Even though CB.NMID and CB.wParam appear to have the same values (control ID), MSDN states that the preferred source of control ID is the NMHDR structure.
Here's a simple example of a callback function which examines the CB function values when %WM_NOTIFY is received. In this case a textbox with control ID of %ID_Txt is assumed.
Callback Function DlgProc() As Long If CB.Msg = %WM_NOTIFY Then If CB.NMID = %ID_Txt Then Is CB.NMCode = %NM_SETFOCUS Then MsgBox "Clicked!" End if End If End Function
Also, here's an example of directly accessing the fields of the NMHDR data structure, rather than using the CB function to supply the information. In this case, a message from a tab control is examined.
CallBack Function tabProc() If Cb.Msg = %WM_Notify Then Local pNMHDR As NMHDR Ptr 'pointer to nmhdr structure pNMHDR = Cb.LParam 'set pointer CB.lParam If @pNMHDR.idFrom = %ID_Tab Then 'control ID If @pNMHDR.code = %TCN_SelChange Then 'notification code '... take action End If Enf Id End If End Function
%WM_COMMAND vs %WM_NOTIFY
In the previous examples, the callback functions needed access to the message type, control ID, and control notification code. Just for reference, here's a side-by-side comparison of the CB functions used for the two common message types:
%WM_COMMAND %WM_NOTIFY CB.Msg CB.Msg 'message type CB.lParam CB.NMHWND 'control handle CB.Ctl CB.NMID 'control ID CB.CtlMsg CB.NMCode 'notification code
If you have any suggestions or corrections, please let me know.