Build a Windows Program with a Graphical User Interface

Learn to use DDT to create dialogs, callbacks and controls. We'll build a program with a main dialog and two buttons, OK and Exit. The current date and time is shown when the OK button is pressed. The program is closed when the Exit button is pressed.

1. Press the down arrow on the toolbar button for New and select "Generic PB program" from the dropdown menu that is shown.

A new document with a template skeleton code will be created, ready for insertion of some working code.

Some explanation of the template codeis in order:

#COMPILE EXE tells the compiler to create an .EXE program, rather than a DLL

#DIM ALL tells the compiler all variables must be declared before use with DIM, LOCAL, STATIC or GLOBAL. While this adds a bit of extra work for the programmer, it helps protect you from simple spelling errors, which can be very difficult to find.

FUNCTION PBMAIN is the program's entry point and

END FUNCTION is its exit point.

2. Insert
%USEMACROS =1
on the line following #DIM ALL as preparation for next step.

3. Insert
#INCLUDE "WIN32API.INC"
on the line following %USEMACROS =1. This tells the compiler to include the file WIN32API.INC, which contains Equates, Types/Unions, and declares for many of the Win32API calls provided by Windows itself. The %USEMACROS equate on the line above tells the compiler to use Macros in WIN32API.INC where possible, which results in a smaller executable file.

4. Insert
LOCAL hDlg AS DWORD
on the line after FUNCTION MAIN, that is, as the first statement inside the Function. We will soon need this DWORD variable to store the handle of the actual DDT dialog.

5. Insert
DIALOG NEW 0, "Caption",,, 220, 140, %WS_CAPTION OR %WS_SYSMENU, 0 TO hDlg
on the line after the LOCAL declare. One line of code, and it will create a complete dialog with a system menu, a caption that says Caption and with the client size of 220 Dialog Units wide, 140 Dialog Units high. The client area is the area available for controls, that is, the light-gray area under the caption bar. See PBWin Help for more detailed explanations of all DIALOG NEW parameters.

6. Insert
DIALOG SHOW MODAL hDlg
on the line after DIALOG NEW.

7. Compile and Execute (Run) the code. The program is not yet complete, but can be compiled and run to see how it looks so far.

And here's the result:

Not very useful yet, but it is a complete, stand-alone 32-bit Windows program, using a resolution independent dialog, Prog2.exe. The total size is only 13,824 bytes (as compiled using PBWin 8.01). Use the program's system menu to close it, or make sure it has focus and press Alt+F4 to close it down.

The Callback Procedure

The dialog needs a way to communicate with controls and the system. It needs a callback procedure. That's a function which will become the central place for messages sent to and from both system and controls.

8. Add a CALLBACK FUNCTION DlgProc with a SELECT CASE filter for messages to the code, and complete the DIALOG SHOW command in PBMAIN with CALL DlgProc, so it looks like:

We have now told the dialog to use the DlgProc as central callback procedure for internal program messages. That's done by adding CALL DlgProc to the DIALOG SHOW command. Then, we added the actual CallBack Function DlgProc with a Select Case message filter for some of the most important messages used in the communication between the system, the dialog and its child controls. The DlgProc receives a message with %WM_INITDIALOG value just before the dialog is shown, so this can often be a good place to initiate variables. The DlgProc receives %WM_DESTROY when the dialog is about to be destroyed, that is, at exit, so this is a place to clean up memory, store settings, etc. The DlgProc receives %WM_COMMAND when controls and menus are activated, so this is a place where more code will have to be added as soon as we add some "live" controls. We will not be adding any code under %WM_INITDIALOG and %WM_DESTROY in this example, but keep them here for later use.

Adding Controls

Windows provides a set of standard controls like Buttons, Labels, ListBoxes and TextBoxes, etc. Which ones are used depends on the task. In this case the task involves creating two buttons, so lets add them to the dialog.

9. Insert the following two CONTROL ADD commands in PBMAIN, between DIALOG NEW and DIALOG SHOW:

  CONTROL ADD BUTTON, hDlg, %IDOK, "&OK", 112, 122, 50, 14
CONTROL ADD BUTTON, hDlg, %IDCANCEL, "E&xit", 166, 122, 50, 14

Two buttons will be created in the lower right corner of the dialog. The %IDOK and %IDCANCEL control IDs are actually system-related IDs, already defined in the file WIN32API.INC, which we included earlier.

The ampersand character (&), when used in string literals, will append an underline and thereby act as a keyboard shorcut to the control (sometimes in combination with the left Alt key) - in this case the letter O for the OK button and the letter x for the Exit button. Please note that in later systems, like XP, this standard Windows behavior may be turned off by default and then has to be activated in the desktop settings for "Effects". Now it's time to add control handlers for the two buttons to the DlgProc.

