It was 1971, and I was a college sophomore at a beer bust put on by a fraternity hungry enough for pledges to admit anyone. I was dressed in a bright yellow sweater and bright purple bell-bottoms, trying very hard to grow my hair without realizing the ultimate futility of the effort. (Can you picture me with shoulder-length hair? Sigh. I can't either.)
As often happens at parties, an impassioned discussion between two people begins to attract a crowd, and before long a considerable fraction of the party was watching me debate some half-sloshed prelaw type on the merits of bringing the United Nations into the Vietnam conflict. Or maybe it was the moral imperative of passing the E.R.A. I forget--because all the while I was half-watching a gorgeous young woman who was hanging on my every word, following my discourse with this look of unbelieving awe on her face.
Shall we say this was not an everyday occurrence, and her interest inspired me to even greater heights of eloquence. Was it my sweater? My sideburns? Or could it be that at least one girl in this five-and-dime college appreciated the power of brains over biceps?
The prelaw slurred some minor insult at me and slunk away, defeated. The crowd wandered off--but she hung on, eyes like sapphires riveted upon me, and in our single moment of intimacy she breathlessly revealed the secret of her admiration: "You know, you always talk in complete sentences!"
My God! She had thrown away the gum and was chewing the wrapper! What about my passion? What about my social awareness? What about my obvious allegiance to the greater good of mankind? No matter--she went home with some football player, and I went home with my complete sentences. I guess in the long run we both got what we deserved.
There's a lesson here. Rarely are our creative efforts admired for what we as creators consider most admirable. Isaac Newton wanted to be remembered for his theology--calculus was just a throwaway. The seminal object orientation of Smalltalk was ignored for 15 years because people were too busy ooh-ing and ahh-ing at its primordial GUI.
I expect this will happen more and more these days, as it ironically grows easier and easier to create a flashy user interface and positively murder to sort out an application's internals. It's humbling to keep in mind as you struggle to master event-driven programming under Turbo Vision or Windows: They're not going to admire the intricate subtlety or robustness of your event loop. They're going to admire the color coordination of your scroll bars.
It's time to duck under the scroll bars and confront that goblin that's been making so many self-educated programmers tear out their hair these modern days. Event-driven programming is the way it's gonna be from now on. Get used to it. And consider the fact that without it, those pretty scroll bars just wouldn't come together as easily.
I've already laid a lot of the conceptual groundwork for Turbo Vision and event-driven architectures generally, in my December 1990 column. If you have it lying around, it wouldn't hurt to read it through once again. I don't have the room to do much recap here...but a little won't hurt:
Deep in the heart of Turbo Vision's application object, TApplication, a loop runs in endless circles waiting for certain things to happen. Mostly those certain things are keystrokes, mouse clicks, and changes in mouse position. When one of these significant user-generated happenings happens, TApplication creates a Pascal record called an event record, fills it with a description of what happened and where, and sends it bubbling upward for some other part of the application to respond to.
A lot of the problem with Turbo Vision is that so much of this process happens beneath the surface. A great many events the user generates never get high enough for your own code to "see" them--other parts of the Turbo Vision infrastructure grab them and react to them first. In general, your code sees an event only after all the rest of Turbo Vision has had a crack at it.
But on the other hand, the major reason for creating an event-driven application is to allow this submerging of detail beneath the level of the application program. In one sense, the whole raison d'etre of event-driven programming is to formalize the handling of user input, so the application framework can handle much of that input on its own. Turbo Vision modularizes a program into distinct and well-defined contexts, and separates relevant from irrelevant input within each context. When you're not working specifically in a given context, Turbo Vision hides input irrelevant to that context.
Once you've become familiar with the details of this service (which is unprecedented in the Turbo Pascal world) its peculiarities cease to be peculiar.
Turbo Vision doesn't really do away with the old-fashioned "loop for input" style of programming. The loop is there. You just can't see it anymore. It's down in the Run method of TApplication, constantly checking its built-in indicators to see if any sort of input has appeared since it last looked. If new input is available, Run wraps it up in a Pascal record called an event record. It then routes the event record to its appropriate destination. What that destination is depends on a number of things, most of which we'll cover in time.
Many events are handled internally to TApplication, without your ever knowing, perhaps, that an event occurred. Some events are handled automatically in methods inherited by views you "created"--so that even one of "your" views can handle events without your necessary knowledge or understanding.
If it were that simple--pass up an event from TApplication, and let an appropriate view grab it and react to it--Turbo Vision wouldn't be the conceptual problem that it is. The path an event takes from the user to the end of one particular event road can be a tangled one. An event can change shape while it winds its way to its destination as well, not once but several times. You can watch the path an event takes (sort of) if you know where to look, and when.
Let me give you an example, from the HCalc program published here a couple of months ago. HCalc has a fairly simple menu, with only two items: one that creates a new mortgage window, and another that closes all open mortgage windows. When your user pulls down the Mortgage menu and clicks the mouse on the New menu option, that's an event.
Even though it may appear that way, the event doesn't go directly to the menu. It goes first to TApplication, which determines (by reading the location of the mouse when it was clicked) which menu option was being selected. It then routes the event to the TMenuBar object.
TMenuBar performs a translation. The mouse click event it was handed (and this happens where you can't see it) becomes a different sort of event, one called a command. The transformed command is then sent to your specific application object, THouseCalcApp, for processing. THouseCalcApp has its own event-processing method called HandleEvent. When THouseCalcApp is routed an event, it first gives its parent class (TApplication) a shot at handling the command. (You can see this in THouseCalcApp.HandleEvent, the first state-ment of which is TApplication.HandleEvent.) If TApplication chooses not to handle the command, THouseCalcApp then handles the event itself--in this case, by calling the constructor for a new mortgage window, thus creating the window. I've drawn a picture of the process, with all its ups and downs, in Figure 1.
Whew. Let's think about what's gone on so far. The user clicks the mouse on a menu item. This click is an event. TApplication detects the event, and creates a mouse-click event record. It sends this record to the menu-bar object it owns, TMenuBar. TMenuBar sends the event to the particular menu that was accessed; in this case, the Mortgage menu. That menu is set up to respond to a mouse click event by creating a new command. This command has a name (cmNewMortgage) and the menu sends the command to the application object, THouseCalcApp.
THouseCalcApp sends the event "upstairs" to its parent object type to look at and perhaps handle. (Note this is the second time TApplication has worked with this event!) TApplication does not know how to handle a cmNewMortgage command, so it does nothing. Finally, THouseCalcApp handles the command by creating a new mortgage.
If you stare at it long enough, the process begins to make sense. Now let's take a look and see what has to be done in the code you write to make all this happen.
First of all, why this translation of a mouse event into a command? Why not just route the mouse event through the menu and on to THouseCalcApp? The answer is that two different kinds of events can select the New option on the Mortgage menu. One is the mouse-generated event we followed in the example, which we call a positional event, because it relates to a specific position on the screen. The other is a keyboard event, which (for reasons I'll explain a little later) is called a focused event. Either kind of event can pick a menu selection, but once the menu selection is picked, there is only one unambiguous path until the event is handled. The command translation serves to force multiple inputs into the menu to a single input out of the menu and into the application's event handler.
This connection of a menu item with a command is defined when you define the menus in the menu bar. It's done in the THouseCalcApp.InitMenuBar method, near line 190 in HCALC.PAS. The definition of the New item looks like this: NewItem('~N~ew','F6',kbF6,cm NewMortgage, hcNoContext,.
Each item on a menu gets a line such as this in the InitMenuBar method. The first item is the name of the menu as it appears in the menu, in string form. The tilde characters surround the single-key abbreviation for the menu item. This abbreviation may be typed, when the menu in question has the focus (more on focus later on; for now consider the focus to be where the keyboard is currently sending characters) and is highlighted in a distinctive color or gray shade to indicate that fact.
The second parameter is the displayable text form of the single-key shortcut that will select that menu item, even when the menu is not pulled down. The third item is the key code for that shortcut. Here, "F6" is what is shown on the New menu line to the right of the word New, and kbF6 is what tells Turbo Vision that the F6 function key is the shortcut to that menu item.
The fourth parameter is the name of the command this particular menu item issues when selected, however it is selected (mouse, menu selection, or shortcut key, regardless). A command is in fact a numeric value you define as a constant and give (one would hope) a descriptive name. The cmNewMortgage constant is defined at line 8 of HCALC.PAS. The value (here, 199) is arbitrary, as long as it doesn't conflict with any predefined command constants. I always begin with the value 199 and work down from there. The "cm" in front of the command name is merely a convention--but there's enough difficulty in understanding TV code as it is, and every reasonable convention should be embraced like a life jacket in a hurricane.
Initializing a TMenuBar object with only one two-item menu is simple and compact. Initializing a seven- or eight-menu menu bar, where each menu has six or seven items, can take pages of nested calls to NewMenu and NewItem. Setting up the menu bar is one of those cases where interactive tools (such as Blaise's Turbo Vision Development Toolkit, described last month) really earn their pay.
One nice thing about events, TV-style, is that they allow you to "stub out" a menu item without doing anything extra. If you've done your UI-design homework and you have your complete menu structure down on paper before you start coding, you can create the entire menu structure long before you write any of the code that implements the menu choices. You "stub out" a menu item by simply not writing anything that handles the command generated by that menu item.
And then when you finally get around to writing code to support that menu item, well, then it's supported.
This is because command events generated from the menu bar aren't sent to one specific destination for handling. In a way, generated commands are passed from hand to hand through the system until some object somewhere knows how to respond to that command. If no object responds to the command, well, nothing bad has to happen.
There are in fact several different ways for events to be routed through a Turbo Vision application. One way is the broadcast event, which, when generated, goes to literally everything within the application that has the ability to respond to events. High-level commands such as "Close up everything and shut down!" are generally broadcast events. I did this in HCALC.PAS, as I'll explain a little later.
Broadcast events are rare, however. Most of the time, you'll be dealing with focused events, which, while they don't have any specific destination, have a definite path to follow.
Somewhere in every active Turbo Vision application is the focus. Think of the focus as the spotlight: Where it shines is where events go. The focus is typically a control; that is, a software gadget such as a button, an edit field, or some other construct that accepts input from the mouse or keyboard. The focus is indicated by a distinctive color on color screens, or by some sort of bracketing characters on monochrome screens. You can move the focus by pressing the tab key, or by clicking on the new focus location with the mouse. There are some shortcut keys as well; pressing F10 always brings the focus to the menu bar, no matter where else you were before. (This isn't quite true. F10 won't get you out of a modal view, such as a typical dialog box. This is one of those "gotta remembers" that will drive you nuts during that crucial first week or so of trying to comprehend TV....)
Think of the focus as the other end of an event hose that begins deep inside TApplication. You can move one end of the hose--the end where the events come out--but the opposite end of the hose is inextricably and invisibly connected to the place where events come from.
Moving the focus from a user perspective is simple: You select something, or hit a shortcut key that moves the focus someplace specific (such as the menu bar). Understanding the routing of events to the focus from a programmer's perspective requires understanding a few other things, such as what a modal view is.
In that seminal August 1981 issue of BYTE that introduced Smalltalk to a slathering world, Larry Tesler (he of the somewhat later Object Pascal spec) wrote of a T-shirt he had that read, "Don't Mode Me In!" The T-shirt's complaint was of environments that got you into a mode of some sort, and while in that mode, severely restricted the meaning and effects of ordinary user operations. The Cuba Lake Effect has something to do with modes; while you're in Menu Mode you can't jump out and check available memory, or clear unnecessary files from disk to make room for something new--perhaps something the menu is demanding before letting you move on. You're stuck in the menu until the menu lets you out on the menu's own terms.
A modal view is a view that won't let you out until you actually close the view. The best example is a typical dialog box such as the one that accepts mortgage values within HCALC.PAS. Once you bring the dialog up, you either have to cancel it or accept its values and close it before you can go on to do anything else. Trying to click on another window or on the menu bar while a dialog box (or any modal view) is open will just get your efforts ignored.
There is always a modal view in control while a TV application is running. Nearly all of the time, that modal view is the application itself, which just means you have to close the application and exit to DOS to get out of it. Occasionally you'll open a more limited modal view like a dialog box. Think of it as pulling the fences in a little closer to force you to focus (there's that word again!) on the tasks immediately at hand.
Focused events begin their journey at the current modal view. Most of the time that means the application itself. When a dialog box is open, all positional (for example, mouse) events and focused events begin at the dialog box, and the portions of the application outside the dialog box never see them. (This is why clicking on things outside the dialog box doesn't get you anywhere--the mouse events themselves are not being "seen" by anything outside the dialog box!)
Mouse clicks are sent through the modal view's subviews in Z-order, one by one, until the subview that contained the original positional event finds the mouse event and handles it.
Focused events, on the other hand, must travel down the focus chain. The focus chain starts at the current model view, and moves to the modal view's selected subview. For example, if the application itself is the modal view, the selected window (if there is one) is the first step on the focus chain. If the selected subview has subviews itself, the focus chain continues at the selected subview owned by the current subview. And so it goes, from selected subview to selected subview, until the end of the line is reached, at a selected view that has no subviews. A view with no subviews is called a terminal view. That selected terminal view at the end of the focus chain is what we informally call "the focus"--it's where keystrokes and commands end up.
So if you've selected a TInputLine object in--and thus owned by--a window, that TInputLine is the focus, and when you press a key, that keyboard event passes down the focus chain to be handled by the TInputLine object. I need to emphasize again that the focus chain has nothing to do with relationships in the Turbo Vision object hierarchy. Object ownership is the key here.
Keyboard events typically travel all the way to the terminal view at the focus. Commands, on the other hand, can be handled anywhere along the focus chain. Certainly they can be handled by the terminal view--but they can just as easily be handled by the window that owns the terminal view.
A broadcast event is well-named: It is sent to every single subview of the current modal view, regardless of the focus chain. A broadcast event is called for in one of two general circumstances: When you want all views to be able to respond in their own way to some sort of command; or when you don't know where or even whether a certain kind of view is active in the application and need for it to take some sort of action. So you send the event to everybody--and anybody who knows how to react to it will.
I used a broadcast event in HCALC.PAS to tell all the mortgage windows to call their destructors and close up. You can have any reasonable number of mortgage windows on the screen at once, so you might as well broadcast the close up message to all of them. The path the event takes from user to action is even more complex than the one we followed for creating a new mortgage window. Here's the sequence:
The user pulls down the Mortgage menu and selects Close all. The positional event represented by the mouse click is sent to the menu bar, which determines which menu item was selected. Just as with the New menu item, a command is generated in the menu bar, this one called cmCloseAll. The command is sent to the current modal view, which in this case is the application itself, THouseCalcApp. Keep in mind that we have not yet generated a broadcast event. The cmCloseAll command is not a broadcast event and is handled no differently from the way cmNewMortgage was handled earlier. The cmCloseAll event finds its way to the HandleEvent method of THouseCalcApp. There, the cmCloseAll command triggers a call to the THouseCalcApp. CloseAll method, which contains this single statement:
This statement generates a broadcast event that emanates from the desktop object to all subviews owned by the desktop object. The Desktop parameter is a pointer to the view from which the broadcast event is to be disseminated. The evBroadcast parameter defines the event as a broadcast event. (The Message function has other uses with other kinds of events.) The cmCloseBC is the command defined as the broadcast event directing mortgage windows to call their destructors and close up. The @Self parameter points to the object that originated the broadcast event, in case any of the recipients of the event need to know who's hollering. (In this case, they don't, so the @Self pointer goes unused.)
At this point, the desktop begins passing the broadcast event to all of its subviews. If you look in the TMortgageView.HandleEvent method, you'll see there is a handler for cmCloseBC events. If a cmCloseBC event turns up, the method handles it by calling the destructor Done on the mortgage window.
Subviews of the desktop that don't understand the cmCloseBC event get a shot at handling it as well, but because cmCloseBC isn't present in their HandleEvent methods, the broadcast is simply ignored.
More on Turbo Vision next column (and the column after that, and maybe a few more as well...). This is not easy stuff to swallow, and if you don't quite get it yet you shouldn't feel bad, especially if you haven't tried your own hand at a TV application. Just reading about TV isn't enough. You have to go in there and start generating error messages and system crashes. By all means read and tinker with HCALC.PAS and the demo programs Borland provides. But at some point you have to come up with your own little program and have at it. If the aggravation bothers you, keep this in mind: Once your little training program works, its usefulness is gone. You learn nothing from your successes compared to what you learn from your mistakes.
Copyright © 1992, Dr. Dobb's JournalChewing the Wrapper
Wrestling Events
Where Events Come From
Following a Simple Event
Getting There from Here
How Events Know Where to Go
Focused Activity
Mode-ing You In
The Focus Chain
Broadcast Events
Who:=Message(Desktop,evBroadcast,cmCloseBC,@Self);
Practice Makes Comprehension