In this section, you learn how to locate and identify the objects in a window by their handles. Because both static and dynamic objects have handles, and attributes and methods you can manipulate through those handles, this material applies to both static and dynamic objects. Later in this section, you add new dynamic objects to the sample window.

As discussed in the section Define Functions and Build Super Procedures one of the principal reasons why you might need to locate and manipulate objects by their handles is to write generic code to adjust the appearance or behavior of objects from another procedure, such as a super procedure. The sample super procedure h-WinSuper.p showed a very simple example of that.

Next, you write another super procedure to make some more substantial adjustments to the objects in the test window by “walking the widget tree” of the window.

To write another super procedure:

  1. Open the version of h-CustOrderWin1.w from Introduction to the OpenEdge AppBuilder as a starting point, as you did before and save it as h-CustOrderWin7.w.
  2. In the main block of h-CustOrderWin7.w, add two lines of code, one to use h-StartSuper.p to start or locate a new super procedure called h-dynsuper.p and the second to run an internal procedure that the AVM locates in h-dynsuper.p:
    MAIN-BLOCK:
    DO ON ERROR UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK
        ON END-KEY UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK:
      RUN enable_UI.
      RUN h-StartSuper.p("h-dynsuper.p").
      RUN changeFields.
      IF NOT THIS-PROCEDURE:PERSISTENT THEN
        WAIT-FOR CLOSE OF THIS-PROCEDURE.
    END.
  3. Create a new structured procedure in the AppBuilder called h-dynsuper.p.
  4. Create a new internal procedure in h-dynsuper.p called changeFields.
  5. Define these variables that the procedure needs:
    /*---------------------------------------------------------------------
    Procedure changeFields:
    Purpose: Locates the fields and browse in the target frame
      and changes some of their attributes.
    Parameters: <none>
    Notes:
    ---------------------------------------------------------------------*/
    DEFINE VARIABLE hWindow AS HANDLE NO-UNDO.
    DEFINE VARIABLE hFrame AS HANDLE NO-UNDO.
    DEFINE VARIABLE hObject AS HANDLE NO-UNDO.
    DEFINE VARIABLE hColumn AS HANDLE NO-UNDO.

These handles allow you to identify the procedure’s current window, its frame, each field or other object inside the frame, and finally the columns of the order browse in the frame. To understand how to walk down through the frame to locate objects, you need to look at the hierarchy of the objects the handles point to.

First, look at how a procedure’s window handles are established. The AVM defines a single default window for a session, and uses this window to display objects in when no other window is specified. There is a built-in system handle, called DEFAULT-WINDOW, that holds the handle of this window. There is also a system handle, called CURRENT-WINDOW, that the AVM uses as the default for parenting frames, dialog boxes, and messages. Initially, the CURRENT-WINDOW is the same as the DEFAULT-WINDOW.

There is no DEFINE WINDOW statement in ABL. All other windows in a session are dynamic windows that you create with the CREATE WINDOW statement. When you start a procedure, you can set the CURRENT-WINDOW system handle to be a window created by your procedure. There is also a CURRENT-WINDOW attribute for the THIS-PROCEDURE system handle, which holds the procedure handle of the procedure containing the reference to THIS-PROCEDURE. Setting the CURRENT-WINDOW attribute sets the default parenting for all frames, dialog boxes, and messages used in that procedure without affecting windows defined by other procedures.

The standard code the AppBuilder uses for a window includes statements to set the CURRENT-WINDOW system handle to the dynamic window the AppBuilder code creates, as you saw in the section Examine the Code the AppBuilder Generates. It also sets the CURRENT-WINDOW attribute of THIS-PROCEDURE to the same window:

/* Set CURRENT-WINDOW: this will parent dialog-boxes and frames. */
ASSIGN CURRENT-WINDOW = {&WINDOW-NAME}
THIS-PROCEDURE:CURRENT-WINDOW = {&WINDOW-NAME}.

Other procedures can access the window of a procedure that adheres to this convention by referencing its CURRENT-WINDOW attribute. Therefore, the first thing the changeFields procedure does is obtain that window handle. Because it is a super procedure of h-CustOrderWin7.w, the TARGET-PROCEDURE system handle within h-dynsuper.p has the same value as THIS-PROCEDURE does within h-CustOrderWin7.w:

