Structured Programming

Who knows what evil lurks behind those traditional menu-tree applications? Event-driven architectures may mean you don't even have to ask the question.


December 01, 1990
URL:http://www.drdobbs.com/architecture-and-design/structured-programming/184408465

Figure 1

DEC90: STRUCTURED PROGRAMMING

STRUCTURED PROGRAMMING

The Cuba Lake Effect

Jeff Duntemann, K16RA/7

It was a wonderful trade: Nancy Kress taught me how to write fiction, and I taught her how to use computers. She has gone on to win the coveted Nebula Award for science fiction, and I, well, I've gone on using computers. But now I can write a character that isn't part of the ASCII table. A good deal all the way around.

Nan published a story some years back that still haunts me. In "Down Behind Cuba Lake," the protagonist is travelling through rural New York State to make amends with an old lover with whom she has had -- and broken off -- a disastrous affair. As night falls she is forced off the main highway by road construction and decides to cut through the back roads near a small lake.

The first back road she takes dead-ends on the shore of the lake. No problem -- back roads can be that way. So she backtracks and tries another dark, twisting road -- and again, winds up headlights to the water. Again, she backtracks -- and again, she winds up somewhere down behind Cuba Lake, on a dead-end dirt road that vanishes into the black water. No matter where she goes, or how far she follows the increasingly narrow and rough dirt roads, she can't get around Cuba Lake. Worse, she can't go back.

It's an exquisite piece of low-key psychological horror. What it means, of course, is that there are some decisions you simply can't rescind, and the more you try, the deeper in trouble you get.

You Can't Get Out

Nan would protest that she doesn't know anything about programming, but anybody who's ever gotten lost somewhere seven levels down in the menu tree of an elaborate transaction-processing application would swear she's been there too.

What I call the "Cuba Lake Effect" is something that almost invariably happens in traditional menu-tree applications that mix modes and menus with data dependencies. You know, things such as this: You're editing a record, and you try to enter a Canadian customer from the Yukon Territory, province code "YT." Whoops, undefined code error -- you forgot to add "YT" to the table when you set it up. (Nobody really lives up there, do they? Yup, they do ....)

The table maintenance screen is up three levels, across, and down two. You hammer the ESC key, scoot up three screens, and back down into table maintenance ... but you get stopped cold: You can't edit tables while you're editing records. So back you go, up a screen, across, and down three. Then your blood runs cold as you remember: You can't get out of record edit mode without closing the customer file. You can't close the customer file while the current record edit screen is in an error mode, and you can't close the record edit screen without deleting the current record, and you can't delete the current record without going through the state/province code key file to bring it up on the screen again, and the key for the current record is in an error state with an undefined value ....

Get me out of here!

Who Chooses the Path?

This sort of nightmare is one major reason that office staffers traditionally hate computers. What I've described is a common situation on traditional mainframe and minicomputer applications. Such applications begin with a top-level menu screen. The user picks a number, and goes "down" into the next level, typically another menu, and continues until some sort of data entry screen or report definition screen is found. There are a number of paths from the top level down to the leaves of the menu tree, but every path is defined by the structure of the menus, and may be constrained by modes and data dependencies. If you change something the return path may change. It may even be blocked.

