OpenEdge Development: Progress 4GL Handbook


Table of ContentsPreviousNextIndex
Examining the Code the AppBuilder Generates

In the previous chapter you didn’t written much code, but rather let the AppBuilder do it for you. This might seem inappropriate in a book about how to learn to program in the Progress 4GL, but there’s a reason for this approach. You need to be familiar with the syntax the AppBuilder generates for you when you put together a window in this way, but you needn’t get into the habit of writing much code like this yourself. The tools are there to do this for you, and you should take advantage of them.

Having said that, in this chapter you’ll look at more of the code the AppBuilder generated for your window, so that you understand how it works. By doing this, you can learn some new 4GL statements used to define visual objects and database queries. But then this chapter explains some of the ways in which this won’t be typical of the complete applications you write. As you progress through the book, you’ll learn about how the OpenEdge development tools can help you create applications where there is no compiled 4GL code for the user interface at all! But to work in that mode, you’ll need to learn a little more about dynamic programming in Progress.

So this chapter provides a grounding in the static definitions that are the starting point for the more dynamic object definitions you’ll use in later chapters. This chapter includes the following sections:

Viewing the entire sample procedure code

In the "Using the Section Editor" , you saw some of the procedural code that gets the window up and running, such as the enable_UI procedure, but not the code that defines all the objects in the window. Take a look at that now. As before, since the AppBuilder generates and maintains that code for you based on how you lay out objects in the design window, it doesn’t show the code to you in the Section Editor.

To view the entire procedure, you can select either Compile Code Preview again or File Print from the AppBuilder’s main menu to print a hard copy of the entire procedure.

Now look through the entire procedure to see some of the syntax that defines the window and its contents. If you look at a printed listing of the procedure file, you’ll notice numerous ANALYZE-SUSPEND and ANALYZE-RESUME preprocessors mixed in with the code. The AppBuilder uses these to separate out code it has generated and maintains for you from code you can add to the procedure yourself. These are stripped out of the Code Preview listing for you, making it a little easier to read. Just try to look beyond these things for the time being to learn what the statements are that really define the window.

The Definitions section

The top of the procedure file is the Definitions section. This section starts out as an empty template. It provides a space where you can enter documentation information about your procedure. You can access this section by selecting it as the Section type in the Section Editor, as shown in Figure 5–1.

Figure 5–1: The Definitions section

It’s a good idea to fill this section in and keep it up to date.

There’s just one statement generated for you in this section: CREATE WIDGET-POOL. Objects that make up an application are sometimes called widgets. Progress uses widget pools to provide memory management for all of the objects that are created in a procedure, including the frames, fields, and buttons in your window as well as some data management objects such as queries. This statement gives you an unnamed widget pool for everything in the procedure. When the procedure finishes executing, all of the memory its objects use is released. This is a good default, but when you get to more advanced programming, you learn how to create named widget pools that have a lifetime you define for them. For the most part you don’t have to worry about them.

Below the CREATE WIDGET-POOL statement is an area where you can define variables and parameters for your procedure. These variables are scoped to the external procedure. That is, any variables you define here are available for use throughout the procedure file, including any internal procedures it contains. You’ll learn a lot more about scoping in "Procedure Blocks and Data Access."

Continuing your look through the Code Preview or printed listing, the next section of the file has all the Preprocessor Definitions the AppBuilder uses to define queries, field lists, and other syntax that can be used in other AppBuilder-generated code throughout the procedure. You’ve seen this before and looked at a few specific definitions in the "Looking at preprocessor values in the Code Preview" .

Window, button, browse, and frame definitions

The next section is labeled Control Definitions. These are definitions for the objects in your window.

Defining a handle variable for the window

First is a definition for the window itself, or rather for a handle used to point to the window’s definition:

DEFINE VAR CustWin AS WIDGET-HANDLE NO-UNDO.

