LESSON 10 Exception Handling

Exception handling allows you to trap errors that occur during the execution of a program and to provide useful information about those errors to the application user. This lesson describes how to create user-defined exception objects and use them to catch exceptions that you throw from a method in a TRY-CATCH statement.

In this lesson you:

How long does it take?

About 45 minutes.

Add a new sheet window to the existing application

Where you are

> Add a new sheet window to the existing application

Create user-defined exception objects

Create a new user function and user event

Call the methods and catch the exceptions

Run the application

In this lesson you add a third sheet window to the main tutorial application. You create and call a function to perform a routine operation (calculate a percentage) on values returned from embedded SQL commands and a value selected by the application user from a drop-down list box control.

The prototype for the function you create throws user-defined exceptions. You call the function in a TRY-CATCH block inside the Clicked event on a command button control. The CATCH clauses in the Clicked event catch user-defined exceptions thrown by the new function as well as a system exception thrown up the application call stack.

You use the new sheet window to calculate the percentage of customers that resides in a selected state. The controls you add to the new sheet window are:

  • Two static text boxes that you change programmatically to display read-only results

  • A command button to call a function that calculates percentages

  • A drop-down list box for a list of states where customers reside

  • A text box that displays the percentage of customers residing in the state that application users select from the drop-down list box

To add a sheet window to the existing application, you must:

Create the sheet window

You inherit the sheet window from the w_pbtutor_basesheet window. This is the base class for sheet windows that you generated with the Template Application wizard. You do not use the w_master_detail_ancestor extension layer window, since the modifications you made to it are not useful in the new sheet window.

  1. Select File>Inherit from the PowerBuilder menu.

    Make sure the Objects of Type box displays Windows.

    Select w_pbtutor_basesheet from the available windows in the pbtutor.pbl library and click OK.

  2. Make sure the Layout view displays in the Window painter.

    Select Insert>Control>StaticText and click near the top left corner of the Layout view.

  3. In the Properties view, highlight the default text in the Text text box and type the following:

    1. Select or type a state code in drop-down list:
  4. Lengthen the control width and the width of the sheet window to display the entire text and allow room for a drop-down list control at the top right of the window.

    A length of 2250 should be sufficient for the sheet window width. You can set this on the Other tab of the Properties view for the window, or you can drag the window edge in the Layout view to make room for an additional control.

  5. Right-click the static text control in the Layout view and click Duplicate from the pop-up menu.

    In the Properties view, highlight the default text in the Text text box of the new static text control and type the following:

    2. Click Percentage button
  6. Select Insert>Control>DropDownListBox and click to the right of the static text boxes near the top right corner of the Layout view.

  7. In the Properties view for the drop-down list box, type ddlb_state for the control name.

    Select the AllowEdit and the VScrollBar check boxes.

  8. Click the CommandButton button in the painter bar and click below the two static text boxes.

    In the Properties view, type cb_percent for the button name and type Percentage for the button text.

  9. Select Insert>Control>SingleLineEdit and click below the command button in the Layout view.

    In the Properties view, type sle_result for the control name and type the following for the control text:

    Text box for percent of customers in the selected state
  10. Lengthen the control width to display the entire text.

  11. Make sure no control is selected and the sheet window properties are displayed in the Properties view.

    Type Customer Location for the Tag property.

    The text you typed will be visible in the sheet window title at runtime. Code in the basesheet ue_postopen event assigns the Tag text to the sheet window title.

  12. Select File>Save from the PowerBuilder menu.

    Select pbtutor.pbl for the application library, type w_cust_pct for the new sheet window name, and click OK.

    This saves the new sheet window with all its controls to the main tutorial library.

Provide access to the sheet window from the main application frame

You must register the new sheet with the sheet manager.

  1. Double-click w_pbtutor_frame in the System Tree.

  2. If the ue_postopen event is not visible in the Script view, click the Event List tab and double-click ue_postopen.

    The ue_postopen event script displays in the Script view.

  3. Edit the list of sheets to add your new window. The following entry should be a single line:

    string ls_sheets[] = { "w_customers", "w_products", "w_cust_pct" }
  4. Edit the display list to add a label for your new window. The following entry should be a single line:

    string ls_display[] = { "Maintain Customers", "Maintain Products", "Customer Location" }
  5. Select File>Save and close the w_pbtutor_frame window.

    The next time you run the pbtutor application, you should be able to open the new sheet window from the File menu of the main frame window. Although you can now run the new sheet window from the development environment, you must make sure that you can run it from a compiled application as well.

    For this purpose, you reference the sheet windows as window objects in the sheet manager of_registersheet script. The reference is necessary for the compiler to know that this object is used in the application so that it will include it in the executable.

    You create a compiled application in Preparing the Application for Deployment

  6. Double-click n_pbtutor_sheetmanager in the System Tree.

  7. Click the Function List tab and double-click of_registersheet.

    The script for the of_registersheet function displays in the Script view.

  8. Enter the following after the lines declaring sheet window variables for the w_customers and w_products windows:

    w_cust_pct lw_sheet3
  9. Save and close the n_pbtutor_sheetmanager user object.