10. Insert the following Select Case control filter under %WM_COMMAND in the DlgProc:

CASE %WM_COMMAND
   SELECT CASE AS LONG CBCTL
   CASE %IDOK
      IF CBCTLMSG = %BN_CLICKED THEN
         MSGBOX 
            "Current time is: " & TIME$ & $LF & _
            "Current date is: " & DATE$, _
            %MB_TASKMODAL, _
            "Time and Date"
      END IF
   CASE %IDCANCEL
      IF CBCTLMSG = %BN_CLICKED THEN
         DIALOG END CBHNDL
      END IF
   END SELECT

%WM_COMMAND is a system equate value, meaning "Window Message - Command". CBCTL (CallBack Control) is a DDT value relating to a control's ID and CBCTLMSG (CallBack Control Message) relates to the notification sent from a control. %BN_CLICKED is a system equate value for "Button Notification - Clicked", which is sent from a Button to the dialog when a Button is clicked.

Under %IDOK, which is the OK Button, we have inserted code for a message box, showing current system time and date on separate lines. TIME$ returns the current system (computer) time as a text string and DATE$ returns current system (computer) date. Here the ampersand (&) character tells the compiler to append the following string to the text before it (a plus sign (+) can also be used) and $LF is a built-in PowerBASIC equate with same value as the standard line feed character in Windows, ASCII 10. The underscore character acts as line continuation character in PowerBASIC and is used to wrap long code lines. The MSGBOX is given %MB_TASKMODAL style (MessageBox - Taskmodal) to make it modal. That is, the user has to close it to be able to continue. We also give it the caption title "Time and Date".

Under %IDCANCEL, which is the Exit Button, we have inserted code for closing the dialog via DIALOG END. CBHNDL contains the dialog's window handle, with same value as hDlg in PBMAIN. There's only one thing left to do before we compile and run the program again:

11. Change the dialog caption in PBMAIN to for example "Time and Date"

12. Compile and Execute (Run) the code.

The Result

This shows what happens when the OK button is clicked:

A complete, stand-alone 32-bit Windows program, Prog2.exe, with a main dialog and two buttons, at a total size of only 15,360 bytes (as compiled using PBWin 8.01).

The Code

Here's the program - Prog2.bas, with additional comments, line feeds and dividers to make the code a bit easier to read and maintain.
'======================================================================
' Prog2.bas
' Example program for PBWin.
'======================================================================

#COMPILE EXE
#DIM ALL
'------------------------------------------------
%USEMACROS = 1
#INCLUDE "WIN32API.INC"
'======================================================================
FUNCTION PBMAIN () AS LONG

   '----------------------------------------------------------------------
   ' Program entrance
   '----------------------------------------------------------------------
   
   LOCAL hDlg AS DWORD
   DIALOG NEW 0, "Time and Date",,, 220, 140, %WS_CAPTION OR %WS_SYSMENU, 0 TO hDlg
   '-------------------------------------------------------------
   CONTROL ADD BUTTON, hDlg, %IDOK, "&OK", 112, 122, 50, 14 CONTROL ADD BUTTON, hDlg, %IDCANCEL, "E&xit", 166, 122, 50, 14
   '-------------------------------------------------------------
   DIALOG SHOW MODAL hDlg, CALL DlgProc
END FUNCTION
'======================================================================
CALLBACK FUNCTION DlgProc() AS LONG
'----------------------------------------------------------------------
' Main Dialog procedure
'----------------------------------------------------------------------
   SELECT CASE AS LONG CBMSG
   CASE %WM_INITDIALOG  ' entry - the dialog is about to be shown
   CASE %WM_DESTROY     ' exit  - the dialog is about to be destroyed
   CASE %WM_COMMAND     ' received from a control or menu item, etc.
      SELECT CASE AS LONG CBCTL  ' which control id is calling?
         CASE %IDOK                 ' the OK button or the Enter key
            IF CBCTLMSG = %BN_CLICKED THEN  ' was clicked
               ' Show a message, telling current time and date
               MSGBOX "Current time is: " & TIME$ & $LF + _
               "Current date is: " & DATE$, _
               %MB_TASKMODAL, _
               "Time and Date"
            END IF
         CASE %IDCANCEL             ' the Exit button or the Escape key
            IF CBCTLMSG = %BN_CLICKED THEN  ' was clicked
               DIALOG END CBHNDL' end the program
            END IF
      END SELECT
   END SELECT
END FUNCTION