There are a couple of things to note about this statement before you examine what a handle really does:

  1. You can see that the AppBuilder has abbreviated the keyword VARIABLE to VAR. You can abbreviate many Progress 4GL keywords in this way, but you cannot use arbitrary abbreviations. That is, each keyword definition in the language also defines the acceptable abbreviation of the keyword. If you want to confirm the minimum abbreviation for a keyword or find out the kind of statements in which it is used, you can always look it up in the Keyword Index at the back of the third volume of the OpenEdge Development: Progress 4GL Reference. Not all keywords have abbreviations. You should generally not abbreviate any keywords. Typing keywords in full eliminates any chance of a conflict with a future keyword that starts the same way, and generally makes your code more readable. As described in the "Using the Intelligent Edit Control and its shortcuts" , the Edit Control provides aliases for you to eliminate manually typing many of them in full.
  2. The variable CustWin is defined as a WIDGET-HANDLE. The term widget applies to all sorts of things, including visual objects such as fields and buttons, as well as other things that can have handles, including queries and even procedures themselves. WIDGET-HANDLE is a synonym for the keyword HANDLE, because a single Progress data type accommodates all these different kinds of objects. Since there is really just one HANDLE data type, and since it is used for more than just objects that you might consider widgets, this book always uses the keyword HANDLE.

In the "Creating the window" , you’ll look a little more about what the handle does when you get to the code that uses it to create the window.

Defining a button

Next is another kind of DEFINE statement. This one defines not a variable, but the first of your buttons:

DEFINE BUTTON BtnFirst
LABEL "First"
SIZE 15 BY 1.14.

There is a separate DEFINE statement for each different type of object you can have in your application. You can learn all the particular attributes you can set for each kind of object from the OpenEdge Development: Progress 4GL Reference or the online help, but they’re similar in form. The DEFINE statement first names the object (BtnFirst in this case) and then has a list of whatever attributes you want to define for the object and their values. In this case the AppBuilder has defined the button’s LABEL to be First, because this is what you set it to in the design window. The SIZE is a standard size the AppBuilder defaults, which you can change by resizing the button in the design window.

The following three statements are the definitions for the other three buttons in the window.

Defining a query

After the DEFINE BUTTON statement, the next statements define the two queries your procedure uses:

DEFINE QUERY OrderBrowse FOR
Order SCROLLING.
DEFINE QUERY CustQuery FOR
Customer SCROLLING.

These statements define the query objects that your code later opens using the specific WHERE clause you defined in the Query Builder. The Order query got its name by default from the browse that displays its data, which you’ll see next. The Customer query got its name from the frame it’s in. You’ll learn more about queries in "Using Queries."

Defining a browse

Next is the statement to define your Order browse:

DEFINE BROWSE OrderBrowse
QUERY OrderBrowse NO-LOCK DISPLAY
Order.Ordernum FORMAT "zzzzzzzzz9":U
Order.OrderDate FORMAT "99/99/99":U
Order.PromiseDate FORMAT "99/99/99":U
Order.ShipDate FORMAT "99/99/9999":U
Order.PO FORMAT "x(20)":U
WITH NO-ROW-MARKERS SEPARATORS SIZE 65 BY 6.67 ROW-HEIGHT-CHARS .57 EXPANDABLE.

This code first names the query the browse is defined for, and then the list of fields to display, along with their formats. Finally there’s a list of attributes for the browse itself, in a phrase starting with the keyword WITH. You can set these and other attributes in the Browse property sheet you looked at in the "Using property sheets" .

Defining a frame

Next is a section marked Frame Definitions, where the window’s one frame is defined:

DEFINE FRAME CustQuery
BtnFirst AT ROW 1.48 COL 8
BtnNext AT ROW 1.48 COL 24.6
BtnPrev AT ROW 1.48 COL 41.2
BtnLast AT ROW 1.48 COL 58
Customer.CustNum AT ROW 3.38 COL 13.4 COLON-ALIGNED
VIEW-AS FILL-IN
SIZE 9 BY 1
Customer.Name AT ROW 3.38 COL 37 COLON-ALIGNED
VIEW-AS FILL-IN
SIZE 32 BY 1
Customer.Address AT ROW 4.57 COL 13 COLON-ALIGNED
VIEW-AS FILL-IN
SIZE 37 BY 1
Customer.City AT ROW 5.76 COL 13 COLON-ALIGNED
VIEW-AS FILL-IN
SIZE 27 BY 1
Customer.State AT ROW 5.76 COL 47 COLON-ALIGNED
VIEW-AS FILL-IN
SIZE 22 BY 1
OrderBrowse AT ROW 7.67 COL 13
WITH 1 DOWN NO-BOX KEEP-TAB-ORDER OVERLAY
SIDE-LABELS NO-UNDERLINE THREE-D
AT COL 1 ROW 1
SIZE 86 BY 13.67.

