Chapter 1. GUI Development

IN THIS CHAPTER

This chapter describes some of the tips and techniques in Forms application development. The power of Forms can be leveraged fully when one moves from GUI to "beyond GUI"—that is, to GUI combined with programming. The tricks of the trade I explore in this chapter include quality-assured front ends such as implementing a standard toolbar, creating dynamic and floating toolbars, implementing timers, and displaying a clock. The chapter also provides special tips for list items, including simulating drill-down Lists of Values (LOVs), ordering by foreign key items, and implementing object-oriented methods in Forms. Finally, I'll present some tips for standard GUI practices. Wherever possible, code segments are provided to help you implement these techniques.

Standard Toolbar Implementation

A common application-specific feature is a standard iconic toolbar that provides icons to carry out common form functions such as Save, Undo, Enter Query, Execute Query, Next Block, Previous Block, Next Record, Previous Record, Insert Record, Delete Record, Exit, Clear, List Values, and Close Application. Toolbars also let users invoke functions such as Notepad, Diary, Calculator, and so on. Such a toolbar is useful especially when the application uses a customized menu bar to replace the default menu provided by Oracle Forms runtime. In addition, a toolbar enhances the GUI's look and feel.

In Forms 5.x and above, the default toolbar comes with Forms runtime (by adding the suffix &SMARTBAR to the DEFAULT value of the Menu Module property). However, think of a function like Close Application, mentioned earlier. This function might refer to closing all forms invoked in a multiform application—a task that cannot be achieved with the normal Exit toolbar icon unless it is clicked repeatedly.

In Forms 5.x and above, toolbars can be associated with a form at three levels at runtime:

  • At the menu level—On an MDI platform, this toolbar is similar to the MDI toolbar in Forms 4.5. This toolbar also comes in an SDI platform. Forms supplies it by default with the name SMARTBAR with the DEFAULT menu.

  • At the form level—This toolbar is new to Forms 5.x and above and is available for all windows within that form. It is not equivalent to the MDI toolbar.

  • At the individual window level—This toolbar is specific to a particular window to which it is attached.

Displaying Menu-Level Toolbars

To create a menu-level toolbar, specify &SMARTBAR after the DEFAULT menu module name in the Form Module property palette. Dynamically hiding the menu using REPLACE_MENU('') in the WHEN_NEW_FORM_INSTANCE trigger will also suppress the display of the associated toolbar.

To create a menu-level toolbar for any customized menu, set either the Visible In Horizontal Menu Toolbar or Visible In Vertical Toolbar property to Yes for each menu item that should appear in the menu toolbar.

Figure 1.1 shows a menu-level toolbar.

A menu-level toolbar.

Figure 1.1. A menu-level toolbar.

Displaying a Form-Level or Window-Level Toolbar

To create a form-level or window-level toolbar, you create a horizontal toolbar canvas view with iconic buttons. Then, specify it as the value for the Form Horizontal Toolbar property or the Window Horizontal Toolbar property. To do so, follow these steps:

  1. Create a canvas view of type horizontal toolbar (that is, CANVAS_TOOLBAR).

  2. Assign to the canvas view a property class specifying the standard canvas characteristics (such as PC_CANVAS). The height of the canvas should be just enough to enclose an iconic button of standard size. Set the Visible property of this canvas to Yes and the Bevel property to None.

  3. Create a property class named PC_ICONIC_BUTTON with the following property values: Iconic set to Yes, Mouse Navigate set to Yes, Keyboard Navigable set to Yes, Width set to 10, Height set to 10, and ToolTip Visual Attribute Group set to DEFAULT.

  4. Construct a block named TOOLBAR with items as iconified push buttons for each of the individual functions. The buttons inherit their properties from the PC_ICONIC_BUTTON property class.

  5. A WHEN-BUTTON-PRESSED trigger for each of the buttons contains the appropriate Forms built in as arguments to the DO_KEY procedure. For example, the Save button can have the following line of code in its WHEN-BUTTON-PRESSED trigger:

    WHEN-BUTTON-PRESSED
    DO_KEY('COMMIT_FORM'),
    

Tip

You can achieve the Close Application functionality described earlier by calling a procedure in the corresponding WHEN-BUTTON-PRESSED trigger. This technique is explained in the sub-section "Simulating a CLOSE ALL Forms" in Chapter 3, "Multi-form Applications."

  1. ToolTips can be specified for each iconic button. This is done by specifying the respective text for the ToolTip Text property for each of the iconic buttons. For example, the ToolTip text for the iconic button corresponding to the Save function can be given in the button property palette by entering Save Changes in the ToolTip Text property.

Note

Specifying ToolTips provides a visual hint feature when the cursor enters the icon. This feature is available from Forms 5.x onwards. In Forms 4.5, this functionality has to be simulated with extra coding, as illustrated later in this section.

  1. Specify this horizontal toolbar canvas CANVAS_TOOLBAR as the value for the Form Horizontal Toolbar property in Forms 5.x and above. In Forms 4.5, specify the Horizontal MDI Toolbar property in the Forms Module property sheet.

A sample toolbar constructed in this way looks like the one shown in Figure 1.2.

A form-level toolbar.

Figure 1.2. A form-level toolbar.

Simulating the ToolTip Feature in Forms 4.5

In Forms 4.5, you can display ToolTips by creating a display item, TOOLTIP_ITEM, with the following properties:

  • Char data type

  • The same visual attribute as the ToolTip visual attribute group

  • The Bevel property set to None and Displayed property set to False

  • Specify the ToolTip text as the value for the Label property for each iconic button

In addition, the property class PC_ICONIC_BUTTON discussed earlier can have the following two triggers attached to it: WHEN-MOUSE-ENTER and WHEN-MOUSE-LEAVE. The code for these triggers is explained in the steps that follow.

Dynamically populate this item with text equivalent to the Label in the WHEN-MOUSE-ENTER trigger, as follows:

WHEN-MOUSE-ENTER

DECLARE
  tooltip_x_pos NUMBER;
  tooltip_y_pos NUMBER;
  tooltip_text  VARCHAR2(30);
  item_id ITEM;
BEGIN
  tooltip_x_pos := GET_ITEM_PROPERTY(:SYSTEM.MOUSE_ITEM,X_POS);
  tooltip_y_pos := GET_ITEM_PROPERTY(:SYSTEM.MOUSE_ITEM,Y_POS);
  tooltip_text := GET_ITEM_PROPERTY(:SYSTEM.MOUSE_ITEM,LABEL);
  :toolbar.tooltip_item := tooltip_text;
  item_id := FIND_ITEM('TOOLBAR.TOOLTIP_ITEM'),
  IF NOT ID_NULL(item_id) THEN
    SET_ITEM_PROPERTY(item_id, DISPLAYED, PROPERTY_TRUE);
    SET_ITEM_PROPERTY(item_id, POSITION tooltip_x_pos+10, tooltip_y_pos+10);
  ELSE
    MESSAGE('Invalid Tooltip Item'),
    RAISE FORM_TRIGGER_FAILURE;
  END IF;
