Getting Started
Introduction
Sample Programs
IDEs
History
Advice
Mini-Tutorial
Tutorials
Code Snippets

Resources
Web Sites
More Tutorials
Forums
Vendors/Tools
Books
Magazines
Newsletters
NewsGroups
User Groups
Talk Shows
Blogs

Controls
Overview
Button
Check3State
Checkbox
ComboBox
Frame
Graphic
Image
ImageX
ImgButton
ImgButtonX
Label
Line
ListBox
ListView
Option
Progress Bar
Scrollbar
StatusBar
TAB
TextBox
Toolbar
TreeView

GBIC >> PowerBASIC >> Tutorials >> CallBack Functions

PowerBASIC Information Center Tutorials
These tutorials were written to help you get a quick, but thorough, understanding of PowerBASIC - the scope of the language as well as it's specific capabilities.

Introduction       Projects       Language           Messages       Functions           Advanced
  • Overview
  • Examples
  • IDE
  • Compilation
  • Distribution
  • Project Files
  • DDT Windows
  • Controls
  • Menus
  • Dialogs  
  • Help Files
  • Resources  
  • Templates  
  • Project Shell  
  • Syntax
  • Operators
  • Data Types
  • Variables
  • Scope
  • Declarations  
  • Procedures
  • Flow Control
  • Windows
  • Messages
  • Callbacks
  • Mouse
  • Keyboard
  • Dialogs
  • Controls
  • Subclassing
  • Arithmetic
  • Trig  
  • Strings
  • Arrays
  • Date/Time
  • Printing
  • Files
  • Folders
  • Keyboard
  • Mouse
  • Sound
  • System
  • Error Traps
  • Debugging
  • Objects
  • Graphics
  • Databases
  • API
  • DLLs
  • ASM
  • Threads
  • Callback Functions - Responding to Messages
    As was discussed in the tutorials on windows and messages, Windows sends messages to the window procedure for all windows - including the pre-defined windows such as dialogs and common controls.

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

    Redirecting Messages
    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.