hWindow = TARGET-PROCEDURE:CURRENT-WINDOW.

If the h-CustOrderWin7.w procedure did not set its CURRENT-WINDOW attribute, you would need to use some other mechanism to identify it. Because this is the most straightforward way to associate a window with a procedure, you should adopt this convention, whether you use this standard AppBuilder template or not.

A procedure can create more than one window. Only one window can be the procedure’s CURRENT-WINDOW. If your procedures use more than one window, then you need to use some other convention to locate other windows in the procedure. For example, you could store a character string representing a list of other window handles in the window’s PRIVATE-DATA attribute. The standard for procedures you build in the AppBuilder, as well as in the SmartObjects that are the components you can build applications from, is to create only one window in a procedure.

The next level down from a window is the frame or frames that are parented to the window. To identify the first frame in a window, you use its FIRST-CHILD attribute:

hFrame = hWindow:FIRST-CHILD.

If a window has more than one frame, you can access the other frames by following the NEXT-SIBLING of the first frame, which chains the frames together.

As you might expect, you go down to the objects in a frame by accessing its FIRST-CHILD attribute. But there is another level of object in between the frame and the fields and other objects in the frame. This level is called a field group.

Remember that ABL supports the notion of a DOWN frame, which displays multiple instances of the same fields to show multiple records from a result set in a manner similar to what a browse does. This visualization is used primarily by older character-mode applications where there is no browse. For this reason most graphical applications use only one-down frames. Regardless of this, the notion of this iteration through multiple instances of the same fields is still present in all frames, and each instance of the fields in the frame is a field group. Therefore, when you reference the FIRST-CHILD attribute of a frame, you are accessing its first field group:

hObject = hFrame:FIRST-CHILD. /* This is the field group */

Unless your frame has a DOWN attribute greater than one, the field group is not very interesting in itself. If the frame has more than one field group, you can use the NEXT-SIBLING chain to get at each of them. Otherwise, you just proceed down another level to get to the first object in the frame:

hObject = hObject:FIRST-CHILD.

In changeFields, you can combine all these steps into a single ASSIGN statement:

ASSIGN hWindow = TARGET-PROCEDURE:CURRENT-WINDOW
  hFrame = hWindow:FIRST-CHILD
  hObject = hFrame:FIRST-CHILD /* This is the field group */
  hObject = hObject:FIRST-CHILD.

The ASSIGN statement is more efficient than a sequence of individual assignments. The steps in the sequence are executed in order, just as they appear. For instance, the value of the hObject variable assigned in the third step (to the field group) can then be used in the fourth step to assign the same variable a new value equal to the first object in the group.

The following figure is a pictorial representation of how these different objects and their handles are related.

Figure 1. Relationships between objects in a window

Read and write object attributes

Once you have the handle to an object, you can change its appearance and behavior through its handle. To locate all the objects in the sample procedure’s frame, you start with the field group’s FIRST-CHILD, which is now in the hObject variable, and walk through the chain of NEXT-SIBLING objects as long as the object handle remains valid.

For example, assume you want to identify each fill-in field in the frame. For each one that is an integer field, you want to disable the field and set its background color to a dark gray. For each other fill-in field, you want to set the background color to green to highlight the field for data entry.

To start with, changeFields looks at the TYPE attribute of each object to see if it is a FILL-IN. If it is, then it checks the DATA-TYPE attribute to see if the field is an INTEGER. If it is, then it sets its SENSITIVE attribute to false and its BGCOLOR attribute to 8, which represents the color gray. Otherwise, if the field is not an integer, it sets the BGCOLOR attribute to 10, which is the color green:

DO WHILE VALID-HANDLE(hObject):
  IF hObject:TYPE = "FILL-IN" THEN
    DO:
      IF hObject:DATA-TYPE = "INTEGER" THEN
        ASSIGN hObject:SENSITIVE = NO
          hObject:BGCOLOR = 8.
      ELSE hObject:BGCOLOR = 10.  
    END.