Here the code defines the position of each object in the frame. The exact position of the objects depends on how you laid them out in the design window. The four buttons are all at row position 1.48, counting in full character units.

Next come the fields from the Customer table. There are no DEFINE statements for these because the field definitions are taken automatically from the Data Dictionary definitions for the Customer fields.

Then the frame definition places the browse control at column 13 of row 7.67 .

Finally, the frame definition has its own WITH clause, where it sets the following frame attributes:

For more information on any of the frame attributes, see their descriptions under the Frame Phrase entry of the OpenEdge Development: Progress 4GL Reference.

Following the Frame Definition section are the Procedure Settings, which are specially formatted comments with information the AppBuilder uses internally. You should never edit special sections like this one.

Creating the window

Now things are going to get a little more interesting. The next part of the generated code, labeled Create Window, has a new type of statement in it that requires a brief explanation of a very basic Progress 4GL concept, that of static versus dynamic objects.

Static objects

Your procedure contains several DEFINE statements in it for a variable, a browse, and four buttons. These are called static objects because they are fully defined in the 4GL syntax that names them. Because of this, the Progress compiler knows most everything it needs to know about them when it compiles the procedure. This allows the compiler to store a complete structure in the r-code that defines each static object. At run time the interpreter scans the r-code and builds each object based on its definition.

Dynamic Objects

The window is a different kind of object—a dynamic object. You use a CREATE statement rather than a DEFINE statement for it. When you create the object you associate the object with a HANDLE variable that must already be defined. It is for this that the DEFINE VARIABLE CustWin statement you saw earlier was coded. The handle acts as a pointer to a structure that describes the object, but it’s different from the structure resulting from a DEFINE statement in that the structure is only built up at run time, as the program is executing. This allows you to set some or all of the object’s attributes based on program conditions. So, in effect, the CREATE statement creates an empty shell to be filled in with the object’s description, and the handle points to that shell. Here’s the CREATE statement for your window:

CREATE WINDOW CustWin ASSIGN
HIDDEN = YES
TITLE = "Customers and Orders"
HEIGHT = 13.67
WIDTH = 86
MAX-HEIGHT = 16
MAX-WIDTH = 86.2
VIRTUAL-HEIGHT = 16
VIRTUAL-WIDTH = 86.2
RESIZE = yes
SCROLL-BARS = no
STATUS-AREA = no
BGCOLOR = ?
FGCOLOR = ?
KEEP-FRAME-Z-ORDER = yes
THREE-D = yes
MESSAGE-AREA = no
SENSITIVE = yes.

Around this code is an IF-THEN-ELSE statement:

IF SESSION:DISPLAY-TYPE = "GUI":U THEN
CREATE WINDOW CustWin …
ELSE {&WINDOW-NAME} = CURRENT-WINDOW.

This rather cryptic-looking sequence effectively says: “If you’re running in the GUI environment, as opposed to on a character device, then create the new window CustWin, which will appear as its own identifiable display space on the desktop. Otherwise use the default (and only) window that’s always there for character environments.”

This DISPLAY-TYPE test is the answer to a question that might have popped into your head:

Why has the AppBuilder made the window dynamic when everything else is static?

There is no DEFINE WINDOW statement in the 4GL, only the CREATE WINDOW statement. And this, in turn, is because Progress 4GL procedures are designed to be compilable for different environments largely without change, including graphical and character environments. There’s only one “window” in a character environment, and that is the entire display device. So your code can never ask a character device to create another window. Thus, to have the same code compile and run in both GUI and character, creating the window the GUI environment requires must be conditional. And that is exactly what dynamic objects are for: to let your procedures decide at run time what objects to create and what attributes to give them.

To set a dynamic object’s attributes, you normally reference the handle in later program statements. However, in this case the CREATE WINDOW statement itself has an ASSIGN phrase that sets all the window’s attributes to their proper initial values. In principle, you can define a dynamic object by associating it with a handle, and then set and reset its attributes as needed.

Setting attributes of dynamic objects

The next part of the procedure consists of mostly internal comments for the AppBuilder’s benefit, but there’s one executable statement in it, and you’ll look at it to learn one or two more things about dynamic objects:

IF SESSION:DISPLAY-TYPE = "GUI":U AND VALID-HANDLE(CustWin)
THEN CustWin:HIDDEN = no.

This statement means: “If the session’s Display Type is GUI and the window handle named CustWin is valid, then set the window’s HIDDEN attribute to no.”

The VALID-HANDLE built-in function tests to see if the structure the handle points to has been properly associated with an object.

Will the handle be valid? It should be because the procedure created the window that uses it just above this. But in your own procedures, you can use handles long after they’re defined. You must always make sure that they point to valid structures before you use them.

This language statement demonstrates that in the 4GL you can set the attributes of a dynamic object after the CREATE statement by using a reference to the object’s handle, followed by a colon, followed by the attribute name. You can read attributes in the same way, as in:

IF CustWin:HIDDEN = no THEN
     .
     .
     .

Using dynamic objects is a very powerful way to write general-purpose procedures that can handle a whole set of variations on any common pattern in your application, whether it is windows with different titles, sizes, and contents, or browses on different queries with different columns displayed. You’ll learn a lot more about these dynamic language constructs in later chapters.

Defining triggers

Skip down to the part of the code marked Control Triggers. Here you’ll find the trigger blocks you defined for the buttons. Before these button triggers, there are a couple of standard AppBuilder-generated triggers to capture window events. Take a look at the second window trigger block:

ON WINDOW-CLOSE OF CustWin /* Customers and Orders */
DO:
/* This event will close the window and terminate the procedure. */
APPLY "CLOSE":U TO THIS-PROCEDURE.
RETURN NO-APPLY.
END.

Note: The :U tag that follows the quoted string tells the compiler to leave this string out of its list of strings that might be sensible to translate into other human languages. Since the word CLOSE is just part of the program logic and not something a user would ever see or want to see in a different language, it should never be translated.

WINDOW-CLOSE is one of those events like the CHOOSE event for a button. Remember that each type of object has its own set of events it can capture. WINDOW-CLOSE, logically enough, is the event that a window receives when it is closed, for example, by choosing the standard close-window box in the corner of the window.

The code then cascades this event down to the running procedure itself, by applying the CLOSE event to the procedure. There’s a special built-in function that always holds the handle of the currently running procedure: THIS-PROCEDURE. The APPLY statement makes an event happen just as a user action would. The event in this case is the CLOSE event for the procedure. Keep this in your mind for a few moments, and you’ll soon see what the CLOSE event does.

The RETURN NO-APPLY statement just means: “Skip whatever action was in the queue of user interface actions, because we’re getting out anyway.”

The button triggers

Next come the four button triggers you defined yourself. Just look at the first of them to confirm the syntax of the ON statement:

ON CHOOSE OF BtnFirst IN FRAME CustQuery /* First */
DO:
GET FIRST CustQuery.
IF AVAILABLE Customer THEN
DISPLAY Customer.CustNum Customer.Name Customer.Address Customer.City
Customer.State
WITH FRAME CustQuery IN WINDOW CustWin.
{&OPEN-BROWSERS-IN-QUERY-CustQuery}
END.

When you define triggers in the Section Editor, it masks the syntax of the ON statement somewhat by putting the event name and the object name into fill-ins that you can select. It also automatically adds the IN FRAME qualifier for you, based on which frame contains the object.

Triggers as event-driven code

There’s one important thing to note about the trigger blocks. In "Introducing the Progress 4GL," you learned that language statements are generally executed or processed in the order in which they appear in the procedure. Now you need to think about the difference between statements being executed and statements merely being processed. Definitional statements such as the DEFINE VARIABLE and DEFINE BUTTON statements aren’t executed at all. They just tell Progress to set up the structures they define for later use. The trigger blocks, however, are executable blocks of code, but they aren’t executed when they’re first encountered. They are just set up to be executed when the event they are defined for occurs. You can think of the Progress compiler marching down through the procedure and defining the r-code for each executable statement in sequence. When it encounters a trigger block, it sets aside a special part of the r-code with those statements in it, keyed by the event that triggers the code.

This concept is fundamental to the nature of OpenEdge applications. These applications are called event-driven, because to a large extent the procedures in the application just set up blocks of code, and even entire procedures, to be available in memory when user-initiated events call for them.