END;

A WHEN-MOUSE-LEAVE trigger will hide the dynamic ToolTip by setting its DISPLAYED property to FALSE as follows:

WHEN-MOUSE-LEAVE

DECLARE
  item_id ITEM;
BEGIN
  item_id := FIND_ITEM('TOOLBAR.TOOLTIP_ITEM'),
  IF NOT ID_NULL(item_id) THEN
    :toolbar.tooltip_item := NULL;
    SET_ITEM_PROPERTY(item_id, DISPLAYED, PROPERTY_FALSE);
  END IF;
END;

In Forms 5.x and above, the ToolTip appears below the iconic button, whereas in Forms 4.5 it appears on the side. This has been intentionally done because the ToolTip in Forms 4.5 is attached to the MDI horizontal toolbar; there is no way to display it outside the toolbar canvas without making the canvas height more than the icons'height. This leaves a view space on the MDI toolbar.

Toolbar Tips

The following tips provide the tidbits often ignored while developing form-level and window-level toolbars. Also, the points to remember while associating toolbars with forms in multiform applications are highlighted.

The Toolbar Template Form

Use a source template form in which the toolbar can be constructed. Then, you can base customized forms on this template.

Common Form Toolbars

Toolbars attached at the form level are not available to all forms in an application. In a multiform application, especially when you're using OPEN_FORM, you can subclass the CANVAS_TOOLBAR from the source template form. Then, set the Form Horizontal Toolbar property to CANVAS_TOOLBAR for each form. Doing so creates an application with a common toolbar for each independent form.

To simulate the same functionality while using Forms 4.5, the CANVAS_TOOLBAR can be referenced from the source template form. Then, set the MDI Horizontal Toolbar property to CANVAS_TOOLBAR for each form.

Window-Specific Toolbars for Modal Windows

A modal window should have a window-level toolbar associated with it. Form-level toolbar icons are not accessible from a modal window. The same is true with toolbars associated with menu, SDI, or MDI windows. Figure 1.3 shows a form-specific toolbar with a Personal Info. icon. If you click on this icon, you get the window-specific toolbar for a modal window, shown in Figure 1.4.

A form-specific toolbar with a Personal Info. icon.

Figure 1.3. A form-specific toolbar with a Personal Info. icon.

A window-level toolbar.

Figure 1.4. A window-level toolbar.

Floating Toolbars

This section discusses the techniques to implement dynamic toolbars. It starts by looking at the various ways a toolbar can be "dynamic."

Dynamic toolbars can be implemented several ways. You can create toolbars that dynamically shrink or expand, based on the current mouse position or the current item, record, block, canvas, or form; you hide, unhide, and resize proportionately the toolbar's iconic buttons, depending on the current cursor position. Examples are as follows:

  • Hiding (or disabling) the List button when the cursor is in a nonupdateable item or an item that has no LOV attached to it, and showing it otherwise.

  • Hiding (or disabling) the Next Record button when the cursor is in the last record of a Query Only block, and showing it otherwise.

  • Hiding (or disabling) the Enter Query and Execute Query buttons when a control block receives input focus, and showing them otherwise.

  • Generating a new toolbar by selecting a subset of buttons from the original set when a form is being run in Query Only mode.

Other dynamic toolbars are scrollable for forms that require greater functionality to be provided by means of application-specific buttons. Floating toolbars can appear almost anywhere—that is, they "float" in an application or appear where you right-click. You can resize toolbars so that the icons fit proportionately. Finally, you can generate toolbars from a global set of iconic buttons, using point-and-click.

In this section, I will discuss the implementation of a floating toolbar. It has the flexibility of being able to float, or be displayed almost anywhere when initiated by a user action. It can also have dynamic resizing capability. It has the same functionality as a regular toolbar but remains displayed until closed by another user action. For example, the user action initiating the display of the toolbar can be a right mouse click on an empty part of the canvas (that's why it's floating), and the user action closing it can be a left mouse click in an empty area of the can vas.

Tip

The form objects including triggers can be included in an Object Group, which in turn can be included in an Object Library and shared (subclassed) across form modules.

Alternatively, a template form can be created with the described objects, and customized application-specific forms can be based on this template form.

Follow these steps to create the floating toolbar:

  1. Create a new WINDOW_FLOATING_TOOLBAR that is nonmodal and is not part of the normal windows being used in any form. Make this window's width and height the same as the canvas view width and height of the window-specific toolbar created in the last section's example.

  2. Set the window's window style to Dialog, its Modal property to No, its Closeable property to Yes, its Minimizable and Maximizable properties to No, and its Bevel property to None.

  3. Create a canvas named CANVAS_FLOATING_TOOLBAR. This canvas is created in the same manner as the standard toolbar described in the earlier example. The only difference is that the number of icons is less here. This number has been chosen at random and can be application specific. Note that this can also be a stacked canvas.

  4. Specify the canvas view properties: Set the Window property to WINDOW_FLOATING_ TOOLBAR, the Canvas Type to Horizontal Toolbar, the Visible property to No, and the Bevel property to None.

  5. Set the Horizontal Toolbar property of the WINDOW_FLOATING_TOOLBAR window to CANVAS_FLOATING_TOOLBAR.

  6. Initiate the window display by writing a WHEN-MOUSE-CLICK trigger at the form level and capturing the right mouse click event. The code for this trigger has the following logic:

    • Check for the cursor not being positioned inside any item, including non–data-bearing items. This is done using the system variable :SYSTEM.MOUSE_ITEM.

    • Check whether the mouse button pressed is not the left mouse button. This is done by checking that the value of the Forms system variable :SYSTEM.MOUSEBUTTON_PRESSED does not equal the constant 1. This system variable can take values 1, 2, or 3. The value 1 is for left, 2 for middle, and 3 for right mouse buttons. All these values are returned as character values. Checking for a value other than 1 ensures that the user-pressed mouse button is not the left one.

    • Position the window WINDOW_FLOATING_TOOLBAR at the current (X,Y) position of the cursor. This is done by dynamically setting the X_POS and Y_POS properties of the window to the values :SYSTEM.MOUSE_X_POS and :SYSTEM.MOUSE_Y_POS, respectively, using SET_WINDOW_PROPERTY.

      The WHEN-MOUSE-CLICK trigger code is as follows:

      WHEN-MOUSE-CLICK
      
      IF NAME_IN('SYSTEM.MOUSE_ITEM') IS NULL THEN
         IF NAME_IN('SYSTEM.MOUSEBUTTON_PRESSED') != '1'THEN
            SET_WINDOW_PROPERTY('WINDOW_FLOATING_TOOLBAR', X_POS,
                       TO_NUMBER(NAME_IN('SYSTEM.MOUSE_X_POS')));
            SET_WINDOW_PROPERTY('WINDOW_FLOATING_TOOLBAR',Y_POS,
                       TO_NUMBER(NAME_IN('SYSTEM.MOUSE_Y_POS')))
            SET_WINDOW_PROPERTY('WINDOW_FLOATING_TOOLBAR',
                       VISIBLE,PROPERTY_TRUE);
         END IF;
      END IF;
      