The essence of a system such as this (which is about as hideous a way of programming as I've ever seen) is that every path is dictated to the user. Navigating through the program is done strictly on the program's terms. In a badly-designed program (which includes most minicomputer and nearly all mainframe software I've experienced) the program's terms don't necessarily include the imperative that the user be able to back gracefully out of any operation without having to yell for help or abort the whole session.

The essence of writing ergonomic software is giving the user more control of what's going on. This begins with providing the user near-absolute control over how to navigate the system. Ideally, this should be the difference between having to follow the roads on a map to get from point A to point B, or simply having to put your finger over point B on the map and saying, "I want to be here." It is the difference between the view from the inside and the view from a height; in the country of the flat, the 3-D view is king.

Event-Driven Architectures

If you've lived your programming life creating endless full-screen menu trees, your first impression might be that this is impossible. It's not impossible at all, but like object-oriented programming, such an ideal requires a whole different way of thinking about the architecture of an application. This new architecture was invented by Xerox, borrowed and popularized by the Macintosh (to the extent that Apple has now convinced itself that Apple did the inventing) and is now (with Microsoft Windows at the vanguard) percolating through most commercial applications for DOS. It's called an event-driven architecture, and if you're not using it yet you're already way behind the times.

Rather than being a maze of full-screen menus, an event-driven application is typically a single screen with two major parts. At the center of the application (and usually at the center of the screen) is the workspace. This contains some representation of the work that is being done. It might be a technical drawing, a spreadsheet, a document, or a database arranged as rows and columns of records and fields. All around the workspace are tools that may be selected to manipulate the workspace and the information within it.

The user selects tools either from the keyboard, with some combination of control keys, or (much more appropriately) with a mouse cursor that zips around the screen and allows the user to "click on" a tool when the mouse cursor is over that tool's representation on the screen. This representation might in fact be a graphics icon, but remember that nothing in an event-driven architecture limits us to a graphics environment. The new Turbo C++ and Turbo Pascal 6.0 environments are mouse-based and truly event-driven, yet both run strictly in text mode.

A Natural for Windows

Nor is there anything in an event-driven architecture that requires the use of windows, but windows are a natural way to return a measure of control to the user in an event-driven system. Think back to the transaction processing nightmare I described a little earlier. In an event-driven system, the user would be editing records in the central workspace of the application. (Editing records is, at the heart of it, what such transaction processing applications are about.) If an invalid state code turned up, the user would simply reach out with the mouse to a tools icon or menu of some sort, "grab" the codes' editor, and open it as a window, either beside or even on top of the record being edited in the central workspace.

You need to think of the user's action as the equivalent of a subroutine call in the work flow. While editing a record, programmers find the need to do some code table editing. Rather than close the edit screen and backtrack through the menu tree to open another screen devoted to code table editing, the user simply "ducks out" from record editing for a while and opens a window into the code tables. Once done with the code tables, you close the code table tool window and you are instantly back where you were in the central workspace when you discovered the undefined state code. No backing up, no opportunity to get lost behind Cuba Lake. When you find a need for a tool, you reach out and grab it. While you're using the tool, your previous work remains, perhaps dormant, in the central workspace. That's the metaphor of an event-driven architecture.

Beneath the Surface

I'm not going to tell you how to code up an event-driven application framework here. It's probably too big a project for a magazine article, and to be honest, 99.999 percent of you would be wasting your time trying to roll your own, because there are far, far better ones on the open market right now than you could ever hope to create yourselves. They're inexpensive, too -- in fact, one of the very best now comes bundled free with every copy of Turbo Pascal 6.0.

Still, it always pays to have a handle on what's going on beneath the surface. So I'll speak in broad terms of how event managers work.

First, to define this thing called an "event:" An event is an occurrence that effects your program but which is not dictated by the flow of control in the program. In other words, an event is not something triggered by an IF statement. It is triggered, instead, by forces outside of the current program, and even outside of the machine itself.

The best example of an event is something that the user does. A keystroke is perhaps the crispest example of an event. The program has no idea what the user will type, or when. Similarly, the user's moving a mouse cursor over a predefined "hot area" (such as a menu bar) is something done when the user chooses to do it. A mouse button click, like a keystroke, is another excellent example of an event.

Events can also come from within the machine. A character from a serial port can be an event, as one can appear at any time once the port is enabled. The system software (DOS, BIOS) can also create events. The best known of these is the "timer tick" that happens 18 times every second. Timer ticks are not beholden to the currently-running program. They march inexorably in step with the hardware timer regardless of what the current program does (unless, of course, the current program inhibits interrupts). Usually, timer tick events don't happen every tick, but every Nticks, providing a programmable-time delay between events.

Similarly, timer ticks can be used to create "now is the time" events; you set a specific date and time when something must happen, and at each timer tick, a simple comparison determines whether it's zero hour or not. If the time is now, the event is generated.

Event Managers

Beneath the surface of an event-driven application must be some sort of event manager. The event manager is a mechanism that watches for events, (typically by "capturing" an interrupt vector signalling an event's occurrence) places them in an application-friendly "wrapper" of some sort, and then queues the wrapped events up for processing in some sort of FIFO (First In, First Out) data structure. The event manager then processes the queued events in order.

Nearly all event-driven systems have this much in common. How the queued-up events are processed is up to the cleverness of the system architect, and varies with the event-driven system or toolkit under discussion. Typically, the programmer associates an event with a procedure, function, or object method that is called when the event occurs. Turbo Pascal 4.x and later, for example, considers function key F10 an event. When the F10 event is detected, Turbo Pascal calls its menuing system and moves the cursor to the menu bar at the top of the screen.

Events can be prioritized, meaning that some events are handled immediately even if others are waiting in the queue. The word "queue" suggest a whole conga-line of events in a row struggling toward their ultimate fate, but that's misleading. In nearly all cases, the event queue is empty or contains one or (on the outside) two events. A good system should not allow events to pile up; there's nothing like watching a system try to execute a queue of a dozen accumulated mouse clicks to drive a user to distraction. If something is begun that absolutely cannot be interrupted, (like saving the work file to disk) event sensing should be suspended until -- but only until -- the critical task is finished.

More typically, an event "freezes" the current task and opens something new in a process akin to (as just mentioned) a subroutine call. You can duck out of editing the work file in Turbo Pascal by pressing any of numerous hot keys such as F10, Alt-F, Alt-C, and so on, each of which generates an event. The edit screen waits patiently while you fool around in the menus; when the menu work is done, the focus returns to the edit file exactly where you left off when you triggered one of the keyboard events.

Event-driven architectures facilitate a level of concurrency impossible in menu-tree systems. Even if the underlying platform isn't multitasking in nature, having three windows open on the screen with an edit file in each is nearly as useful, especially if you can zip from one to another with a single click of the mouse on the selected window.

Getting Involved

People who have been using event-driven systems for years will find all this stultifyingly obvious, but a great many people (particularly programming newcomers) haven't caught on. I think a good many of those holding back assume you have to come up with the event management and menuing code yourself, which simply isn't true. There are a dozen or more solid event-driven application framework products out there, with more appearing all the time.

Most of them, as you might imagine, are limited to C, and many operate only in graphics mode. Still, some of the best support text mode and are in fact available for Pascal. (The best one I've seen for any language, The Zinc Interface Library, is an SAA-compliant C++ product that operates interchangeably in either graphics or text mode; if you're working in C++, I powerfully suggest that you look into it.) TurboPower Software's Object Professional is one that I've mentioned in the past, and will mention again.

Turbo Vision

But the one that most people will be talking about for a while is the event-driven application framework provided as part of the recently released Turbo Pascal 6.0. Borland's Turbo Vision is an "empty application" in object form that you inherit and extend using object-oriented techniques.

Turbo Vision provides a classic, mouse-based windowing user interface in text mode. In fact, the Turbo Pascal 6.0 integrated development environment (IDE) itself was written using Turbo Vision, so if you want to see what a Turbo Vision application looks like, just look at Turbo Pascal.

Turbo Vision's TApplication object type and the object types it includes already contain thousands of lines of code implementing event management, menuing, and windowing, as well as dialog boxes, buttons, pick lists, and other controls. Your part in the programming process is to define a child class of TApplication and add the code specific to your own application.

If your application is fairly simple, you don't even have to write the main program. These three method calls comprise the main program of most Turbo Vision applications, and all three are inherited whole from TApplication:

BEGIN     
	MyApp.Init;     
	MyApp.Run;     
	MyApp.Done  
END.

Init is the application's constructor, and Done is its destructor. You set the application up, you run it, and you shut it down. This is structured programming with a vengeance.

Like its elder cousin Object Professional, Turbo Vision defies easy description in a 3500-word magazine column. The best I can do this time is give you a flavor for how it works, and show you a simple program that uses it. In future columns I'll be speaking of Turbo Vision regularly, now that it's an intrinsic part of Turbo Pascal.

An Experimental Desktop Manager

In only an hour or two, I was able to come up with the prototype of a desk-top manager in Turbo Vision, and I've presented it in Listing One (page 151). It's a prototype because what's there is a menu structure and a couple of dialog boxes; there's no real "guts" to the program. That's by design, because Turbo Vision is a user interface tool, and I wanted to show how it helps you put together an event-driven user interface. The guts are a separate issue.

Working with Turbo Vision begins with the definition of your application object, which is always a child class of the abstract class TApplication. I called mine TDesk (the "T" prefix means "type," and is an Object Pascal convention going back to Apple's Lisa days). You must override three TApplication methods: InitMenuBar, which sets up menus in the menu bar at the top of the screen; InitStatusLine, which sets up any prompts in the status line at the bottom of the screen; and HandleEvent, which is your application's event handler, and in many ways the key to the whole process.

Your own code never actually calls any of these three methods. The application constructor Init calls InitStatusLine and InitMenuBarthem while it's setting up the program. HandleEvent is called from within TApplication's Run method, which is inherited unchanged by your own specific application class.

This struck me very oddly at first, but like OOP in general, it gets more natural the more you work with it. Working with Turbo Vision might be considered a process of setting up a series of effects, and allowing Turbo Visions internals to provide the causes.

When you call the Run method, what happens is that execution vanishes into Turbo Vision (for which source code is not available, sadly) and waits for events to happen. When an event happens, Turbo Vision gets first crack at handling the event. (Some events, like the Alt-X hotkey for exiting an application, are pre-coded into Turbo Vision and inherited by your own application class.) If the event is not something Turbo Vision handles, it passes the event on to your application by calling your application's HandleEvent method. Inside your HandleEvent method, you parse the event passed along by Turbo Vision, typically through a CASE statement. If the event provided by Turbo Vision is something your application knows how to handle, the CASE statement calls an appropriate method or other routine to service the event. If the event is undefined, you have the option of safely ignoring it. See Figure 1.

This, at the highest level, is how the flow of control goes in a Turbo Vision application: Events happen. Turbo Vision queues them up, and gets first shot at processing them. If it cannot process an event, Turbo Vision passes the event along to your application. Once your application processes the event, control eventually falls back into Turbo Vision to wait for the next event.

For simplicity's sake I have created only one method that handles an event in JXDESK: SystemStatistics, which is called in response to the Statistics option in the System menu. The call is made from the HandleEvent method. Note that the event is passed out from within Turbo Vision as a named constant, SysStatCmd, which is simply a name given to the value 102. Such a constant is called a "command" in Turbo Vision. You define commands and associate them with events by passing a command to Turbo Vision along with a menu option definition, when InitMenuBar is called. Note that all the NewItem calls made from within InitMenuBar associate their menu items with the null command except for one: The one that sets up the Statistics menu item on the System menu.

Products Mentioned

Turbo Pascal 6.0 Borland International 1800 Green Hills Road Scotts Valley, CA 95066 408-438-8400 $149.95 Turbo Professional $299.95

The Zinc Interface Library Zinc Software Incorporated 405 South 100 East Pleasant Grove, UT 84062 801-785-8900 $199.95

Read the code for JXDESK a few times, and it should start making sense. Better still, download the .EXE file that I have provided along with the source to JXDESK and play around with it for a while. Like many other things in our complicated world, Turbo Vision shows better than it tells.

The End of an Era

I'll have more to say about JXDESK in my next column, and more about Turbo Vision as well. And I'll wrap this month up by positing that we're coming to the close of another era in structured programming: The era of Doing It All Yourself. Everywhere I look, Pascal and Modula-2 programmers are using screen generators, procedure and object libraries, menu generators, and all other manner of productivity enhancers and canned code. Nobody but the rankest beginners or the hardest-headed compulsives sit down with the naked compiler and roll it all from scratch.

This makes the apps happen a lot faster, but there's a whole crop of new dangers heaving over the horizon, stemming from not really knowing how your "own" code works. Turbo Vision is a case in point; from now on, as much as half of your application will probably remain a total mystery from a source code standpoint, rather than just some canned procs here or there. You can even get away without learning in detail what Turbo Vision does, as long as you can set up menus and an event handler.

Is this good? I don't know yet. I haven't the vaguest idea how DOS works, and don't care much, because it hasn't hurt me. This wasn't always the case; back in the CP/M era we had to understand what the OS did, because we had to modify and extend it to mate with our oddball hardware. And before CP/M, it was every hacker for himself. I even wrote my own operating system once, lordy.

Now, more and more of the code we use in creating our applications comes standard in a can from somebody else and can be taken for granted once we learn how to use it. Obviously, we're heading somewhere. When I figure out where, I'll be sure to let you know.

_STRUCTURED PROGRAMMING COLUMN_ by Jeff Duntemann

[LISTING ONE]



{--------------------------------------------------------}
{                                                        }
{                        JXDESK                          }
{                                                        }
{  Jeff's Experimental Desktop Manager for Turbo Vision  }
{                                                        }
{                               by Jeff Duntemann        }
{                               For Turbo Pascal V6.0    }
{                                                        }
{--------------------------------------------------------}


PROGRAM JDesk;

{ These are all Turbo Vision units: }
USES Objects, Drivers, Views, Menus, Dialogs, App;


CONST
  SysStatCmd = 102;
  NullCmd    = 101;


TYPE
  PDesk = ^TDesk;
  TDesk = OBJECT(TApplication)
    PROCEDURE HandleEvent(VAR Event: TEvent); VIRTUAL;
    PROCEDURE InitMenuBar; VIRTUAL;
    PROCEDURE InitStatusLine; VIRTUAL;
    PROCEDURE SystemStatistics;
  END;


VAR
  Desk: TDesk;   { Allocate an instantiation of TDesk }

{ TDesk method definitions: }


PROCEDURE TDesk.HandleEvent(VAR Event: TEvent);


BEGIN
  TApplication.HandleEvent(Event);
  IF Event.What = evCommand THEN  { If the event was a command }
  BEGIN
    CASE Event.Command of
      { The system invokes a method in response to a command: }
      SysStatCmd : SystemStatistics;
    ELSE
      Exit;  { Exit the event handler; i.e., do nothing }
    END;
    ClearEvent(Event);
  END;
END;


PROCEDURE TDesk.SystemStatistics;


VAR
  R: TRect;
  D: PDialog;
  C: Word;


BEGIN
  { Create a new dialog: }
  R.Assign(25, 5, 55, 14);
  D := New(PDialog,Init(R,'System Stats'));

  { Create and insert controls into the dialog: }
  R.Assign(9, 6, 21, 8);
  D^.Insert(New(PButton,Init(R,'OK',cmCancel,bfNormal)));

  { Execute the modal dialog: }
  C := DeskTop^.ExecView(D);
END;


PROCEDURE TDesk.InitMenuBar;


VAR
  R: TRect;


BEGIN
  GetExtent(R);
  R.B.Y := R.A.Y+1;
  MenuBar := New(PMenuBar,Init(R,NewMenu(
    NewSubMenu('~p~',hcNoContext,NewMenu(
      NewItem('~A~bout',          '',0,NullCmd,hcNoContext,
      NewItem('How to ~R~egister','',0,NullCmd,hcNoContext,
      nil))),
    NewSubMenu('~S~ystem', hcNoContext, NewMenu(
      NewItem('~S~tatistics',    '',0,SysStatCmd,hcNoContext,
      NewItem('Set ~T~ime',      '',0,NullCmd,hcNoContext,
      NewItem('Set ~D~ate',      '',0,NullCmd,hcNoContext,
      NewItem('~R~un DOS app...','',0,NullCmd,hcNoContext,
      NewLine(
      NewItem('E~x~it','Alt-X',kbAltX,cmQuit,hcNoContext,
      nil))))))),
    NewSubMenu('Address ~B~ook',hcNoContext,NewMenu(
      NewItem('~O~pen book',  '',0,NullCmd,hcNoContext,
      NewItem('~C~reate book','',0,NullCmd,hcNoContext,
      NewItem('~P~rint book', '',0,NullCmd,hcNoContext,
      nil)))),
    NewSubMenu('~T~erm',hcNoContext,NewMenu(
      NewItem('Link to ~M~CI',       '',0,NullCmd,hcNoContext,
      NewItem('Link to ~C~ompuServe','',0,NullCmd,hcNoContext,
      NewItem('Link to ~B~ix',       '',0,NullCmd,hcNoContext,
      NewItem('~T~erminal window',   '',0,NullCmd,hcNoContext,
      nil))))),
  nil)))))));
END;


PROCEDURE TDesk.InitStatusLine;

VAR
  R: TRect;


BEGIN
  GetExtent(R);
  R.A.Y := R.B.Y-1;
  StatusLine := New(PStatusLine, Init(R,
    NewStatusDef(0, $FFFF,
      NewStatusKey('~Alt-X~ Exit',kbAltX,cmQuit,nil),nil)));
END;


BEGIN
  Desk.Init;
  Desk.Run;
  Desk.Done;
END.









Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.