Looking at the main block

So far most of the code you’ve looked at has been set-up code. It is either definitions of objects and variables or triggers to execute when events happen later on.

Finally you’ve arrived at the part of the procedure that is actually executed when you run the procedure. This is the main block of the procedure. Note that main block is not a Progress 4GL syntax element or a required structure in any way. It is really just a convention observed by the Section Editor. After all the definitions and triggers, you write the executable code for the procedure itself.

The main block of an AppBuilder-generated procedure that creates a window is entirely standard. You can add more initialization code to the block if you wish, but normally you won’t want to remove what it already does. A few of these statements won’t mean a lot to you yet, but for now take a quick look at this block to see a bit more about how events are set up for a typical GUI application.

The first statement simply assigns the value for a built-in function you saw earlier, the CURRENT-WINDOW. As the comment on the statement explains, this determines defaults for proper parenting of dialog boxes and frames to the windows with which they are associated:

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

The next statement is more interesting. Remember that back in the Trigger section there is a trigger on the WINDOW-CLOSE event, then another trigger for when the user clicks on the close button in the upper-right corner of a window. This event in turn applies the CLOSE event to the executing procedure itself:

ON WINDOW-CLOSE OF CustWin /* Customers and Orders */
DO:
/* This event will close the window and terminate the procedure. */
APPLY "CLOSE":U TO THIS-PROCEDURE.
RETURN NO-APPLY.
END.

Now you have come to the next step in this cascading series of actions:

ON CLOSE OF THIS-PROCEDURE
RUN disable_UI.

When the user chooses the close button in the window, a trigger applies a CLOSE event to the procedure. Now here in the main block is another trigger block that defines the action for that CLOSE event, namely to run an internal procedure called disable_UI. This AppBuilder-generated procedure is the counterpart of enable_UI. It deletes the C-Win structure that defines the window and then deletes the procedure itself.

So now the AppBuilder has set up all the actions necessary to clean up when the window and the procedure that created it are no longer needed.

The next statement, PAUSE 0 BEFORE-HIDE, sets up a standard GUI default for automatically hiding windows and frames when the user selects some other object that is on top of them.

Now you get to the main block itself. The AppBuilder emphasizes this by giving the header name MAIN-BLOCK to the DO-END block, which is the heart of the procedure:

MAIN-BLOCK:
DO ON ERROR UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK
ON END-KEY UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK:
RUN enable_UI.
IF NOT THIS-PROCEDURE:PERSISTENT THEN
WAIT-FOR CLOSE OF THIS-PROCEDURE.
END.

The qualifiers on the DO statement aren’t important for now. They simply assure that on an error, a cancel, or an escape request from the user, the block terminates. You’ll learn more about undoing blocks in "Managing Transactions."

The first action the block takes is to run the internal procedure enable_UI. You’ve seen that enable_UI opens the queries, gets a Customer, displays it and its Orders, and enables all the fields. Remember that enable_UI doesn’t have to create the window because one of the few executable statements in all the code you looked through before you got to the main block was the CREATE WINDOW statement. So that has already happened before this point in the code.

Don’t worry about the meaning of THIS-PROCEDURE:PERSISTENT just yet. This condition isn’t true for this window when you run it by itself, as you’re doing. So Progress executes the statement WAIT-FOR CLOSE OF THIS-PROCEDURE.

The WAIT-FOR statement is the crux of any event-driven application. A standard procedure in an older application, or any procedure that simply performs a task and then returns to its caller, proceeds through its list of statements and then ends. When Progress gets to the final executable statement in a procedure like that, it automatically destroys the procedure (giving back the memory its variables used) and returns to the caller.

But in our event-driven procedure, all that really has happened when this last statement is reached is to create a window, set up a few triggers, open two queries, display a record and a browse, and enable some fields. If the procedure were to terminate as soon as that was done, the user would have no chance to interact with the window.

