OpenEdge Development: Progress 4GL Handbook
![]() ![]() ![]()
|
Defining Graphical ObjectsThis chapter returns to the user interface of your application. In "Introducing the OpenEdge AppBuilder," you saw how to lay out fill-in fields and a browse control for data display. Now you’ll learn about other kinds of visual objects and their object definitions, attributes, methods, and events.
This chapter includes the following sections:
Types of objects
The fill-in field is the simplest representation of a single data field. Other ways to represent data fields include:
- Editors — For longer text strings.
- Toggle boxes — For logical values.
- Selection lists — For lists of valid values.
- Combo boxes — For a list display that disappears when you’re not using it.
- Sliders — For visual display of an integer value within a range.
- Radio sets — For presenting a choice among a small set of distinct values.
Among objects that don’t display data values, you have already worked with buttons. In addition, Progress supports rectangles for highlighting and grouping other objects on the screen, and images to display pictures and diagrams. Menus and menu items for a window are other types of visual objects. Windows and frames are objects that serve as containers for other objects within a user interface.
The browse is a major visual object with many capabilities. Because of its special use as a display device for an entire query, a detailed discussion of how to use and customize the browse is postponed until "Using the Browse Object," after you’ve learned more about how to define and use queries.
Various terms describe all these objects in general. The Progress language syntax often uses the term widget. In other places you see the word control. But there is such variety to the display devices Progress supports that this book refers to them all as objects. Sometimes this book refers to them as basic objects or simple objects, to differentiate them from SmartObjects™. SmartObjects are procedure-based and have a great deal of additional standard behavior built into them via the 4GL procedures that support them. Progress Dynamics uses them to construct rich interfaces with very little developer coding. The basic objects Progress supports run the gamut from very simple objects (such as rectangles, which are purely decorational) to complex data controls (such as the browse).
For all their variety, all basic objects have the following in common:
- You can define them in a Progress 4GL procedure using forms of the
DEFINEstatement. These are called static objects, and it is these that you’ll focus on in this chapter.- You can also create them during program execution using the
CREATEstatement. These are called dynamic objects, and you’ll learn much more about them in later chapters.- They can have a handle that acts as a pointer to a control structure that describes the object. Since handles are used mostly with dynamic objects, you’ll learn more about handles in the chapters on creating and using dynamic objects.
- They respond to various events that can come from user actions or can be applied to the object programmatically.
- They support blocks of 4GL code called triggers that Progress executes when an associated event occurs.
- They have various methods defined for them, which are procedural actions you can invoke in your programs to perform tasks related to the object.
In the next sections you’ll learn about object definitions, attributes, and methods.
Defining static objects
You’ve already seen several uses of the
DEFINEstatement. You have defined program variables for your procedures with theDEFINE VARIABLEstatement. You have also seen the AppBuilder-generatedDEFINE BUTTONstatements for the four buttons in the Customers and Orders window you built in "Introducing the OpenEdge AppBuilder."These two forms are representative of two basic ways you can use the
DEFINEstatement, one to define a variable for a data value that is viewed in a particular way and the other to define an object that doesn’t represent a data value. In this section, you’ll look at the two forms in a general way. Then in the next chapter, you’ll look at specific types of objects you define in each way.Using the VIEW-AS phrase for data representation objects
Many visual objects represent a single data value. This value can be a field from a database table or it can be a program variable. In both these cases, the default visual representation of the field is normally a fill-in field.
Why specify normally? You can define a visualization for a field when you create it as part of a database table definition in the Data Dictionary.
To see an example of how you define a field’s visualization:
- From the AppBuilder menu, select Tools
Data Dictionary.
- From the Sports2000 database, select the Item table. This table holds information about the different sports-related items that customers can order from your business.
- Choose the Fields icon and select the CatDescription field from the Fields list. This field contains the full description of an Item in the catalog.
- Choose the Field Properties button.
- In the Field Properties dialog box, choose the View-As button.
A dialog box appears where you can define a default visualization for a field if you want it to be an object other than a fill-in. In this case, there is a definition for the field:
The CatDescription field is defined to be viewed as an editor object, with a size of 41 characters by 5 lines and a vertical scrollbar. Whenever you select the CatDescription field and drop it onto a frame in the AppBuilder, Progress automatically visualizes it as an editor of this description. If you define a variable to beLIKEthe CatDescription field, it inherits these visual attributes along with the rest of the field description. You can change these attributes in your programs just as you can change the display for any other field, but the default is always to view it as an editor.- Cancel your way out of the dialog boxes and the Data Dictionary to return to the AppBuilder main window.
For all fields where there is no specific
VIEW-ASdefinition in the Data Dictionary, the default visualization is a fill-in field. If you want another visualization of the field, you use aVIEW-ASphrase as part of the object definition. There are several variations on this.In a
DEFINE VARIABLEstatement, you can append theVIEW-ASphrase to the definition:
The
optionsare attributes for that visual type that you can choose, such as theSIZEof the editor and theSCROLLBAR-VERTICALkeyword.If you’re not defining a variable but simply placing a database field or other field into a frame, then you append the
VIEW-ASphrase to the name of the field in theDEFINE FRAMEstatement, along with whatever options apply. Here’s an example from the frame definition the AppBuilder generates if you drop the Customer Comments field onto a window and define it as an editor, as you’ll do later in the next chapter:
DEFINE FRAME CustQuery...Customer.Comments AT ROW 5.29 COL 76 NO-LABELVIEW-AS EDITOR SCROLLBAR-VERTICALSIZE 36 BY 3.14...
Defining objects that don’t represent data values
Objects that don’t represent single data values use a form of the
DEFINEstatement that names the object type directly. You have seen theDEFINE BUTTON,DEFINE BROWSE, andDEFINE FRAMEstatements already in the AppBuilder code forCustOrders.w. These are all examples of this form. Each statement type accepts the same kinds of options that theVIEW-ASphrase of theDEFINE VARIABLEstatement does, with the options list specialized for each object type. You’ll look at some of these object types in the next chapter.Using and setting object attributes
Each different type of object has its own attributes, which represent some aspect or capability of the object that you can set and query. Attributes fall into several basic categories:
- Geometry — The size and location of the object in a frame or window.
- Appearance — The color and font, or whether the object has optional features such as a scrollbar, or whether it is visible or not, for example.
- Data management — The initial value and whether the value is undone as part of a transaction, for example.
- Relationships to other objects — The parent or sibling, for example.
- Identifying characteristics of an object — Its name, for example.
You can specify initial values for many object attributes when you define the object. The 4GL supports a large number of keywords that represent these various attributes. In some cases, a single keyword represents the attribute, such as
SCROLLBAR-VERTICALfor an editor. In other cases, the attribute keyword takes one or more arguments, in the form of other values that follow the keyword in theDEFINEstatement, such asROW 5.You can also query most attribute values from within the procedure that defines the object. To retrieve the value of an attribute, you use the form:
Do not use spaces on either side of the colon between the
object-nameand theattribute-name. Each attribute has an appropriate data type, such asDECIMALfor theROWattribute orLOGICALfor theHIDDENattribute. You can use an attribute value anywhere in an expression or assignment where you would use any other value. If the attribute reference is not unambiguous, you can provide context for it in the reference by qualifying it with the name of the frame, menu, or submenu it appears in. The default is the most recently defined container whose description includes the object.For example, this attribute reference checks whether the
Firstbutton is hidden:
Changing attribute values
You can also change many attribute values at run time, even those for static objects. You simply place the attribute reference on the left side of an assignment. The documentation of the individual attributes in the final volume of OpenEdge Development: Progress 4GL Reference, as well as the online help, tells you whether you can set them. Generally, at run time you cannot change attributes that are part of the definition of an object, such as its initial value or its display type. But many attributes can change during program execution, such as attributes that define whether an object is hidden or visible and whether it is enabled or disabled. Even some basic display attributes such as a field’s font or a button’s label can change at run time to give greater flexibility to your application’s interface. If you try to set an attribute for an object that is not settable, you get a compile-time error telling you so.
To get a full description of any attribute from online help, including what object types it applies to and whether it is readable, writable, or both:
Common attribute values for visual objects
There are literally hundreds of different attributes, many of which apply only to a single object type. But several basic ones that you will likely use most often apply to most or all visual objects. This section summarizes a few of these.
Geometry attributes
These attributes affect the size and location of the object in the frame or window:
ROW, COLUMN— TheseDECIMALattributes are the character positions of the object within its container. For objects in a frame, the values represent the position within the frame and not within the frame’s window. The frame has its own position within its window, and the window has its own position within the display device. When you lay out objects in the AppBuilder, it generates code to position the objects usingROWandCOLUMNcoordinates. Alternately, you can use pixel values for positions. Generally, character positions are more portable and flexible if the font or display device changes.X, Y— TheseINTEGERattributes are equivalent toROWandCOLUMNbut measured in pixels.HEIGHT-CHARS, WIDTH-CHARS— TheseDECIMALattributes are the height and width of the object in character units.HEIGHT-PIXELS, WIDTH-PIXELS— TheseINTEGERattributes are the height and width of the object in pixels.Appearance attributes
These attributes affect the appearance of the object:
HIDDEN— If thisLOGICALattribute is true, then the object is hidden and does not appear even when its container is displayed. If it is false, then the object does appear when its container is displayed. If you set theHIDDENattribute of a container object, such as a window or frame, to true, then the container and all the objects in it are hidden. If you set it to false for a container, then the container and any contained objects that aren’t themselves hidden appear. This is an attribute you can set only at run time. The definition of an object cannot describe it as initially hidden.VISIBLE— ThisLOGICALattribute is not simply the opposite ofHIDDEN. Its relation toHIDDENcan be somewhat confusing to understand. Generally, you use it much less than theHIDDENattribute. Setting theVISIBLEattribute of an object to true forces it to be viewed. For example, setting theVISIBLEattribute of a field-level object in a window to true forces the window to be displayed even if it was previously hidden. By contrast, setting theHIDDENattribute to false doesn’t force the container to be viewed. You can read all the details of the effects of theVISIBLEattribute in the online help.SENSITIVE— ThisLOGICALattribute determines whether an object is enabled for input or not. Its use is parallel to theENABLEverb. That is, executing anENABLEstatement for an object is the same as setting itsSENSITIVEattribute to true. Similarly, executing aDISABLEstatement for an object is the same as setting itsSENSITIVEattribute to false. Like theHIDDENattribute, you can set theSENSITIVEattribute only at run time. This means that if you check the Enable toggle box or the Display toggle box on or off in the AppBuilder property sheet for an object when you are building a screen, you do not change theDEFINEstatement the AppBuilder generates for the object. Rather you change theENABLEandDISPLAYstatements the AppBuilder generates that execute when the window is initialized.READ-ONLY— ThisLOGICALattribute applies to data-representation objects and prevents the user from modifying the field value. Sometimes you might want to combine setting the Enable toggle box in a field’s property sheet with setting theREAD-ONLYattribute to true. This gives a fill-in field some of the appearance of an enabled field (with its characteristic box outline, which can improve readability), but prevents the user from changing it. The CustOrders window uses this form for its Customer fill-ins.HELP— This is the help text to display when the object is selected.TOOLTIP— This is the text to display when the user hovers the mouse over the object. For data-representation objects, you can initialize the ToolTip text in the frame definition for the object, such as in this excerpt from the frame definition for the CustOrders window:
DEFINE FRAME CustQuery...Customer.City AT ROW 7.19 COL 13 COLON-ALIGNEDVIEW-AS FILL-INSIZE 27 BY 1TOOLTIP "Enter the City"...- For other types of objects, you can specify the ToolTip text as part of the object definition, as in this button definition:
DEFINE BUTTON BtnFirstLABEL "First"SIZE 15 BY 1.14TOOLTIP "Press this to see the first Customer.".LABEL— ThisCHARACTERattribute is the label of the field or button.SELECTABLE— You can set up most visual objects for direct manipulation. This means that the user can actually select, move, and resize the object at run time just as you can move and resize objects in a design window in the AppBuilder. The AppBuilder uses these attributes to provide you with the behavior you see in a design window, where you can drag objects around to where you want them. TheSELECTABLEattribute is aLOGICALvalue which, if true, allows the user to select the object by clicking on it with the mouse. It then sprouts the characteristic resize handles around the object border that let the user size or move it.RESIZABLE— You can set thisLOGICALattribute to true to let a user change the size of an object at run time. You must also set theSELECTABLEattribute to true to provide the resize handles.MOVABLE— You can set thisLOGICALattribute to true to let a user move an object at run time. You do not need to set theSELECTABLEattribute to make an object movable. The user can move the object without using its resize handles. However, it is considered a more standard user provision to setSELECTABLEalong withMOVABLE.Data management attributes
These attributes affect how to manage data associated with the object:
SCREEN-VALUE— There is a special screen buffer that holds the displayed values of all data-representation objects in a frame. This buffer is separate from the underlying database record buffer for database fields and from the buffer where variable values are held. If a user enters a value into a field on the screen, that value is not assigned to the underlying record buffer until you execute anASSIGNstatement for the field. In the meantime, the input value is held only in the screen buffer. You can retrieve a value from the screen buffer using theSCREEN-VALUEattribute, which is aCHARACTERvalue representing the value as it appears on the screen, or as it would appear on the screen in a fill-in field. You can also set theSCREEN-VALUEattribute of an object to display a value without setting the underlying record value. It is important to remember that the value of theSCREEN-VALUEattribute is always the formatted value as it appears in the user interface. If this contains special format mask characters (such as commas and decimal points in a decimal value, for example), then any comparisons you do against this string must include those format characters. If this presents problems, you must assign the screen value to its underlying record buffer and reference theBUFFER-VALUEof the field to access it in its native data type and without format characters.INITIAL— This attribute holds the initial value of a data-representation object. You can set it only in theDEFINEstatement.Relationship attributes
These attributes affect how the object interacts with other objects:
FRAME-NAME— ThisCHARACTERattribute holds the name of the container frame the object is in.FRAME— ThisHANDLEattribute holds the object handle of the container frame the object is in. In later chapters, you learn how to traverse from one handle to another to locate an object or to locate all objects that are in a container.WINDOW— ThisHANDLEattribute holds the object handle of the container window the object is in.Identifying attributes
These attributes identify characteristics of the object:
You can see most of the object attributes that you can set for an object type in the AppBuilder property sheet for the object, such as the one shown in Figure 8–1 for the Customer.Name fill-in field in the CustOrders window.
Figure 8–1: Property sheet for a fill-in field
![]()
The Advanced button takes you to the dialog box shown in Figure 8–2. These are attributes that you might use less often, some of which are mentioned in this chapter.
Figure 8–2: Advanced Properties dialog box
![]()
Certain property sheet settings, such as Display and Enable, affect AppBuilder-generated executable statements and preprocessor values rather than the
DEFINEstatement for the object itself. You’ve seen the effect of the Display and Enable settings already in the code for the AppBuilder-generatedenable_UIprocedure:
The AppBuilder also keeps the list of displayed and enabled objects in preprocessor values:
Other property sheet settings for attributes that you cannot set as part of the object definition are set in a series of
ASSIGNstatements the AppBuilder generates, such as these for theREAD-ONLY,PRIVATE-DATA,SELECTABLE,MOVABLE, andRESIZABLEattributes in this example:
As you develop your own applications, you need to keep in mind which attributes you can define in a
DEFINEstatement, which in a frame definition, and which only in executable statements at run time.Invoking object methods
In addition to attributes, some objects support methods. A method is an operation that performs a specific action related to an object. You can think of methods as being very much like the built-in functions the language supports. The methods are also identified by keywords that you use in 4GL syntax following an object reference, which can be the object name or handle followed by a colon, just as for attributes:
Methods typically take one or more arguments, defined in a comma-separated list in parentheses following the method name.
For example, in the next chapter you’ll learn how to view a text field as an editor, with multiple lines and scrollbars and so forth. There’s an editor method, called
READ-FILE, that you can use to open and read an operating system file into an editor, just as the Progress Procedure Editor does.READ-FILEtakes a single argument, the name of the file to read. So this sample syntax reads a file into an editor calledcEditor:
Methods always return a value, just as built-in functions do. Generally, that value is a
LOGICALindicating whether the operation succeeded or not (with aTRUEvalue indicating success). You can assign the return value to a variable or field in an assignment statement:
The initial letter
lindicates that this is a logical variable.You can also ignore the return value (as you can with any function) and simply treat the method reference as a statement of its own:
Some methods return more meaningful values that you would normally not ignore. Indeed, some methods exist solely to return a meaningful value. You can think of these methods as being similar to attributes. However, because an input parameter is required and attribute references cannot take parameters, you use a method instead to retrieve the return value. For example, the following code sample uses a Progress system-wide object called
FONT-TABLEto calculate the width of a button label in the current font. It then uses this value to calculate the required width of a frame that has five buttons. Because the button label must be passed in to the operation, the syntax must be defined as a method (in this case calledGET-TEXT-WIDTH-CHARS) rather than an attribute (which might have been calledTEXT-WIDTH-CHARS):
As you can see from this example, you can reference the method within an expression anywhere another value could appear.
Instantiating and realizing objects
There are a couple of important principles that this chapter has been taking for granted in the discussion of objects. In this section, you look at exactly how objects are instantiated (or created) at run time, how they are realized (or viewed), and what their scope is.
Instantiating objects in a container
When you define a frame you give it a name. Progress creates only one instance of a named frame within a procedure, so that frame name identifies a unique object. Progress instantiates the frame when you first use it by executing an I/O-oriented statement involving the frame, such as a
DISPLAYorVIEWstatement. The same is true for dialog boxes (which are a type of frame), and for menus and their submenus.Other objects, however, might not be uniquely identified by their names. When Progress encounters, for example, a
DEFINE BUTTONstatement, or aDEFINE VARIABLEstatement with aVIEW-ASphrase that defines a particular type of visual object, it registers the description of the object but it does not actually create it. Progress can create the object only when you associate it with a container frame or window that has itself been instantiated. Then Progress can identify a unique instance of the object in that container, and create it as part of the container.This means that you could create multiple instances of an object in different containers. For example, this code fragment describes a button and creates two instances of it in two different frames:
Qualifying object references to specify a unique identity
The two
ENABLEstatements for different frames are two distinct instances of a single button definition. Each one is said to have a unique identity, which is manifested in its object handle. Using either the handle or a frame qualifier that identifies which instance of the object you’re referring to, you can set and retrieve attribute values for the object instances independently of one another.Recall that Progress associates an object reference by default with the most recently defined container for that object. It’s important to keep this in mind when you’re writing your applications. As with all defaults, you are much better off being explicit about object references than taking your chances with how the defaults work in your case.
Here are a few examples to illustrate this point. If you run the code fragment above that defines and enables button
bBothFrames, it terminates immediately because there is no statement to make it wait for user input.To test the effects of how Progress defines a unique identity for an object:
- Add a statement to make the procedure and its frame stay active while you observe the behavior of the two buttons:
DEFINE BUTTON bBothFrames.ENABLE bBothFrames WITH FRAME F1.ENABLE bBothFrames WITH FRAME F2.WAIT-FOR CLOSE OF THIS-PROCEDURE.- Run this code to see the two instances of the same button, each in its own frame:
You can’t really see the frames F1 and F2 because Progress has made them just big enough to hold the buttons. You can see that both buttons have the same default label (the button name), and they are both enabled.- Add an attribute reference to disable the button:
DEFINE BUTTON bBothFrames.ENABLE bBothFrames WITH FRAME F1.ENABLE bBothFrames WITH FRAME F2.bBothFrames:SENSITIVE = NO.WAIT-FOR CLOSE OF THIS-PROCEDURE.The question is, just which instance of the button are you disabling?- Run the procedure again to see which button is disabled:
It’s the second one, because that’s the most recent reference to the button in a frame. To change that behavior (or to make it explicit without relying on the default), you can use the frame qualifier.- Add this frame phrase to the statement to identify the first frame:
DEFINE BUTTON bBothFrames.ENABLE bBothFrames WITH FRAME F1.ENABLE bBothFrames WITH FRAME F2.bBothFrames:SENSITIVEIN FRAME F1= NO.WAIT-FOR CLOSE OF THIS-PROCEDURE.- Run the procedure again to see the result:
Now it is the first instance of the button that is disabled.To make sure it’s clear which of these frames is which, you can put the frame name into the button. Make these changes to the procedure:
Take a look at the new code you added. The first new statement identifies the button instance in frame F1 explicitly:
bBothFrames:LABEL IN FRAME F1. It sets the button label attribute to the text Frame plus the value of theFRAME-NAMEattribute for the button. The second reference to the button in that statement likewise has to be qualified by theIN FRAMEphrase to get the right one:bBothFrames:FRAME-NAME IN FRAME F1.By contrast, the second new statement just takes the defaults:
bBothFrames:LABEL = "Frame " + bBothFrames:FRAME-NAME. Because frame F2 is the most recently referenced frame for the button, the defaults use that frame. Figure 8–3 shows the result when you rerun the procedure.Figure 8–3: Frames for buttons
![]()
In Figure 8–3 you can see the frames Progress created for the buttons. At first they were the same size as the buttons. Your new statements changed the label of each button, and Progress automatically reduced the button size accordingly. The remaining portion of the frame appears as a white space after each button.
The lesson of this little exercise is that you must always be aware that every use of an object in a different container is a distinct instance of the object, and you should always make your object references as explicit as needed to be sure that you do not simply get a default behavior that isn’t what you want.
Realizing and derealizing objects
When you define an object, Progress creates an internal data structure associated with that object. Before the object can be displayed, the window system must also create a data structure for the object. When this second data structure has been created, then the object is realized.
You can modify some object attributes at any time. Others are initially modifiable, but become fixed once the object is realized. In addition, some attributes must be set before realization takes place. For this reason, it is important to have an understanding of when objects are realized.
In general, an object is realized when the application needs to make it visible on the screen. Therefore, field-level objects, buttons, and the like are typically realized when their containers are realized or made visible, and conversely, a container object is realized when a statement forces any of its contained objects to be realized or made visible.
In addition, an object is realized if a statement references any method of the object because methods operate on the realized instance object. Also, some attribute values, such as those that reference an object’s size, can only be determined if the object has been realized, so a reference to any of those attributes forces the object to be realized if it is not already. Examples of this are the
MODIFIEDattribute of an editor, which is true if the text in the editor has been changed since it was initialized, and theMAX-HEIGHTorMAX-WIDTHattributes of a frame which cannot be calculated without realizing the frame.A static object is derealized, or destroyed, when it goes out of scope. Generally, the scope of a defined object is the procedure in which it’s defined. Field-level objects are derealized when their frame is derealized. In turn, a frame is derealized when its containing window is deleted.
Using object events
Graphical applications are often referred to as event-driven applications. Unlike the hierarchical, menu-driven applications typical in a character terminal environment, graphical applications put the user more in control of the sequence of events. Using the mouse, menus, and active controls (like buttons on the screen that can respond to user actions), the user can navigate through the application with much more flexibility than in most older applications.
But this flexibility does not happen automatically. You, the developer, must build it into the application by programming procedures or blocks of code that respond to user actions. These are called triggers. The construction of the user interface of an application around blocks of trigger code is the single most fundamental difference in the architecture of event-driven applications.
You’ve already seen some examples of the language constructs the Progress 4GL uses to establish triggers and respond to events when you looked through and extended the AppBuilder-generated code for the
CustOrderswindow in "Using Basic 4GL Constructs." In this section, you’ll look at those constructs in a little more detail.User interface events
Each visual object type supports a set of user interface events. You can see a list of all these events in the AppBuilder if you go into the triggers section of the Section Editor for an object and then choose the New button. Each object type first supports a list of common events, such as the events for a button, shown in Figure 8–4.
Figure 8–4: Common button events
![]()
They are called common events simply because they are the events most commonly associated with an object. Each object has its own set of these events but there is a lot of overlap. For example, any object that can be part of the tab order of a frame has the
ENTRYevent, which fires when the user tabs into the object, and theLEAVEevent, which fires when the user tabs out of that object. Any object that can be the target of keystrokes (even an object like a button that doesn’t use those keystrokes to set a data value) can use theANY-KEYorANY-PRINTABLEevents to respond to them.In some cases, the most common event is one that is distinctive for that object. For example, the whole purpose of a button is for someone to choose it and to have an action result. Therefore, the button supports the
CHOOSEevent, which is supported only by buttons and menu items. This is the default event that comes up in the Section Editor when you go into the triggers section for it. Data-representation objects, which can have actual values, support theVALUE-CHANGEDevent, which fires when the user enters a new value for the object.To see a complete description of all the common events, see the high-level widget events topic in the online Help.
Objects support direct manipulation events, such as
CHOOSEshown in Figure 8–5, which fire when the user performs an action (typically using the mouse) that involves selecting an object and possibly moving or resizing it. Some of these events are the result of making the objectSELECTABLE,RESIZABLE, orMOVABLE.Figure 8–5: Direct manipulation events
![]()
There is a whole host of events associated with all of the possible ways the user can make a selection with either a standard two-button mouse or a three-button mouse, such as the portable mouse events shown in Figure 8–6, that can map to either type of mouse.
Figure 8–6: Portable mouse events
![]()
Progress also supports a set of ten miscellaneous events that have no standard meaning or action, but which are intended to let you associate a trigger with some event that only happens programmatically using the
APPLYstatement, and which has nothing to do with a particular user action. These are called the developer events and are numbered U1 through U10, as shown in Figure 8–7.Figure 8–7: Developer events
![]()
These developer events have no built-in significance, but allow you to define a block of code to execute. For example, you can specify
ON U1 OFan object and then programmaticallyAPPLY “U1”to the object to execute the code.Finally, you can associate a trigger with virtually any keyboard keystroke combination, by choosing the Keyboard Event button and then typing the keystroke combination, such as CTRL-X shown in Figure 8–8.
Figure 8–8: Keyboard Event dialog box
![]()
Defining triggers
The most basic way to define a trigger is to put the trigger definition directly into the object definition:
The
TRIGGERSblock in aDEFINEstatement can contain one or more individual trigger definitions, each starting with anONphrase naming the event followed by a single statement or aDO-ENDblock of statements. This form is called a definitional trigger.If you use the AppBuilder to create your application procedures, it always creates separate blocks of executable code that attach triggers to objects at run time. This form is called a run-time trigger. There is no inherent advantage to one form over the other. Either way, the code in the trigger block is compiled and turned into r-code along with the rest of the procedure. You should use the AppBuilder to organize your trigger blocks to provide a more readable structure to your procedures.
The form of the trigger block for a run-time trigger names both the event and the object it applies to:
Either the
event-nameor theobject-name(or both) can be a comma-separated list. Thestatementscan be either a single statement or aDO-ENDblock of statements.Setting up triggers to be executed
A trigger is executed if the object it applies to is enabled (sensitive), and if the trigger is currently active and in scope.
The trigger is active if the statement that defines it has been executed. As discussed earlier in the book, Progress processes statements in a procedure in the order it encounters them in. Thus, the definition of an object must come before the block of code that defines a trigger for the object. Otherwise, Progress won’t recognize the object reference. And the trigger definition must come before the user receives control and can perform the action that would fire the event and run the trigger.
The scope of a definitional trigger is the scope of the object it’s defined for. The scope of a run-time trigger is the nearest containing procedure or trigger block where it is defined. So if a trigger definition is inside an internal procedure, then its scope is limited to that internal procedure. If it’s outside of any internal procedure, its scope is the entire external procedure.
The AppBuilder places all trigger definitions toward the top of the procedure, following the Definitions section of the procedure but before the main block and all internal procedures. In this way the trigger blocks are scoped to the entire procedure and they are made active before any user actions that invoke them can occur.
To build a very simple example procedure to demonstrate some of the rules of run-time trigger processing in Progress:
- Define a button and give it an initial label, then define an
INTEGERvariable as a counter:
- Add a statement to enable the button in a frame. As you learned earlier, this causes both the button and its frame to be instantiated and realized:
- Define a run-time trigger for the button that changes its label so that you can see that the trigger fired:
ON CHOOSE OF bButton IN FRAME fFrameDO:iCount = iCount + 1.bButton:LABEL IN FRAME fFrame = "External " + STRING(iCount).END.- Create a
WAIT-FORstatement that blocks the termination of the procedure and allows the user to choose the button:
- Run the procedure. As you would expect, the button’s label is changed when you choose the button:
TheON CHOOSEtrigger you defined is scoped to the entire procedure and executes each time the button is pressed.To define another trigger for the button in an internal procedure, make these changes to the procedure:
Before the
WAIT-FORstatement the code runs the internal procedureChooseProc. This procedure defines a different trigger for the same button, which identifies the trigger with the labelInternal. The second trigger definition replaces the first one within theChooseProcprocedure.Note that the scope of the button is the entire external procedure because it is defined at that level. The scope of the variable
iCountis also the external procedure for the same reason. But what is the scope of the second trigger definition you just added?It is scoped only to the internal procedure where it is defined. So what happens when you run this version of the procedure? Before you run it, walk through the code in your head to decide what happens.
The external procedure defines a trigger for the button. It then runs an internal procedure that defines a different trigger. That internal procedure then exits, without giving the user any chance to use the new trigger, and its trigger goes out of scope. So what happens when the user chooses the button?
Figure 8–9: Result of trigger example procedure
![]()
Figure 8–9 shows that Progress reverts to the original trigger. This example shows you that Progress effectively keeps a stack of trigger definitions. If a later definition goes out of scope, Progress reverts to the trigger definition that is now back in scope (if there is one).
So how would you see the effect of the internal procedure trigger? You can place a
WAIT-FORstatement inside the internal procedure to force Progress to wait until the user chooses the button.To try this, add this statement to the end of the
ChooseProcprocedure:
Note that you can wait for any event, not just the close of the procedure or its window. This statement waits until the user chooses the button exactly once. Then the
WAIT-FORis satisfied, the internal procedure exits, and the first trigger takes over.To see the result of each button press, run the procedure again. Figure 8–10 shows the result of the first four button presses.
Figure 8–10: Results of running the ChooseProc procedure
![]()
Making a trigger persistent
Adding the second
WAIT-FORstatement is very awkward, and it is pretty close to an absolute rule in Progress that your entire application, not just a single procedure, should have only a singleWAIT-FORstatement. This rule is summarized in the saying: “One world, one WAIT-FOR.” MultipleWAIT-FORstatements can easily get tangled up in each other if they are not satisfied in the exact reverse order from the order they are defined in, and this can result in aWAIT-FORthat doesn’t terminate properly.So how else do you establish a trigger inside an internal procedure or a trigger block, or for that matter inside another external procedure that you call, and have that trigger persist beyond the scope of its declaration?
Progress provides a special statement to let you do this:
The persistent trigger definition can have only this one
RUNstatement (not aDO-ENDblock with any other statements). You can pass optionalINPUTparameters to the procedure you run but noOUTPUTorINPUT-OUTPUTparameters. If you pass parameters, the parameter values are evaluated once when the trigger is defined. They are not re-evaluated each time the trigger executes.To see how you write a persistent trigger and what its effects are, change the
ChooseProcprocedure and add the new procedurePersistProc, as follows:
Now when you run the procedure and choose the button you get a very different result.
Figure 8–11 shows the results of the first three button presses.Figure 8–11: Results of running the PersistProc procedure
![]()
Once you have established the persistent trigger, it remains in effect as long as the object it’s associated with exists, just as a definitional trigger would.
Using the REVERT statement to cancel a trigger
An
ONstatement can contain the single keywordREVERTto cancel an existing trigger definition before it goes out of scope:
The
REVERTstatement cancels any run-time trigger for the event and object defined in the current procedure or trigger. Note that you cannot useREVERTto cancel a persistent trigger. Instead, you must run another persistent trigger procedure that either defines a new trigger or consists of just aRETURNstatement.Defining triggers to fire anywhere
You can use the
ANYWHEREoption of theONstatement to set up a trigger that applies to all objects in an application. Use the following syntax:
Applying events in your application
You have already seen cases where a procedure causes an event to fire “artificially” by using the
APPLYstatement. In theCustOrdersprocedure, each button trigger has toAPPLYtheVALUE-CHANGEDevent to the Order browse to get it to run the internal procedure to display related data for the Order, such as this code for the Next button trigger:
The
APPLYstatement causes whatever trigger is currently active for the event and object to fire. Unlike theONstatement, you must place the name of the event in quotation marks in anAPPLYstatement, if it is a literal value. This makes it possible toAPPLYthe value of a character variable instead. In this way, you can define aCHARACTERvariable, assign it a value of an event name during program execution, and then use that value in anAPPLYstatement, adding flexibility to your programming. For example:
DEFINE VARIABLE cEvent AS CHARACTER NO-UNDO....cEvent = “VALUE-CHANGED”....APPLY cEvent TO <some object>.
By contrast, Progress must know the event for an
ONstatement at compile time to prepare the trigger properly. For this reason the event name can’t be a variable, so you can specify it with or without quotation marks in theONstatement.As another example, here’s a little procedure that transfers keystrokes from one fill-in field to another:
The
LAST-KEYkeyword is a built-in function that returns the value of the last keystroke the user pressed. Every time you enter a letter into the first fill-in field,cFillFrom, theANY-PRINTABLEtrigger fires which, as its name suggests, responds to any printable character typed on the keyboard, and this trigger retrieves that keystroke and applies it to the other fill-in field,cFillTo. So you can apply not just the standard events but even keyboard characters to an object like a fill-in field, if is enabled for input. Figure 8–12 shows the effect of typing Text intocFillFrom.Figure 8–12: Result of typing into cFillFrom
![]()
Using NO-APPLY to suppress default processing for an event
When you type a letter such as A in a field, it naturally appears in the field on the screen. This is called the default processing for the event. If there were a trigger
ON “A” OF cFillTo, then you couldAPPLYthat event to the fill-in field, the trigger would fire, and the letter would appear. This is the normal result of applying an event: both the default processing and any trigger on the event occur. However, some event-object pairs do not get Progress default processing using theAPPLYstatement. For example, applying theCHOOSEevent programmatically to a button executes the trigger on that button but does not give focus to the button in the way that choosing it would.As another twist to this relationship between events and their actions, consider the action on the object that initiates the event, not the one that receives it and does its default processing for the event. Sometimes you want only the trigger action on the target object to occur and not the default processing for the object that initiated the event. In this case, you can use the special
RETURN NO-APPLYstatement at the end of the trigger definition to suppress the default processing on the object that initiated it.To suppress the default processing for an event in your test procedure:
- Add the
RETURN NO-APPLYstatement (along with theDO-ENDstatements to turn the trigger into a block of code):
- Run the procedure and type some text into cFillFrom:
When you type, the keystrokes are applied to cFillTo, that is its default processing. But theRETURN NO-APPLYstatement suppresses the default processing the cFillFrom, so it remains blank.- Add this statement to the trigger:
ON ANY-PRINTABLE OF cFillFromDO:APPLY LAST-KEY TO cFillto.APPLY '*' TO cFillFrom.RETURN NO-APPLY.END.- Run the procedure.You get asterisks in cFillFrom for each keystroke you type:
![]()
The trigger first applies the keystroke to the second fill-in, which causes the character to be displayed. It then applies the literal ‘:*’ to the first fill-in, causing it to be displayed there. Finally, it does a
RETURN NO-APPLYto suppress the display of the character you actually typed into the first fill-in. You could use this sort of code for a password field, for example.Applying a nonstandard event
With the
APPLYstatement, you can actually send any event to any object that has a trigger for that event. Progress executes the trigger even if there is no default handling for the event associated with that object type. Thus, you can use this feature to extend the repertoire of developer events (predefined events with the names U1 through U10) to include any event not normally associated with an object. For example, you could applyCHOOSEto a fill-in field, which does not normally support that event.
|
Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095 |
![]() ![]() ![]()
|