Create user-defined exception objects

Where you are

Add a new sheet window to the existing application

> Create user-defined exception objects

Create a new user function and user event

Call the methods and catch the exceptions

Run the application

Now you create two user-defined exception objects that you will throw from a function that is invoked when the user clicks the command button on the w_cust_pct window. You also create a user-defined exception object that you throw from a user event on the drop-down list box control that you added to the w_cust_pct window.

  1. Select File>New from the PowerBuilder menu and click the PBObject tab in the New dialog box.

  2. Select the Standard Class icon and click OK.

    Select throwable from the Types list box and click OK.

    The new exception object displays in the User Object painter.

  3. In the Text box of the Properties view, type the following:

    No rows were returned from the database. If you typed or selected a state code in the drop-down list box and the database connection has not been closed, either the state you entered has no customers or you entered the state code incorrectly. 

    The exception object has get and set methods for handling the Text property. Here you set the text property directly in the user interface.

  4. Click outside the Properties view to enable the Save button.

    Select File>Save, select the pbtutor.pbl application library, and type exc_no_rows in the User Objects box for the new exception object name, and click OK.

  5. Select File>Close.

  6. Repeat steps 1-5 using the following values for the Text property and the exception object name:

    Property

    Value

    Text

    Percentage too low. Only one customer in this state. Notify regional sales manager...

    Exception object name

    exc_low_number


  7. Repeat steps 1-5 using the following values for the Text property and the exception object name:

    Property

    Value

    Text

    You must use the two-letter postal code for the state name.

    Exception object name

    exc_bad_entry


Create a new user function and user event

Where you are

Add a new sheet window to the existing application

Create user-defined exception objects

> Create a new user function and user event

Call the methods and catch the exceptions

Run the application

Now you add a function that you invoke from the Percentage command button's Clicked event and an event that is triggered when the focus is changed from the drop-down list box on the w_cust_pct window. The function calculates the percentage of customers living in a particular state. The event processes the current value of the drop-down list box control to make sure it is two characters in length (for the state code).

  1. Open w_cust_pct in the Window painter if it is not already open.

    Select (Functions) in the first list box in the Script view.

    The Script view displays the Prototype window. The first drop-down list box in the Script view displays (Functions) and the second drop-down list box displays (New Function).

  2. Select decimal for the Return Type and type uf_percentage for Function Name.

  3. Select integer for the Argument Type and type ai_custbystate for the Argument Name.

    You will add a second argument in the next step.

  4. Right-click anywhere in the Prototype window and select Add Parameter from the pop-up menu.

  5. Select integer for the second Argument Type and type ai_totalcust for the second Argument Name.

  6. Type exc_no_rows,exc_low_number in the Throws box.

  7. Enter the following script for the new function:

    Decimal my_result
    exc_no_rows  le_nr
    exc_low_number le_ex
    /* Process two integers passed as parameters. Instantiate and throw exceptions 
    if the first integer value is 0 or 1. Otherwise calculate a percentage and 
    return a numeric value truncated to a single decimal place. If the second 
    integer value is 0, catch and rethrow the runtime dividebyzero error during 
    the calculation. 
    */
    CHOOSE CASE ai_custbystate
       CASE 0
          le_nr = create exc_no_rows
          throw le_nr      
       CASE 1
          le_ex = create exc_low_number
          throw le_ex
       CASE ELSE
          TRY
             my_result=(ai_custbystate/ai_totalcust)*100
          CATCH (dividebyzeroerror le_zero)
             throw le_zero
          End TRY   
    END CHOOSE
    return truncate(my_result,1)

    Later in this tutorial, you will call the uf_percentage function from the Clicked event on the command button, passing in two integers and processing the return value.

    You now add a user event for the drop-down list box that throws the exc_bad_entry exception object when a user-entered state code is not exactly two characters in length.

  8. Select ddlb_state in the first drop-down list box of the Script view and select (New Event) in the second drop-down list box.

  9. Select integer for Return Type and type ue_modified for Event Name.

    Select string for Argument Type and type as_statecode for Argument Name.

    Type exc_bad_entry in the Throws box or drag it from the System Tree to the Throws box.

    Note that the Event ID is (None). You do not select an Event ID for the ue_modified event. If you selected an Event ID, you could not enter user-defined exception objects in the event Throws clause.

  10. Enter the following script for the new ue_modified event:

    exc_bad_entry  le_ex
    //Make sure the current text in the drop-down list 
    //box is two characters in length. Otherwise, 
    //instantiate the exc_bad_entry exception object and 
    //throw the exception.
    IF len(this.text)<>2 Then
          le_ex = create exc_bad_entry
          throw le_ex
    END IF
    Return 1

    Next you call the ue_modified event and the uf_percentage function, and catch the exceptions thrown by these methods.