To experiment and see what happens with and without the WAIT-FOR statement:
  1. Highlight the two-line statement with the WAIT-FOR keyword in it:
  2. From the AppBuilder menu, select Edit Format Selection Comment.
  3. This option puts comment markers around the text you selected. The Comment and Indent options are a handy way to try things out with and without bits of code you’re testing.
  4. Choose the Save button or select File Save from the menu to save h-CustOrderWin1.w back to the operating system.
  5. Now comes the tricky part. If you simply run the window procedure now from the AppBuilder, it works perfectly well. That is, the window comes up and stays up until the user closes it. How come? The AppBuilder is smart enough to realize that whenever you run any procedure that has a window, it has to persist so that the user can interact with it. So it effectively adds a WAIT-FOR statement for you for testing purposes.
To see the actual effect of your change in a run-time environment::
  1. To bring up a separate editor window, select Tools New Procedure Window from the AppBuilder menu.
  2. Type the statement: RUN h-CustOrderWin1.w.
  3. Press F2 to run this procedure.

The test window flashes almost imperceptibly and then disappears. All the initialization actions happen but then the procedure terminates. That’s why you need the WAIT-FOR.

To correct this code:
  1. Highlight the two lines you commented out and select Edit Format Selections Uncomment.
  2. Choose the Save button to resave the procedure.
  3. Select your Editor window and press F2.

Now the window comes up and stays up, waiting for the events that fire its trigger code, as you choose buttons, and then finally the close event that terminates the procedures and destroys the window.

This exercise illustrates why you have to tell Progress not to terminate the procedure until the user is done with it. This is what the WAIT-FOR statement does. And when you write a WAIT-FOR statement, you have to tell Progress to wait for some particular event, because otherwise the procedure would never end.

So the statement says to wait for the CLOSE event on the procedure. And when will this happen? It will be when the user closes the window, as you’ve seen. So at that point the main block ends, the CLOSE trigger fires, which executes the disable_UI code to clean up the resources the procedure and the window use, and everything goes away.

The internal procedures

Following the main block you find the code for the two internal procedures the AppBuilder generated, disable_UI and enable_UI. If you use the Section Editor to define internal procedures, the AppBuilder places them at the end of the source file and keeps them in alphabetical order for you. When the compiler encounters an internal procedure it acts much as it does when it finds a trigger block. It compiles the code and places it in a part of the r-code identified by the procedure name rather than by an event. When Progress encounters a RUN statement for the internal procedure as it executes, it transfers control to the statements for the internal procedure and then returns to the main procedure when it completes.

You can place internal procedure definitions anywhere within the source procedure file. By convention you should place them at the end, but this is strictly an organizational preference. If you use the AppBuilder and its Section Editor to create your procedures, which you should almost always do, then they will organize the code consistently for you.

Contrasting procedural and event-driven programs

Understanding the difference between procedural and event-driven programs is one of the most important requirements of learning how to write OpenEdge applications. The program you have written so far is a simple but good example of a procedural program. It executes basically from the top down. When it comes to a statement to RUN another procedure, such as the calcDays internal procedure, then it initiates that procedure, passes parameters into it, and when it gets to the end, returns any output parameters, and that’s the end of it. There’s no code placed off to the side for events initiated by the user. The programmer determines the procedure flow. A program doesn’t prompt the user for input until it’s ready to. The user has little ability to move around in the interface flexibly. This is typical of a character interface application. The simple diagram in Figure 5–2 illustrates top-down or hierarchy programming.

Figure 5–2: Procedural top-down application flow

When the procedure mainproc.p runs a.p, and then a.p runs a1.p, then mainproc.p, a.p, and a1.p are in memory. When a1.p returns, its context is removed and it goes out of scope. When a.p returns, its context is deleted. And only then can the code run b.p, which might run b1.p. The thread of execution goes right up and down through this hierarchy of called procedures.

By contrast, your sample procedure from this chapter is a simple but very good example of an event-driven procedure, as illustrated in Figure 5–3. Most of the code the interpreter encounters as it initializes the procedure doesn’t actually get executed. It just sets up bodies of code to run when the user requests it.

Figure 5–3: Event-driven application flow

Advantages of the AppBuilder file format

You have seen that the AppBuilder applies a special format to the procedures it generates, regardless of whether they represent a graphical user interface definition or just procedural logic for your application. When you open any procedure created with the AppBuilder, it parses this special format, which includes some specially formatted Progress comments that it alone is expected to read, and identifies all the different sections of the procedure, including object definitions and internal procedures.