The floating toolbar will look like Figure 1.5.

Tip

The actual (X,Y) position of the displayed toolbar is an offset from the actual cursor (X,Y) position because the former is relative to the MDI Window and the latter is relative to the primary canvas of WINDOW0.

A floating toolbar.

Figure 1.5. A floating toolbar.

You can initiate the window close in several ways. First, you can set the Hide On Exit property to Yes for this toolbar window. This is not a standard practice because the user might want to perform multiple functions in the form using this toolbar and would therefore want the toolbar to remain floating until explicitly closed. Applications demanding user-specific requests for such a feature are an exception to this rule.

Second, the window can close when the user clicks the upper-right ×. This is the recommended practice, unless a user requirement specifies a different approach. To use this technique, write a WHEN-WINDOW-CLOSED trigger at form level that checks for the currently active window and closes it if it is WINDOW_FLOATING_TOOLBAR:

WHEN-WINDOW-CLOSED

IF NAME_IN('SYSTEM.EVENT_WINDOW') = 'WINDOW_FLOATING_TOOLBAR'THEN
   SET_WINDOW_PEROPERTY('WINDOW_FLOATING_TOOLBAR', VISIBLE, PROPERTY_FALSE);
END IF;

Third, the window can be made to disappear by creating a timer at the time of window initia tion and writing a WHEN-TIMER-EXPIRED trigger to hide it after a certain interval of time. You provide this second way of closing by specifying an appreciably large time interval—a minute, for example. Closing the floating toolbar by means of a timer involves the following steps:

  1. Create a timer in the WHEN-MOUSE-CLICK trigger immediately before displaying the toolbar window. The modified WHEN-MOUSE-CLICK trigger is as follows:

    WHEN-MOUSE-CLICK
    
    DECLARE
        timer_id TIMER;
         one_minute NUMBER := 60000;
    BEGIN
      IF NAME_IN('SYSTEM.MOUSE_ITEM') IS NULL THEN
         IF NAME_IN('SYSTEM.MOUSE_BUTTON_PRESSED') != '1'THEN
            SET_WINDOW_PROPERTY('WINDOW_FLOATING_TOOLBAR',
                        X_POS, NAME_IN('SYSTEM.MOUSE_X_POS'));
            SET_WINDOW_PROPERTY('WINDOW_FLOATING_TOOLBAR',
                        Y_POS, NAME_IN('SYSTEM.MOUSE_Y_POS'));
             BEGIN
                timer_id := FIND_TIMER('TIMER_HIDE_FT'),
                  IF ID_NULL(timer_id) THEN
                    timer_id :=
                      CREATE_TIMER('TIMER_HIDE_FT',one_minute, NO_REPEAT);
                  END IF;
             END;
           SET_WINDOW_PROPERTY('WINDOW_FLOATING_TOOLBAR',
                       VISIBLE, PROPERTY_TRUE);
         END IF;
      END IF;
    
  2. Write a WHEN-TIMER-EXPIRED trigger at the form level to close the window after the time period elapses, in one minute in this case:

    WHEN-TIMER-EXPIRED
    
    IF GET_APPLICATION_PROPERTY(TIMER_NAME) = 'TIMER_HIDE_FT'THEN
       IF GET_WINDOW_PROPERTY('WINDOW_FLOATING_FT', VISIBLE) = 'FALSE'THEN
          SET_WINDOW_PROPERTY('WINDOW_FLOATING_TOOLBAR',
                      VISIBLE, PROPERTY_FALSE);
        END IF;
    END IF;
    

Floating Toolbar Pop-Up Menus

Another convenient way of displaying a floating toolbar is by providing a pop-up menu on a right mouse button click. This pop-up can have at least two menu options: Show Toolbar and Hide Toolbar. This can be achieved by the following steps:

  1. Create a pop-up menu in the Object Navigator with two items: SHOW_TOOLBAR (labeled Show Toolbar) and HIDE_TOOLBAR (labeled Hide Toolbar).

  2. The WHEN-MOUSE-CLICK trigger is still necessary because the :system.mouse_x_pos for a pop-up menu is 0 when the cursor is in the pop-up. Therefore, you capture the (mouse_x_pos, mouse_y_pos) as soon as the user right-clicks to display the pop-up menu. You can write a WHEN-MOUSE-CLICK trigger , in addition to the menu item code, to capture the current (X,Y) of the cursor on right click before the cursor is positioned on one of the items of the pop-up menu:

    WHEN-MOUSE-CLICK
    
    IF :SYSTEM.MOUSE_BUTTON_PRESSED != '1'THEN
      IF  :SYSTEM.MOUSE_ITEM IS NULL THEN
    
        COPY(NAME_IN('SYSTEM.MOUSE_X_POS'), 'CTRL_BLK.MOUSEXPOS'),
        COPY(NAME_IN('SYSTEM.MOUSE_Y_POS'), 'CTRL_BLK.MOUSEYPOS'),
    
      END IF;
    END IF;
    

    Here, CTRL_BLK.MOUSEXPOS and CTRL_BLK.MOUSEYPOS are two control items to hold the current values of SYSTEM.MOUSE_X_POS and SYSTEM.MOUSE_Y_POS as defined in the preceding code snippet.

  3. The menu item code for the SHOW_TOOLBAR item is as follows:

    IF :SYSTEM.MOUSE_ITEM IS NULL THEN
      SET_WINDOW_PROPERTY('window_ft', X_POS,
                  TO_NUMBER(:ctrl_blk.mousexpos));
       SET_WINDOW_PROPERTY('window_ft', Y_POS,
                  TO_NUMBER(:ctrl_blk.mouseypos));
       SET_WINDOW_PROPERTY('window_ft', VISIBLE, PROPERTY_TRUE);
    END IF;
    
  4. The menu item code for the HIDE_TOOLBAR item is as follows:

    SET_WINDOW_PROPERTY('window_ft', VISIBLE, PROPERTY_FALSE);
    :ctrl_blk.mousexpos := NULL;
    :ctrl_blk.mouseypos := NULL;
    