Call the methods and catch the exceptions

Where you are

Add a new sheet window to the existing application

Create user-defined exception objects

Create a new user function and user event

> Call the methods and catch the exceptions

Run the application

You now write code to populate the drop-down list box controls with state codes from the customer table in the Demo Database database. Since you made the control editable, an application user can also type in a value for the state code. Before you process a user-entered value, you check to make sure the value conforms to the conditions you set in the ue_modified event, namely that it is two characters in length.

You also add code to the Clicked event of the command button control to process the current state code in the drop-down list box control. In the Clicked event you call the uf_percentage function to calculate the percentage of customers from the selected state and catch all exceptions that can be thrown by the function.

  1. Make sure the w_cust_pct is open in the Window painter and that ddlb_state displays in the first drop-down list box of the Script view.

  2. Select losefocus ( ) returns long [pbm_cbnkillfocus] in the second drop-down list box.

  3. Call the ue_modified event and catch the exception object that it throws by entering the following lines for the losefocus event script:

    Try 
          this.EVENT ue_modified(this.text)
    Catch (exc_bad_entry le_be)
          messagebox ("from exc_bad_entry", &             
             le_be.getmessage())
    End Try
    
    return 1
  4. Select constructor ( ) returns long [pbm_constructor] from the second drop-down list box in the Script view prototype window for the ddlb_state control.

  5. Enter the following lines in the Constructor event to populate the drop-down list box control:

    int       li_nrows, n
    string       ls_state
    
    //Get the distinct count of all states in the 
    //customer table
    SELECT count(distinct state) INTO :li_nrows 
    FROM customer;
    
    //Declare the SQL cursor to select all states
    //in customer table but avoid
    //rows with duplicate values for state.
    DECLARE custstatecursor CURSOR FOR 
    SELECT state FROM customer
    GROUP BY state HAVING count(state)=1
    UNION 
    SELECT state FROM customer
    GROUP BY state
    HAVING count(state)>1;
    OPEN custstatecursor ;
    //Populate the control with a single entry for
    //every state in the customer table.
    FOR n=1 TO li_nrows
          FETCH NEXT custstatecursor INTO :ls_state;            
          this.additem( ls_state)
    NEXT
    CLOSE custstatecursor ;
    //Set first item in list as selected item
    this.selectitem (1)
    
  6. Select cb_percent from the first drop-down list in the Script view.

    Make sure clicked ( ) returns long [pbm_bnclicked] displays in the second drop-down list box.

  7. Enter the following lines for the Clicked event script:

    Decimal my_result 
    Double entry_1, entry_2
    Int li_int, li_rtn
    String sel_state
    
    sel_state=ddlb_state.text
    //Get the number of rows with customers from the 
    //selected states and place in the entry_1 variable.
    //Change the first static control to display this
    //number.
    
    SELECT count(*) INTO :entry_1 FROM customer
          WHERE customer.state=:sel_state;
    st_1.text="Customers in state: " + string(entry_1)
    
    //Get the total number of customers and place in 
    //the entry_2 variable.
    //Change the second static control to display this 
    //number.
    SELECT count(*) INTO :entry_2 FROM customer;
    st_2.text="Total number of customers: " &
          + string(entry_2)
    
    //Call uf_percentage and catch its exceptions.
    TRY 
          my_result = uf_percentage (entry_1, entry_2)
    CATCH (exc_no_rows e_nr )
          MessageBox("From exc_no_rows",       &
             e_nr.getmessage())
    CATCH (exc_low_number e_ln )
          li_int=1 
          MessageBox("From exc_low_number", &             
             e_ln.getmessage())
       CATCH (dividebyzeroerror e_zero)
          li_rtn = MessageBox("No Customers", &
             "Terminate Application?", Stopsign!, YesNo!)
          IF li_rtn=1 THEN
             HALT
          END IF
    END TRY
    
    //Display the message in the text box. Vary the //message depending on whether there is only one 
    //customer for the selected state or if more than 
    //one customer resides in selected state.
    IF li_int=1 THEN
          sle_result.text ="Value not calculated for " & 
             + sel_state + "."    + " Try another state."
    ELSE
          sle_result.text = String (my_result) + &
             " % of customers are in " + sel_state + "."
    END IF
    

Run the application

Where you are

Add a new sheet window to the existing application

Create user-defined exception objects

