Manipulate the objects in a window
- Last Updated: October 21, 2024
- 6 minute read
- OpenEdge
- Version 12.8
- Documentation
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:
- 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.
- 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. - Create a new structured procedure in the AppBuilder called h-dynsuper.p.
- Create a new internal procedure in h-dynsuper.p called changeFields.
- 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:
|
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:
|
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:
|
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:
|
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:
|
In changeFields, you can combine all these steps into a single
ASSIGN statement:
|
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.
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:
|