The pop-up menu appears somewhat like the one shown in Figure 1.6.

A pop-up menu to display a dynamic floating toolbar.

Figure 1.6. A pop-up menu to display a dynamic floating toolbar.

Timers and Displaying a Clock

An external clock can be constructed using timers. Timers correspond to internal clocks, which have a specific time period. When the specified duration expires, the timer can either perform an action once and stop or repeat the action regularly every time the timer expires. Timer duration is always in milliseconds. Timers are created using the CREATE_TIMER built in and require a WHEN-TIMER-EXPIRED trigger to be written at the form level. This trigger fires every time the timer expires.

Using REPEAT Timers

Let's create a display item, CURRENT_TIME, in the horizontal toolbar canvas CANVAS_TOOLBAR created earlier. This item shows the time in HH24:MI:SS format and updates itself every second (the timer duration).

In the WHEN-NEW-FORM-INSTANCE trigger, create a timer named CLOCK_TIMER, which iterates after every one second and populates the CURRENT_TIME item with the system date in HH24:MI:SS format. The code is as follows:

DECLARE
   timer_id TIMER;
   one_second NUMBER := 1000;
BEGIN
   timer_id := FIND_TIMER('CLOCK_TIMER'),
   IF NOT ID_NULL(timer_id) THEN
     DELETE_TIMER(timer_id);
   ELSE
     timer_id := CREATE_TIMER('CLOCK_TIMER',one_second, REPEAT);
   END IF;
     SELECT  TO_CHAR(SYSDATE,'HH24:MI:SS')
     INTO   :toolbar.current_time
     FROM   DUAL;
   EXCEPTION WHEN OTHERS THEN
     MESSAGE(TO_CHAR(SQLCODE)||''||SQLERRM);
END;

Create a WHEN-TIMER-EXPIRED trigger as follows:

DECLARE
   timer_name VARCHAR2(30);
BEGIN
   timer_name := GET_APPLICATION_PROPERTY(TIMER_NAME);
   IF  timer_name = 'CLOCK_TIMER' THEN
      SELECT  TO_CHAR(SYSDATE,'HH24:MI:SS')
      INTO   :toolbar.current_time
      FROM   DUAL;
   END IF;
   EXCEPTION WHEN OTHERS THEN
      MESSAGE(TO_CHAR(SQLCODE)||''||SQLERRM);
END;

Special Tips for List Items

In this section, I shall discuss some special tips and techniques offered by Forms with respect to lists and list items.

Populating List Items Dynamically

List items appear as either drop-down list boxes, T-lists, or combo boxes in Forms. Mostly, list items have static values as their elements, created during design time. However, there arises a need to populate a list item with elements based on runtime criteria or from a database table. For example, one might populate a drop-down list box with all the state codes from a look-up table named STATE_TAB. This can be done either using a query or adding elements one by one at runtime. This way of populating list items programmatically without hard-coding their values at design time is what is termed dynamically populating list items.

This technique involves populating the list with a query or without a query. I will discuss populating by means of a query because it is very powerful and efficient.

One elegant way to populate list items dynamically is to use programmatically created records groups. This record group should have a two-column structure, both being of character data type. The first column should correspond to the label of the list item, and the second column, to the corresponding value.

Tip

The record group has to be created programmatically. However, it can either query or nonquery as long as it follows the two-column structure mentioned here.

Never try to populate a list item using a query directly. Always create and populate a query record group, and use this record group to do the job.

The code performs these tasks:

  • Create the record group using CREATE_GROUP_FROM_QUERY.

  • Populate the record group using POPULATE_GROUP or POPULATE_GROUP_FROM_QUERY.

  • Populate the list item using POPULATE_LIST.

The sample code is given here:

DECLARE
   rg_list_id RECORDGROUP;
   rg_name VARCHAR2(20) := 'RG_LIST';
   ret_code       NUMBER;
--The following holds a SELECT query from which the list elements are derived.
   v_select       VARCHAR2(300);
BEGIN
  v_select := 'SELECT state_code, state_code FROM state_tab ORDER BY 2';
  rg_list_id := FIND_GROUP(rg_name);
  IF NOT Id_Null(rg_list_id) THEN
     DELETE_GROUP(rg_list_id);
  END IF;
  rg_list_id := CREATE_GROUP_FROM_QUERY(rg_name, v_select);
  ret_code := POPULATE_GROUP(rg_list_id);
  POPULATE_LIST('LIST_ITEM','RG_LIST'),
  DELETE_GROUP(rg_list_id);
END;

Tip

Use a form procedure, passing the input query and list item name as parameters.

A nonquery record group can also be used instead of a query record group, provided that it is created programmatically using CREATE_GROUP and populated using POPULATE_GROUP or POPULATE_GROUP_WITH_QUERY.

Populating a List Item with Date and Number Values

Sometimes, it might be necessary to populate NUMBER and DATE columns as list item element values. Because list items always retain character values only, for both the label and value, it is necessary to perform conversion to VARCHAR2 from NUMBER and DATE.

As an illustration, consider a drop-down list box showing all departments in an organization. You can assume that the DEPT table has the following structure:

CREATE TABLE DEPT
(ID NUMBER(6) PRIMARY KEY,
 NAME VARCHAR2(30) NOT NULl);

The label column has its values derived from the NAME column. The value corresponding to each NAME is derived from the ID column and should be stored in the list item as a character value. This requires the use of TO_CHAR. Therefore, the query text in the preceding example changes to

v_select := 'SELECT name, TO_CHAR(id) FROM dept ORDER BY 1';

After populating the list, the value can be accessed by doing a reconversion to NUMBER using the TO_NUMBER function. The following shows how to access the ith element from the list discussed in the preceding example:

DECLARE
  v_id NUMBER;
  v_id_char VARCHAR2(6);
  item_id ITEM;

BEGIN
  item_id := FIND_ITEM('LIST_ITEM'),
  IF ID_NULL(item_id) THEN
    MESSAGE('Invalid List'),
    RAISE FORM_TRIGGER_FAILURE;
  END IF;
    FOR I IN 1..GET_LIST_ELEMENT_COUNT(item_id) LOOP
      v_id_char = GET_LIST_ELEMENT_VALUE(item_id, i);
      v_id := TO_NUMBER(v_id);