Scanning through a long procedure in a listing, or in an editor window, or even in the Code Preview window, doesn’t give you a very good overview of what the procedure does, how it’s put together, and what its contents are. By contrast, when you use the Section Editor, you get a consistent predefined format to your procedures, with only the parts you’re generally interested in shown to you for review or editing. You can get a proper listing of all the elements in the procedure, either all together by choosing the List button or grouped by Section type. You can also print individual sections from the Section Editor. You can print the entire file from the main menu.

The Section Editor provides shortcuts for inserting all manner of text into your procedure, under the Insert menu shown in Figure 5–4.

Figure 5–4: Section Editor Insert menu

The following options appear under the Insert menu:

All these options are useful aids that can increase your productivity and accuracy as a programmer.

There’s one more extremely important reason to use the AppBuilder whenever possible. In the example you’ve worked with in this chapter, the AppBuilder generates 4GL code and manages its format in a source file. The AppBuilder can do much more than that. In conjunction with the Progress Dynamics development framework and Progress SmartObjects, the AppBuilder can convert some kinds of procedures into data-driven application components, whose definitions are stored in a repository database and created from that data at run time, eliminating the need for procedural code altogether. This is part of a very advanced concept in OpenEdge development. Keep in mind that any procedure that is readable by the AppBuilder is in a better position to be converted to another useful form automatically, which won’t be the case with hand-written procedures.

Looking ahead

Everything you’ve looked at in these first chapters looks like a pretty easy and powerful way to use the language and the tools to put applications together. And yet there’s a lot more to it than this. In some ways you’re still at the snowplow stage of learning principles that you’ll later apply in very different ways. So what are the essential limitations of the kind of AppBuilder-generated procedure you built in this chapter?

Reusable components

For one thing, the sample procedure is not easily reusable. If you wanted to build a hundred table maintenance windows that all work much the same way (and that’s a basic part of nearly any real business application), you’d have to set up an assembly line process to create them all. In the end you’d wind up with many procedures that create windows that all look and act more or less the same, but they would all be separate procedures requiring separate maintenance and testing. If you needed to make a change to your design, you’d need to make that change to all these separate procedures, and then retest and redeploy them. This would be a major headache and a source of unreliability in your application.

At the same time, you would undoubtedly want to extend the behavior of these windows far beyond what the simple Customers and Orders window does. You might want a real toolbar, for example, with standard icons and a menu. You might want the browse to perform a lot of additional tasks, such as sorting and column reordering and so forth. You might want other kinds of controls in the window, such as drop-down lists of valid values. If you coded each of these additional features for each window, you’d have a tremendous amount of work on your hands.

So OpenEdge provides a set of standard components that do many of these things for you, which you can use to build many different kinds of application windows, as well as the back-end business logic those windows talk to. You can get to know these Progress SmartObjects™ in other OpenEdge documentation.

User interface independence

You might also like to make changes to the interface of your application, and even to change the client platform your application runs on, without having to rewrite your procedures. OpenEdge provides you with the tools to do this as well.

Distributed applications

Another limitation of your sample procedure is that is presumes that the client session running the interface has direct access to the database the application data is coming from. In a modern application, this is normally not the case. Whether you’re running your application on the Web with a browser-based UI, or simply providing global access to your database server from client machines running all over the country or all over the world, you won’t be able to provide all of your users with the same kind of direct database connection that you may have had with older host-based or client/server applications. Later chapters in this book introduce you to how to use the Progress 4GL to construct business logic procedures that can run close to your database server using the OpenEdge AppServer™ technology.

Dynamic programming

And perhaps most remarkable of all, in "Using Graphical Objects in Your Interface," you’ll learn about 4GL constructs that help you build dynamic procedures that can handle whole classes of behavior, so that one procedure can replace dozens or hundreds of older procedures that all did a similar kind of job for you. In this way, you’ll learn how to write more effective and certainly more flexible and maintainable applications by writing far less code than you used to.

So there is a lot of exciting territory ahead. But before you get into some of the more advanced topics, such as how to work with the OpenEdge AppServer and how to write dynamic procedures, there are a lot of important and very powerful basics still to cover. The next chapter, for instance, goes into more depth on some of the block-structured principles that make the Progress 4GL work and how you can use these blocks to retrieve and manage application data. Onward!


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095
Table of ContentsPreviousNextIndex