Callbacks

A callback is a Function called by Windows when an event occurs. In the previous modal dialog example, when the OK button is clicked by the user, Windows calls the OkButton() function. PowerBASIC's Dynamic Dialog Tools allows you to create a single callback to handle all events for the dialog, or you can create individual Callback Functions for each control in your dialog. (You can even use a combination of the two methods!).

 

Control Callback

If you are a Visual Basic user, you will be familiar with a Control Callback although Microsoft does not call it that. A Control Callback is a function that is called when a %WM_COMMAND event occurs for a given control. In the earlier example, we arranged it so when the OK button was clicked, the OkButton() function was called. Conversely, when the Cancel button was clicked, the CancelButton() function was called.

CONTROL ADD BUTTON, hDlg, %IDOK, "OK", 34, 32, 40, 14, %BS_DEFAULT OR %WS_TABSTOP CALL OkButton

CONTROL ADD BUTTON, hDlg, %IDCANCEL, "Cancel", 84, 32, 40, 14 CALL CancelButton

In Visual Basic when you add a button to a form, a callback is created on the form with a name like Command1_Click(). When the VB program is running and the user clicks on the button, the Command1_Click() function is called.

Some controls, such as text boxes, list boxes, and combo boxes, can generate more than one type of event. In VB, each separate event is handled by a new function. For example, if your VB form includes a list box, it may include a Callback Function such as List1_Change() that is called whenever the current selected item changes. In PowerBASIC, only a single Callback Function is allowed for each control, and the Callback Function code must choose the events the callback should respond to. If your PowerBASIC callback wanted to process the Change event for a list box, your code would look like this:

CALLBACK FUNCTION List1() AS LONG

  IF CBCTLMSG = %LBN_SELCHANGE THEN

    ... your code here ...

    FUNCTION = 1

  END IF

END FUNCTION

The CBCTLMSG function tells your code exactly which event has occurred. The control notification %LBN_SELCHANGE is sent to the callback for the list box whenever the item in the list box changes (the user clicks on the new item or uses the keyboard to select a new item).

All of the control and dialog message equates are located in the DDT.INC file. This file is simply a subset of the much larger WIN32API.INC file and is provided only for convenience. Therefore, the use of these two files is mutually exclusive.

If your code processes one of these event messages, it should return non-zero to Windows, by setting FUNCTION = 1 within the Control Callback. This tells Windows that it should not need to process that message any further. If you return zero, Windows will pass this message on to your Dialog Callback. If the message is still unhandled by your Dialog Callback, the DDT dialog engine itself will handle the message on your behalf. This can add unnecessary overhead to the performance of your application.

Also see the notes in the Dialog Callback section that follows, on returning values from a callback function.

When a Control Callback receives a click notification for a control, the callback will receive a %WM_COMMAND message in the CBMSG variable. A common mistake made by programmers is to fail to test the CBCTLMSG parameter before responding to the message. If the message is truly generated from a click event, CBCTLMSG will contain %BN_CLICKED. This simple test ensures that your code does not react in an inappropriate manner to other notification messages (such as focus change notifications).

CALLBACK FUNCTION OkButton() AS LONG

  IF CBMSG = %WM_COMMAND AND CBCTLMSG = %BN_CLICKED THEN

    '...Process the click event here

    FUNCTION = 1

  END IF

END FUNCTION

It pays to be sure you are responding to the correct message in your callback. This can help avoid subtle bugs in your GUI code that may occur if the style of a control is changed, causing unanticipated messages being directed to your control Callback Function.

It should also be noted that there are ranges of notification messages that individual controls can send to the Control (or Dialog Callback); however, many of these messages are suppressed unless the controls have been initially assigned a "notify" style. For controls that are members of the Button class (CHECKBOX, OPTION, FRAME, etc), this is the %BS_NOTIFY style. Please refer to the CONTROL ADD statements for additional information on notification styles for other control types. Additionally, %WM_NOTIFY messages that originate from many Common Controls are always sent to the callback for the parent dialog, and are not sent to Control Callback functions.

 

Dialog callback

If you review the example code in most Windows programming books (particularly the Windows 32-bit SDK), you will see that most of the examples create a single callback for the entire dialog. Each time the user presses a button, a message is sent to this Callback Function. Within this Callback Function, there is often a large SELECT CASE or IF/ELSEIF/THEN structure, designed to pick out the incoming event messages and then process the desired messages.

C programmers are usually quite familiar with this concept, and often resort to using "Message Cracker" functions to separate their event handling code into a set of independent functions. On the other hand, PowerBASIC's DDT takes much of this drudgery away. By permitting separate callbacks for each CONTROL ADD statement, you become free to enclose your event handling code in separate functions, just like a C programmer may do but without the confusing macros C programmers often resort to.

DDT gives the programmer the choice of either using a single callback to handle all dialog and control events, or writing a callback for each (or any) specific control. If you intentionally omit a callback for a given control, the programmer now has the choice of handling messages for that control within the dialog Callback Function, or ignoring them altogether.

In addition to handling control messages within the dialog callback, this Callback Function also provides a way to handle events that concern the actual dialog box itself. For example, handling a %WM_PAINT message, or notification that the dialog was minimized, etc.

To specify a dialog callback in our code, we extend the DIALOG SHOW statement as follows:

DIALOG SHOW MODELESS hDlg CALL DlgProc TO lResult&

or:

DIALOG SHOW MODAL hDlg CALL DlgProc TO lResult&

These two lines of code specify that dialog related events should be directed to the Callback Function DlgProc(). If we rewrote our earlier DDT example to use a single dialog callback instead of individual control Callback Functions, the Callback Function may look something like this:

CALLBACK FUNCTION DlgProc()

  SELECT CASE CBMSG

    CASE %WM_COMMAND

      IF CBCTLMSG = %BN_CLICKED THEN

 

        IF CBCTL = %IDOK THEN

          DIALOG END CBHNDL, 1

          FUNCTION = 1

 

        ELSEIF CBCTL = %IDCANCEL THEN

          DIALOG END CBHNDL, 0

          FUNCTION = 1

 

        END IF

      END IF

  END SELECT

END FUNCTION

Before we can complete this stage of modifications to our original example code, we would also remove the "CALL OkButton" and "CALL CancelButton" parameters from the CONTROL ADD lines. Once changed, this modified code produces the identical behavior of the original example - however, we only used one Callback Function.

This simple example only scrapes the surface of what can be achieved in a dialog Callback Function. For example, by intercepting a %WM_ERASEBKGND message, we could draw onto the dialog client area, producing colorful dialogs with ease.

In general, a dialog Callback Function should return TRUE (non-zero) for all %WM_COMMAND messages it processes. However, this rule cannot be equally applied to other types of messages, since the return value will be message-specific.

In some cases, especially when dealing with Common Controls and custom controls, it can become necessary to return an additional result value to the dialog engine through a special data area referred to by the name DWL_MSGRESULT.

In prior versions of PowerBASIC, this value had to be explicitly set by the programmer, and required intimate knowledge of when to assign the value, and when it was not necessary. To ease the burden of the additional return result, PowerBASIC now automatically copies the CALLBACK FUNCTION return value to the DWL_MSGRESULT data area.

However, PowerBASIC only performs this step when exiting a CALLBACK FUNCTION if the function return result is non-zero, and the existing DWL_MSGRESULT value is zero. For more information on return values for messages, consult WIN32.HLP or MSDN at http://msdn.microsoft.com.

 

See Also

Dynamic Dialog Tools (DDT)

Creating a Dialog

Adding Controls to the Dialog

Modal vs. Modeless

Controls

Control Styles

Dialog Styles

Menus