. . . . use this value for further processing
    END LOOP;
END;

Tip

Use conversion functions TO_CHAR, TO_DATE, and TO_NUMBER.

On input to the list, use TO_CHAR for both date and numeric values that correspond to the Value column of the list.

On output from the list, use TO_DATE and TO_NUMBER, respectively, for date and numeric values.

Use these in the SELECT column list, which is used to populate the list.

Adding Items to the Beginning of a List

How many of you are aware of the fact that in Forms, you can add elements to the beginning of an already populated list item without repopulating it? This seems trivial at first thought but is a very powerful and flexible technique to be used in many demanding situations.

Use ADD_LIST_ELEMENT and specify the constant 1 for the element index. This displaces all the existing elements to one position below and makes room for the new element with index number 1. Do not specify the constant 0 instead of 1.

The code you will use is as follows:

ADD_LIST_ELEMENT(list_id, 1, <label>, <value>);

Adding Items in the Middle and to the End of a List

So far, I have discussed how to add elements dynamically to an empty list, how to access noncharacter list elements, and how to add to the beginning of an existing list. Now I will present a simple method to insert elements in the middle of an existing list and to append elements to the end of an existing list.

Use ADD_LIST_ELEMENT and specify the index number of the new element as

current element index + 1

where current element is the element after which the new element should be added. This displaces all the remaining elements to one position below and makes room for the new element.

To add to the end of the list, specify the constant returned by GET_LIST_ELEMENT_COUNT, which gives the number of existing elements, and then specify the index of the new element as the value of this constant incremented by 1.

The following is the code for this:

DECLARE
   cnt NUMBER := 0;
BEGIN
   cnt := GET_LIST_ELEMENT_COUNT(list_id);
        ADD_LIST_ELEMENT(list_id, (cnt+1), <label>, <value>);

END;

In this example, you take the count of the list elements and increment it by 1. This value serves as the index for the new list element to be added. This works even when the list is wholly empty, because you initialize the count to zero before incrementing it by 1. Therefore, it adds the first element in case of an empty list.

Simulating a Drill-Down and Drill-Up LOV Using T-Lists

LOV is the Forms acronym for List of Values. It functions in a manner similar to a pick list of choices. Drill-down LOV refers to descending through the LOV to its sublevels, starting from a highlighted element in the LOV.

Drill-down LOVs are very useful for tables involving recursive and/or parent-child relationships. Examples of such relationships are those involving a department and its employees, a manager and employees, or a company and its hierarchical representation.

In each of these cases, a foreign key is involved that is either self-referential or parent- referential. For example, the department-employee relationship involves a parent-referential foreign key from the Department table (the parent table). The manager-employees relationship is self-referential, with the primary and foreign keys being selected from the same table. Also, the information is hierarchical. The company information is another example of a hierarchical representation.

LOVs are a commonly required feature of any application involving this kind of look-ups. The features of LOVs supported by Forms are limited in the sense that

  • There is no way to do multiselection from an LOV.

  • There is no way to drill down an LOV into its sublevels.

  • There is no way to add new rows to the look-up table using an LOV.

Out of these limitations, the most required functionality in case of parent-child relationships, especially tree-oriented, is the drill-down.

Drill-down functionality can be incorporated in an LOV directly using a Forms-provided LOV or using list items. This section discusses the implementation details of building a drill-down LOV using list items. The same method can be followed when using a Forms-supplied LOV.

You will use a T-list and dynamically populate it using a record group. The drill-down is achieved by dynamic replacement of the same record group query again and again till there is no further drill-down. Double-clicking a parent element will show this parent element and all its children one level below. The user "drills down" the LOV to reach a deeper level. The operation is repeatable until leaf elements are reached. The string '- Parent' is appended to the parent element label to identify it as the parent. This element is always the first element of the list, irrespective of whether it has children.

Double-clicking a parent element will show its parent and all its children, that is, one level above. The user "drills up" the LOV to reach a higher level. The operation is repeatable until the root element is reached. Once at the root, the original list is restored; that is, all EMPNOs that exist as MGRs. The string '- Parent' is still appended to the parent element label until the initial list is restored.

The same T-list and the same record group are reused for the drill-down and drill-up operations.

The selection of an element is done by pressing Shift and double-clicking on the element. The normal double-clicking being reserved for drill-down, the Shift+double-click is used as an alternative for selecting an element from the LOV.

  1. Create a block, BLOCK2, having an item named LOV in it. The properties for the block are set as follows: Database Data Block property set to No, Number Of Records Displayed set to 1, Insert Allowed set to Yes, and Update Allowed set to Yes. The properties for the LOV item are as follows: Subclass Information property set to PC_TLIST (property class).

    The property class PC_TLIST has its properties set as shown in Figure 1.7.

    A property class named PC_TLIST for the T-list item LOV.

    Figure 1.7. A property class named PC_TLIST for the T-list item LOV.

Tip