Create a new user function and user event

Call the methods and catch the exceptions

> Run the application

You are now ready to run the application and calculate the percentage of customers in a selected state.

You can test the exception conditions you scripted, but to test the divide-by-zero error condition, you need to artificially set the number of customers in the database to zero. You do this by adding a check box to the sheet window, then setting the number of customers to zero if the check box is selected.

In this exercise you:

Test the new sheet window

  1. Click the Run button () in the PowerBar.

    If PowerBuilder prompts you to save changes, click Yes.

  2. Type dba in the User ID box.

    Type sql in the Password box and click OK.

    The database connection is established, and the MDI frame for the application displays.

  3. Select File>Report>Customer Location from the menu bar.

    The Customer Location window displays. The current entry in the drop-down list is AB for Alberta.

  4. Click the Percentage button.

    Because there is only one customer in Alberta, the exc_low_number user-defined exception is thrown. The message from the exception is displayed in a message box that was defined in a CATCH clause in the button Clicked event.

  5. Click OK to close the message box.

    The text in the static text boxes now displays the number of customers in Alberta and the total number of customers in the database. The text in the editable text box tells you the value was not calculated and prompts you to select another state.

  6. Select or type CA in the drop-down list box and click the Percentage button.

    The results from the database show 10 customers in California for a total of 7.9% of all customers in the database. The percentage may be different if you have modified the database.

  7. Type Ohio into the drop-down list box and click the Percentage button.

    When you lose focus from the drop-down list box by clicking the Percentage button control, the LoseFocus event fires. This event calls the ue_modified event that throws the exc_bad_entry user-defined exception. The exception message tells you to use a two-letter postal code for the state name.

  8. Click OK to close the message box, type US in the drop-down list box, and click the Percentage button.

    Because no rows are found in the database with US as the state code, the exc_no_rows exception is thrown. A message displays indicating no rows have been returned and suggests reasons why that might be the case. A more robust application might compare the typed text to a list of state codes and throw the exc_bad_entry exception instead, letting you know that US is not a state code.

  9. Click OK to close the message box.

  10. Right-click the database icon for the Demo Database, a red and yellow SQL symbol (), in your Windows System Tray.

  11. Select Shut down from the pop-up menu, and click Yes in the Warning message box that displays.

    This shuts down the connection to the Demo Database.

  12. Select or type AB again in the drop-down list box and click the Percentage button.

    The message from the exc_no_rows exception object displays for Alberta because the connection to the database was closed. To obtain results again, you need to terminate the application and restart it. PowerBuilder reestablishes a connection to the database at runtime when you restart the application.

  13. Click OK to close the message box and select File>Exit from the application menu to return to the development environment.

    The Database painter and the Database Profile painter might still list the database connection as being open. In this case you can use either painter to disconnect and reconnect to the database at design time.

Add a test for the divide-by-zero error

You now add a check box to the w_cust_pct window. You then write code to force a divide-by-zero error if the check box is selected. Because this test requires an instantiated check box object, you surround the new code in a TRY-CATCH statement that checks for null object errors.

  1. Make sure the w_cust_pct window is open in the Layout view.

    Select Insert>Control>CheckBox from the Window painter menu.

  2. Click in the window just to the right of the Percentage command button.

  3. In the Name box in the Properties view, type cbx_zero.

    In the Text box, type Test divide-by-zero error.

  4. Click the Function List tab.

    Double-click the uf_percentage function.

  5. Type the following text just above the CHOOSE CASE statement:

    //Set denominator to zero to test error condition
    //Numerator unimportant, avoid user exception cases
    TRY
    IF cbx_zero.checked=TRUE THEN
          ai_totalcust=0
          ai_custbystate=2
    END IF
    CATCH (nullobjecterror e_no)
          MessageBox ("Null object", "Invalid Test")
    END TRY
    

    Testing for the null object error

    After you finish this lesson, you can test for the null object error by adding the following line above the TRY statement: DESTROY cbx_zero.

  6. Click the Run button in the PowerBar.

    If PowerBuilder prompts you to save changes, click Yes.

  7. Type dba in the User ID box.

    Type sql in the Password box and click OK.

    The database connection is established, and the MDI frame for the application displays.

  8. Select File>Report>Customer Location from the menu bar.

    Select a state code from the drop-down list box.

  9. Select the Test divide-by-zero error check box.

  10. Click the Percentage button.

    The division by zero error is thrown during the percentage calculation and caught by the button Clicked event. The message box that you coded in the CATCH clause for this error displays.

  11. Click No to continue running the application.

    Continue to test the application by selecting another state code and optionally clearing the check box.

    If the check box is selected when you click the button again and you select Yes in the error message box, the application closes and you return to the development environment.

  12. Close the application when you have finished testing it.