Remember to create a NULL list element; that is, both the label and its value are NULL. NULL means a null value, not the string 'NULL'.

  1. The EMP table is used to project the hierarchical relationship between manager (MGR column) and employees (EMPNO column). Assume that the EMP table has the following structure:

    CREATE TABLE EMP
    (EMPNO   NUMBER(10) PRIMARY KEY,
     ENAME   VARCHAR2(30) NOT NULL,
     MGR     NUMBER(10) REFERENCES EMP (EMPNO),
     HIREDATE DATE,
     SAL     NUMBER(11,2),
     JOB     VARCHAR2(20),
     DEPTNO  NUMBER);
    

    The LOV functions the following way. At first, all the employees at the parent level are displayed. The query for doing this follows:

    SELECT ename, TO_CHAR(empno) empno FROM emp WHERE
                      empno IN (SELECT mgr FROM  emp a)
    

    This SELECT statement also guarantees that the first-level EMPNO is selected.

  2. A dynamic record group, 'RG_'||'LOV', is created from the preceding query and then populated and repopulated using rows returned by the query. The T-list is populated using this record group. You use a generic procedure for this step. The procedure is named p_populate_list:

    PROCEDURE p_populate_list(item_name_in VARCHAR2,
                              query_in varchar2,
                              o_retcd OUT NUMBER)
    IS
      rg_id RECORDGROUP;
      retcd NUMBER;
      rg_name VARCHAR2(100);
      item_id ITEM;
    BEGIN
        item_id := FIND_ITEM(item_name_in);
        IF ID_NULL(item_id) THEN
                o_retcd := -1;
                RETRUN;
        END IF;
        rg_name :=
          'RG_'||substr(item_name_in, INSTR(item_name_in, '.', 1)+1,
                        LENGTH(item_name_in));
      rg_id := FIND_GROUP(rg_name);
      IF NOT ID_NULL(rg_id) THEN
        DELETE_GROUP(rg_id);
      END IF;
      rg_id := CREATE_GROUP_FROM_QUERY(rg_name, query_in);
      retcd := POPULATE_GROUP(rg_id);
      IF (retcd <> 0) THEN
        o_retcd := retcd;
        RETURN;
      END IF;
      POPULATE_LIST(item_name_in, rg_id);
      IF NOT FORM_SUCCESS THEN
        o_retcd := -2;
      END IF;
      o_retcd := 0;
    END;
    

    The appropriate trigger for calling the p_populate_list procedure is WHEN-NEW-FORM-INSTANCE:

    WHEN-NEW-FORM-INSTANCE
    
    DECLARE
        query_in VARCHAR2(32767) :=
                      'SELECT ename, TO_CHAR(empno) empno
                       FROM emp
                        WHERE empno IN (SELECT mgr FROM emp);
        retcd NUMBER;
    BEGIN
      p_populate_list('BLOCK2.LOV', query_in, retcd);
      IF (retcd <> 0) THEN
        MESSAGE('ERR: Could not populate list item.'),
        RAISE FORM_TRIGGER_FAILURE;
      END IF;
    END;
    
    
    
  3. The right place for the drill-down code is the WHEN-LIST-ACTIVATED trigger:

    DECLARE
       query_in VARCHAR2(32767);
       item_name VARCHAR2(100) := NAME_IN('SYSTEM.TRIGGER_ITEM'),
       retcd NUMBER;
       current_rows_mgr NUMBER;
       current_rows_empno NUMBER;
    BEGIN
      query_in :=
        'select LPAD(ename, ((LEVEL-1)*4+LENGTH(ename)), '''')||
             DECODE(TO_CHAR(empno), '||
      NAME_IN(item_name)||', ''- Parent'', NULL) ename, TO_CHAR(empno) '||
          ' FROM  emp '||'WHERE empno = '||TO_NUMBER(NAME_IN(item_name))||
               'or mgr = '||TO_NUMBER(NAME_IN(item_name))||
          ' START WITH empno = '||
    TO_NUMBER(NAME_IN(item_name))||'CONNECT BY PRIOR empno = mgr';
    
                  ||
    
      p_populate_list(item_name, query_in, retcd);
    END;
    
  4. The WHEN-LIST-ACTIVATED trigger is modified as follows (the following listing shows the complete code) to accomplish both drill-down and drill-up:

    WHEN-LIST-ACTIVATED
    
    DECLARE
       query_in VARCHAR2(32767);
       item_name VARCHAR2(100) := NAME_IN('SYSTEM.TRIGGER_ITEM'),
       retcd NUMBER;
       current_rows_mgr NUMBER;
       current_rows_empno NUMBER;
    BEGIN
      IF INSTR(GET_LIST_ELEMENT_LABEL(item_name, 1),'Parent', 1) = 0 THEN
      -- if current element is in the initial list
      query_in :=
        'SELECT LPAD(ename, ((LEVEL-1)*4+LENGTH(ename)), '''') ||
          DECODE(TO_CHAR(empno), '||
          NAME_IN(item_name)||',''- Parent'',NULL) ename, TO_CHAR(empno)'||
        'FROM  emp '||
        'WHERE empno = '||TO_NUMBER(NAME_IN(item_name))||'or mgr = '||
        TO_NUMBER(NAME_IN(item_name))||
        'START WITH empno = '||TO_NUMBER(NAME_IN(item_name))||
        'CONNECT BY PRIOR empno = mgr ';
      ELSIF INSTR(GET_LIST_ELEMENT_LABEL(item_name, 1),'Parent', 1) > 0 and
        (TO_NUMBER(GET_LIST_ELEMENT_VALUE(item_name,1))
    
    
        != TO_NUMBER(NAME_IN(item_name))) THEN
      -- if current is a child of a parent
      query_in :=
          'SELECT  LPAD(ename, ((LEVEL-1)*4+LENGTH(ename)), '''') ||
            DECODE(TO_CHAR(empno), '||
      NAME_IN(item_name)||', ''- Parent'',NULL) ename, TO_CHAR(empno)'||
     'FROM  emp '||
    'WHERE empno = '||TO_NUMBER(NAME_IN(item_name))||'or mgr = '||
      TO_NUMBER(NAME_IN(item_name))||
    'START WITH empno = '||TO_NUMBER(NAME_IN(item_name))||
    'CONNECT BY PRIOR empno = mgr ';
      ELSIF INSTR(GET_LIST_ELEMENT_LABEL(item_name, 1),'Parent', 1) > 0 AND
     (TO_NUMBER(GET_LIST_ELEMENT_VALUE(item_name,1))
        = TO_NUMBER(NAME_IN(item_name))) THEN
    -- if current element is a parent
        BEGIN
                  current_rows_empno := TO_NUMBER(NAME_IN(item_name));
                  MESSAGE(TO_CHAR(current_rows_empno), ACKNOWLEDGE);
                  SELECT mgr
                  INTO   current_rows_mgr
                  FROM   emp
                  WHERE  empno = current_rows_empno;
                  EXCEPTION WHEN NO_DATA_FOUND THEN NULL;
        END;
      IF current_rows_mgr IS NOT NULL THEN
         query_in :=
           'SELECT LPAD(ename, ((LEVEL-1)*4+LENGTH(ename)), '''') ||
             DECODE(TO_CHAR(empno), '||TO_CHAR(current_rows_mgr)||
           ', ''- Parent'', NULL) ename, TO_CHAR(empno) '||
    'FROM  emp '||
    'WHERE empno = '||current_rows_mgr||'or mgr = '||current_rows_mgr||
    'START WITH empno = '||current_rows_mgr||
    'CONNECT BY PRIOR empno = mgr ';
      ELSE
        query_in := 'SELECT ename, TO_CHAR(empno) empno FROM emp WHERE
                      empno IN (SELECT mgr FROM emp)';
        END IF;;
        END IF;
        p_populate_list(item_name, query_in, retcd);
    END;
    

Figures 1.8 through 1.10 depict the drill-down operation on the sample list discussed here. The parent for the second level is suffixed with the phrase '- Parent' to mark it as the parent element for the children displayed below it.

Original List before drill-down

Figure 1.8. Original List before drill-down

First Level LOV after drill-down

Figure 1.9. First Level LOV after drill-down

Second Level LOV after drill-down

Figure 1.10. Second Level LOV after drill-down

Simulating the Find Feature of List of Values

The Find feature of LOVs enables you to enlarge or reduce the LOV list as the user types in characters of the LOV element value. This feature can be simulated using COMBO BOX and T-LIST type list items in Forms 4.5. Although it doesn't use the exact mechanism offered by LOVs, the method described here imitates the same functionality. You will assume that the list to be displayed is SITE_NAME, based on the SITE_TAB table. The SITE_TAB table has the following structure:

SITE_NO    NUMBER(6) NOT NULL,
SITE_NAME VARCHAR2(20) NOT NULL.

Follow these steps:

  1. Create a combo box–type list item. Name it LIST_ITEM_COMBO. The trick lies in using a combo box so that as the user types in characters, the resulting list can be populated with a record group, which is created dynamically, based on user input. A WHEN-LIST-CHANGED trigger would suffice for this.

  2. Create a T-list–type list item below LIST_ITEM_COMBO. Name it LIST_DEST_ITEM. This holds the destination list based on the characters the user types in LIST_ITEM_COMBO.

  3. Create a WHEN-LIST-CHANGED trigger for LIST_ITEM_COMBO as follows:

    DECLARE
       rg_list_id RECORDGROUP;
       ret_code       NUMBER;
    BEGIN
       rg_list_id := FIND_GROUP('RG_LIST'),
       IF NOT ID_NULL(rg_list_id) THEN
          DELETE_GROUP(rg_list_id);
       END IF;
       rg_list_id := CREATE_GROUP_FROM_QUERY('RG_LIST',
        'SELECT site_name, site_name
         FROM site_tab
         WHERE site_name LIKE '||''''||:LIST_ITEM_COMBO||'%'||''''),
       ret_code := POPULATE_GROUP(rg_list_id);
       POPULATE_LIST('LIST_DEST_ITEM','RG_LIST'),
       DELETE_GROUP(rg_list_id);
    END;
    

    This reduces or enlarges the list, based on user input in LIST_ITEM_COMBO.

Ordering by Foreign Key Items in Forms

Items based on foreign key look-ups often figure in Forms applications. One of the features most admired in such applications is a sort order option for users to choose the data item to base the sorting order. This is often a feature required by end users and MIS managers alike. For example, an MIS manager might want to choose a sort order by the department name while tracking changes to his employees, department-wise. This seems so trivial on first thought but is not, when it comes to actual coding. The department name is generally a foreign key look-up item in the Employees screen and, therefore, a direct ORDER BY is not possible. This section presents a special technique to implement such ORDER-ing.

Use a stored function in the database, which retrieves the look-up value based on the foreign key column; specify this function in the ORDER BY clause for the corresponding block.

Consider a block based on the EMPLOYEE table with a nonbase table item DEPT_NAME in it. You want to order by DEPT_NAME, which is a look-up column (a nonbase table item in the block under consideration), from the DEPT table based on DEPT_ID in the EMPLOYEE table. The follow ing steps will do the job:

  1. CREATE a stored function named POPULATE_DEPT_NAME(ip_dept_id IN NUMBER) that returns the DEPT_NAME corresponding to the parameter ip_dept_id.

  2. CREATE a nonbase item DEPT_NAME in the EMPLOYEE block (which is based on the EMPLOYEE table).

  3. In the block properties, for the ORDER BY clause specify POPULATE_DEPT_NAME(DEPT_ID). Note that the argument passed is the actual column name (that is, DEPT_ID) in the DEPT table.

  4. Create a POST-QUERY trigger for the EMPLOYEE block as follows:

    :employee.dept_name := populate_dept_name(:employee.dept_id);
    
  5. On querying, it can be seen that the records in the block are ordered by DEPT_NAME.

Querying by Nonbase Table Items

As a continuation to the sorting capability by foreign key look-up items discussed in the preceding section, I explore here the functionality of querying by nonbase items. The DEPT_NAME item in the EMPLOYEE block (illustrated in the preceding section) is a nonbase table, and Forms default query processing does not apply to it. A typical requirement would be to query on all employees belonging to a particular department, based on DEPT_NAME. The DEPT_ID is not as suggestive as the DEPT_NAME in identifying a department.

With the introduction of the capability to change the DEFAULT_WHERE of a base table block dynamically, querying by nonbase table items can be accomplished with a PRE-QUERY trigger containing an explicit cursor for determining the foreign key column values corresponding to the nonbase table item value.

Tip

An alternative way to order by foreign key items or query by nonbase table items involves specifying an inline SELECT in place of the base table for the block, based on a stored procedure returning a result set. See Chapter 2, "Advanced GUI Development: Developing Beyond GUI," for more details regarding this technique.

Consider the standard DEPT and EMP application. You will outline the technique of querying by DEPT_NAME that is a nonbase table item in the EMPLOYEE block. Follow these steps:

  1. Set the Query Allowed property to YES/TRUE for the item DNAME.

  2. Derive the foreign key item DEPT_ID based on the ad hoc query input of :EMPLOYEE.DEPT_NAME. Use the POPULATE_DEPT_NAME procedure to populate the DEPT_NAME item for every DEPT_ID retrieved. Use a POST-QUERY trigger on the EMPLOYEE block to do this:

    POST-QUERY
    
    :employee.dept_name := populate_dept_name(:employee.dept_id);
    
  3. In the PRE-QUERY trigger, set the DEFAULT_WHERE of the EMP block dynamically based on the resulting DEPTNO:

    PRE-QUERY
    
    DECLARE
       v_dyn_where VARCHAR2(100) := NULL;
    BEGIN
       v_dyn_where :=
        'WHERE deptno IN
          (SELECT deptno FROM dept WHERE dname = NVL(:emp.dname, dname) '),
       SET_BLOCK_PROPERTY('EMP', DEFAULT_WHERE, v_dyn_where);
    END;
    
  4. Performing an Enter Query in the EMPLOYEE block and specifying a criteria like S% in the DEPT_NAME item followed by Execute Query would retrieve only those employees belonging to departments whose names start with uppercase S.

Tip

PRE-QUERY is fired even before the SELECT is constructed and is the best place to dynamically change the DEFAULT_WHERE and ORDER BY clauses. PRE-SELECT is fired after the SELECT is constructed and before it is issued for execution. The system variable :SYSTEM.LAST_QUERY holds this SELECT at this point in time. POST-SELECT fires after the SELECT has been executed but before records have been FETCHED. POST-FETCH fires repeatedly for each set of records fetched.

Tips for Standard GUI Practices and Forms Development

Any Forms application should conform to a specific set of GUI standards, which can be frozen for that application. The following serves as a selected list of guidelines that can prove helpful:

  • Use a sign-on form for each application. A sign-on form helps in two ways. First, an added level of security can be provided. For example, in addition to the Oracle login and password, there can be a secret code for an application particular to each user. This second level of checking can be easily done using a sign-on form. Second, added functionality for the Oracle Forms login can be incorporated, for example, a pick-list of database names for the user to choose from.

  • Use a standard iconic toolbar and button bar across forms by subclassing from a tem-plate form. You can have a vertical toolbar for function-specific buttons and a horizontal toolbar for application-specific or module-specific feature buttons. Also, common application-specific toolbars and common form-functionality toolbars can figure as menu-level toolbars for forms sharing common menus. Specific icons particular to a form can figure as form-level toolbars. Within a single form, window-specific icons can be organized as window-level toolbars.

  • Use ToolTips or balloon help for iconic buttons. ToolTips provide onscreen visual hints and also improve the GUI look-and-feel.

  • Highlight current and cursor records in multirecord blocks and blocks having master-detail relationships. This preserves the focus of the record the user is working on, as well as any of its related record(s).

  • Use alerts, and standardize the alert categories. This provides a visual way of displaying messages, as well as a distinct categorization of messages according to which ones are real errors, which ones are warnings, and which ones are just informative. For example, it is worthwhile to display a save confirmation alert having Yes, No and Cancel buttons when the user is navigating out of a form or a subwindow.

  • Use list items for more than five or six choices. Use radio groups in case of fewer choices.

  • Leverage the full potential of List Items by populating list items dynamically. This helps in customizing the list choices according to user-input runtime criteria.

  • Use drill-down LOVs in case of multiple levels of data in look-up tables, whether hierarchical or not. This eliminates the need for using multiple LOVs in case of multilevel look-ups.

  • Take care of both keyboard navigation and mouse navigation.

  • Control record access at the block level by specifying records-buffered and records-fetched properties for the block. This is a performance-related point and should be done with proper care. The cost of fetch time versus memory should be analyzed carefully. (This tip is not meant to be taken as a rule.)

  • Exit child windows by means of buttons, such as OK or Close. Also, program the × for each window. This improves the flexibility, in addition to conforming to standard windows conventions.

  • Use KEY triggers only to override default functionality of the corresponding physical function key. This is because KEY triggers are the only directly related components corresponding to a physical function key.

    As an example, to check for user selection from an LOV, writing a KEY-LISTVAL trigger is recommended to redefine the List Values functionality. The following piece of code illustrates this:

    KEY-LISTVAL
    
    DECLARE
      Selected BOOLEAN := FALSE;
    BEGIN
      LOOP
        Selected := SHOW_LOV('<lov_name'),
        IF selected THEN
          EXIT;
        ELSE
          MESSAGE('A value has to be selected from the List of Values'),
        END IF;
      END LOOP;
    END;
    

    The KEY-LISTVAL trigger can then be invoked using the DO_KEY built in at a single place or more than one place, if required. By doing this, you provide code that is both appropriate and sharable.

    DO_KEY('LIST_VALUES'),
    
  • Use POST-QUERY to populate nonbase items during query time. This ensures that a database read is done only to query the look-up item based on the foreign key value and not also for the actual foreign key item.

  • Use changing LOVs dynamically at runtime rather than creating multiple LOVs.

  • Use Default Where and Order By to dynamically change query criteria rather than create multiple blocks for each criterion.

  • Simulate the GUI window-specific standard functionality in forms wherever not supported—for example, closing a window using the × in the upper-right corner.

    As another example, you can use the Cut and Paste operations for duplicating an item, regardless of the instance of the record from where data is cut and pasted. The default Forms duplicate item functionality cuts data only from the immediately preceding record and pastes in the new (current) record. An easy way of implementing this is by means of pop-up menus.

  • Implement multiform applications because they increase modularity and provide ease of use.

  • Use timers for duration-specific applications such as balloon help.

  • Implement object and code reusability. Make effective use of object groups and property classes.

  • Do not set the Keyboard Navigable and Mouse Navigate properties to TRUE/YES for push buttons present in an exclusive BUTTON_PALETTE block. This prevents the control from shifting, that is navigating, to the buttons by using either the keyboard or the mouse. One very important reason for doing this is to prevent invalid operations in the button block.

    For example, assume that the BUTTON_PALETTE has a button labeled List to invoke the LOV corresponding to the cursor item, which is a text item. Specifying the Keyboard Navigable and/or Mouse Navigate properties to Yes would shift the input focus from the cursor item to the List button. This causes Forms to invoke the LOV for the button instead, which does not make sense. As a result, you get the error FRM-41026: Field does not understand operation.

  • Use text items with Insert Allowed and Update Allowed properties negated, to behave like display items for items with data length greater than the item width. This enables you to scroll to the end of the data present in the item . Secondly, it provides consistency by giving a standard look-and-feel to all data-bearing items.

  • Do not set the current record attribute property for single record blocks and button-only blocks. There is no need to do so, because only one record is displayed at any point in time.

  • Programmatically populating default values for items is a better practice than setting them through object properties, because it prevents the risk of messing up validation. Default values set through object properties in a record do not initiate validation unless there is either user input or a programmatic setting of some item in a record.

    Imagine a record with two items, BATCH_NO and BATCH_DATE, with BATCH_SEQNO being display only (because it will be populated through PRE-INSERT later on) and a default value of $$DATE$$ set for BATCH_DATE. Doing so ignores the default form-level validation. As a consequence, consider a detail block for this master BATCH record, say, BATCH_ITEMS, where masterless operations are not allowed (because they should not be allowed). There is no way that the user can input data in this detail block unless there is user input—here it will be overwriting the same date. To control this programmatically, use WHEN-CREATE-RECORD. If there are items that can be entered besides the first item, use WHEN-DATABASE-RECORD. Alternatively, you can use WHEN-NEW-RECORD-INSTANCE with :SYSTEM.RECORD_STATUS = 'NEW'.

  • Use standard form-specific libraries and application-specific libraries. Eliminate SQL from the Forms side as far as possible. This can be achieved by having a set of packaged procedures and functions, which can be called by the Forms Libraries.

Summary

The purpose of this chapter was to highlight some tips and techniques in Forms application development. The topics considered were standard toolbar implementation, including dynamic and floating toolbars, timers and a clock display, special tips for list items, querying and ordering by foreign key items in Forms, and object-oriented methods in forms. Last, this chapter also touched on tips for standard GUI practices and forms development. The concepts were illustrated with examples, and alternatives to each concept were discussed. These tips are based on the author's individual experience in the related field, and any code segments presented herein are the author's own.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.162.65