I get at least one or two letters a week from people demanding to know what the "KG7JF" after my name means. One chap guessed it was a lodge slogan, in the manner of IOOF, while another reader wanted to know if it was a clue in some sort of national treasure hunt. Sorry, gang. I don't do lodges and I don't do treasure hunts. (I don't even buy lottery tickets, since I only take sucker bets.) KG7JF is my amateur radio (ham) call-sign, meaning that I've earned the right to buy or build my own radio transmitter and thereby (within a body of accepted practice) disturb the ether.
It's what I do when I can't stand computers anymore, and I give ham radio credit for keeping my head from exploding on numerous occasions. Not everybody builds their own radios anymore, which is a damned shame. In fact, my current radio project is designing a two-way FM radio from integrated circuits designed for the cordless phone industry, in order to make building amateur gear simpler and less expensive. (Not to mention a way of asking the Fallen Viking to forgive me...) The project has been an amazing education in a lot of ways, not the least of which is my discovery of the parallel between modern radio design and object-oriented programming.
I've been involved with radios a lot longer than I've been involved with programming. I have 25 years experience building radios the old way; that is, part by part, transistor by transistor, soldering a component in here and another there, each addition a custom job, and the whole project taking weeks or even months of loose moments to complete.
Does this sound familiar, in a metaphorical way? Lord knows, it should. Most of us, especially when we're in a hurry, build programs line by line, each line conscious of the one before, and in its own way a custom job. Moreover, most of us get this nagging feeling that it's all way too much work and that there has got to be a better way.
In designing the radio I'm calling Chipper, I set out with full intent of buying whole radio subsystems in the form of integrated circuits, rather than building subsystems, a transistor, and a resistor at a time. I expected it to take four or five ICs (rather than 25 or so transistors) to create a useful dual-conversion narrow-band FM receiver. Instead (to my shock) I found that it took two.
The boss IC is something called the MC3362 from Motorola. To build a radio, you add components to its 24 pins. If you want a bare-bones radio, you only add a few components. To create a more elaborate radio, you add more components. The important point is that you add things; you don't change them. There are some fundamental behaviors exhibited by the MC3362 that come through no matter what, and there are some other behaviors that can at best be masked or reinterpreted.
The MC3362 is a literal black box. A signal emerges from one pin for intermediate frequency filtering; you send it through a filter of your own design, and then feed the filtered signal back into the little black chip through another pin. You can only control what emerges from the chip. The stuff inside is beyond your control.
But it's worse than that. The old saw of "out of sight, out of mind" comes into play with a new twist: What you can't see is pure hell to understand. I used to be able to point to a resistor in a circuit and say, "That's a bias resistor. It sets the operating point for transistor Q14. To change the operating point, change the value of the resistor." No more. The MC3362 has plenty of transistors (about 75, in fact) and plenty of bias resistors. But they're buried in the middle of the chip where you can't change them. You not only can't change the operating points, you can't even sample them to see what they are. You can't "tweak" them and watch the consequences. You remain in a state of enforced ignorance. Encapsulation in black polyethylene is pretty total.
And the upshot is that I don't understand the MC3362. I know what goes in and what comes out, sort of, but the dynamics of what goes on inside is undocumented and might as well be magic. Worst of all, my lack of understanding of the hidden parts of the system cripples my understanding of those parts of the system that I can see.
This is the Tragedy of the Black Box--which is a great deal of what is wrong with Turbo Vision. So much happens behind the scenes that even the stuff that we see on the surface becomes mysterious, legendary, and contrary to conventional wisdom. The Black Box problem will dog you throughout your experience with Turbo Vision. Get used to it. Strive whenever you can to understand even the parts of the system that don't require your direct intervention. Knowledge is power, and (less obviously) knowledge is cumulative.
So. Let's start here. There are actually two necessary technical descriptions of Turbo Vision: One is of what it's made of, and the other is of what it does. Both are mutually connected in a multitude of ways, but it's marginally easier to first approach Turbo Vision from the standpoint of its structure. This will probably take an entire column. It's a big, subtle, and confusing subject.
From a height: A Turbo Vision application is a whole crew of objects, allocated on the heap and linked by pointers. There is one boss object, the application object. The application object owns all other objects present in the application. This ownership is a question of pointer referents, and has nothing to do with object hierarchy relationships. Don't get the two confused! We're going to discuss object ownership first, long before we get into the details of the Turbo Vision object hierarchy.
We have to define some technical terms here: A view in Turbo Vision is any object that can display itself to the screen. Anything that can't display itself is not a view, and is what I call a mute object. The term "mute object" was introduced very early in the Turbo Vision Guide (by me) and then forgotten about when I passed the project into other hands. It's a good term, however, and I'll continue to use it.
The application object can own other objects because it is a special kind of object called a group. A group is a special kind of views that own other views. A group is the root node of a linked list of views (or other groups) that it owns, and we say that it owns them by virtue of their being part of that linked list.
The structure of Turbo Vision can best be understood in terms of groups. In a sense, Turbo Vision is made of groups and very little else.
You can instantiate and run an object of type TApplication, and it's an interesting thing to do. Not much happens (remember, TApplication is a boilerplate application that does no actual work) but you will see something on your screen. What you see is a menu bar at the top of the screen, a status line at the bottom of the screen, and a pattern of halftone characters in the middle, completely covering the rest of the screen. You're not actually looking at TApplication. The TApplication object itself is not a typical view and has no on-screen presence. Instead, it is a group that owns (at minimum) three views: A menu bar view, a status line view, and a desktop view, and these are what you're seeing.
For the sake of clarity, I lied a moment ago. The desktop view isn't really a view. It's another group, and what you see is not actually the desktop object but another object owned by the desktop group, called a background. The background view is simply a way of displaying a pattern on all parts of the screen not taken up by other things. This is a good example of an important truth: A group can own other groups. The application group (an instance of TApplication) owns the desktop group (an instance of TDesktop).
The desktop group, in turn, owns the background object (at very minimum) but it also owns all the visible elements you create for your program: windows, dialog boxes, and so on. See Figure 1. Note in Figure 1 that only groups (the elliptical objects) can own other things. A window is a group, and we don't actually see the window object itself on the screen. Instead, we see the component views that the window owns: its pane, its frame, and its scroll bars.
The ownership relationship between two objects is not something defined at compile time, but is something that happens strictly at runtime. At runtime, an object can be inserted into a group by way of an Insert method present in every group: MyGroup.Insert(PtrToMy-Object);. This is the way that any arbitrary object is inserted into a group. A pointer to the object is passed to the group's Insert method, and the referent of PtrToMyObject is inserted into the linked list of objects whose root node lies in MyGroup. (There are a lot of operations like this that deal with objects only through pointers. You must get comfortable with pointers before you have a ghost of a chance of understanding Turbo Vision!)
If you've compiled and run my HCALC program published last month, you'll get a fell for this. When HCALC begins running, there are no windows on the desktop. When you pull down the Mortgage menu and select New, you create a new mortgage window and insert it into the desktop group. The desktop group owns that new window, and all other windows you may create later on. This is why we can say that the desktop group owns literally everything in your application except the status line and menu bar. Typically, over the life of an application session your desktop group will insert into itself and later delete numerous objects as windows are opened and closed.
The notion of a group is critical when we begin putting windows together. A window is a group--and therefore, we can insert into the window group whatever "standard parts" the window needs--and only those parts it needs. If a window doesn't need any scroll bars, don't insert them. A window may need more than a single pane--so insert two or three or however many will do the job. The versatility of the TV group concept is stunning.
As objects, groups aren't very "bright." They don't have a lot of native intelligence. Mostly, groups exist as glue to tie other objects together. The smarts in a group are pretty much all in the objects owned by the group. The group itself acts as a fairly dumb linked list manager, and that's all.
There are times when all the components of a group must act together. When you move a window, the parts of the window all have to move at once, and in the same direction for the same distance, or the window will come apart while you watch. The group must thus have some way of telling all the objects it owns to do the same thing at the same time.
In formal terms, the group object has the power to iterate an operation over all the objects it owns. In other words, you define a procedure to be performed (typically by creating a pointer to said procedure) and then have the group command each object it owns to execute that procedure.
Every group has a method named ForEach to do the job. ForEach takes as its only parameter a pointer to (and this is extremely important!) a far local procedure to be performed. That procedure can't be a method, but the procedure can be local to a method and also call a method. (This is one ugly shortcoming of Turbo Pascal: Methods cannot be accessed directly through procedure pointers.) Don't fret the details for now. It's enough to understand at this point that a group can force all the objects it owns to execute a given procedure: MyGroup.ForEach (@ DoSomething);. Here, every object owned by MyGroup is instructed to execute the DoSomething procedure. If MyGroup owns any groups, each group then, in turn, orders all the objects that it owns to execute DoSomething.
Structurally, that's the greater part of understanding Turbo Vision: the inserting and deleting of groups into your desktop, and the creation of groups to be windows of various kinds.
There's another subtlety, however, involving the way that groups are displayed on the screen. Views can plainly overlap--create two mortgage windows in HCALC and drag them over one another--and something has to dictate which one is on the top. That something is called Z order, and it's a concept you have to master before trying to put a group together.
We're used to thinking of the screen as a Cartesian plane with X and Y coordinates. The notion of depth is a foreign one, especially in text mode screens such as the one used by Turbo Vision. TV, however, adds the dimension of depth to the text screen. In a sense, it provides a third axis--the Z axis, coming after X and Y--to define that dimension of depth. When two views overlap on the screen, one is "underneath" another--hence the depth dimension.
Again, look to TApplication for a simple example. When an application object first runs, it only owns three things by default: the desktop, the status line, and the menu bar. The desktop is "on the bottom" and both of the other views sit on top of it, partially obscuring it.
The first view inserted into a group is the view "behind" all the other views inserted later. By this rule, the desktop is inserted into the application object before both the menu bar and the status line. If you inserted the menu bar and status line first and then inserted the desktop, neither the menu bar nor the status line would be visible--because the desktop would be in front of both and block them from view!
You can assign a numerical correspondence to Z order. The first view inserted is #1, and the number increases as you stack views one atop another. (This becomes explicit when you apply numbers to open windows that overlap: Until you start swapping them around, the window with the highest number is the window on the top of the heap.) The component parts of a window illustrate Z order fairly well. Consider Figure 2.
A Turbo Vision window is a group. Most windows you will encounter consist of at least two objects: a frame object and a pane object. Many windows also have a scroll bar object, as shown here. (Some windows have two scroll bar objects. A window can also have more than one pane.) The figure shows the Z order in terms of looking "down" into the text screen from above. The first object to be encountered is the pane object. Beneath the pane is the scroll bar, and beneath everything is the frame.
The pane is set up to be one character position smaller than the frame in both X and Y. This is why the pane doesn't hide the frame even though the pane is "above" the frame in Z order. The scroll bar, however, does overlap the right edge of the frame and hides that edge from view.
Z order is set initially by the order that objects are inserted into a group. The first object inserted is on the bottom, and all subsequent objects should be thought of as in layers, with the last object inserted into the group on the top. There are some circumstances under which the Z order can change, but this usually involves the order of opened windows on the desktop. The component parts of a window have one Z order, established at compile time, that never changes.
If you ever try to put a group of views together and some of the views don't appear on the screen, check your Z order. You may have unwittingly put one of the views behind another, larger view, totally obscuring the "missing" view. Remember: First view inserted is on the bottom, last view inserted is on the top.
One of the criticisms I have of Turbo Vision is that it has no easily graspable set of Bit Principles--you might say it's a collection of exceptions that totally overwhelms its rules. This is especially evident when you look closely at TV's family of views and how they have to be used. Some views need to be subclassed to use them, and some may be used as is. Some views must not be subclassed--and there's really no way to keep it all straight except to remember a whole raft of special rules. Let's run down the list to get oriented.
TView is one of those "abstract classes" that isn't intended to be instantiated and used. It contains all the essential common characteristics of a view, and you subclass it to make a specific kind of view.
In fact, most of the time you won't even directly subclass TView, but will instead subclass one of TView's more specific children. TView is the ancestor class of all views, and Turbo Vision provides numerous child classes that you either use as is or subclass further.
TGroup is technically a view (because it is a child class of TView) but you should think of groups as special cases among views. A group has no screen presence of its own (as mentioned earlier) but instead is a group of views bound together into a linked list. As with TView, TGroup is an abstract class that isn't useful in and of itself. You create custom groups that do the work you require by subclassing TGroup. Most of the Turbo Vision components you'll be using frequently are groups. TApplication, TWindow (as well as their subclasses) and TDialog are all groups.
TDesktop is a group, owned by TApplication. You don't typically instantiate TDesktop, because TApplication does it for you, and the only reason you would subclass TDesktop is to create some arcane variant of the standard desktop--which I promise you is advanced Turbo Vision and not something to be approached lightly.
TBackground (a view) provides the textured background displayed behind all you other windows in a TV application. It's not good for much other than that, and in most TV work you'll neither use it directly (the TDesktop class controls the one you see) nor subclass it.
TWindow (a group) will act as the parent type for about half of the objects you'll end up creating under TV. (The other half will be dialog boxes and everything else.) TWindow is another abstract class that serves no use if instantiated directly. You have to subclass it and flesh out the subclass with some meat. In HCALC.PAS, for example, I created the TMortgageView window type by subclassing TWindow. The child class is given a mortgage object and a mortgage-specific constructor (among other things) to enable it to display a mortgage table on the screen.
But what about scroll bars and interior panes? You need to remember that the TWindow type is a group--and you flesh it out in part by giving it new fields and methods, but also in part by inserting subviews (such as scroll bars and panes) into it by using the Insert method. The key to knowing which way to flesh out a window view is to insert things that are also views or groups--like the panes and scroll bars--and add anything else (that is, ordinary types, mute objects, and sub-programs) as fields and methods. That's why TMortgageView gets a TMortgage field-- TMortgage is a mute object, not a view or a group.
TDialog is a child class of TWindow (and hence a group) but it is handled in a radically different way from the typical TWindow subclasses you'll create. (Ahh, the grand confusion of it all!) A dialog box is a special kind of window that asks questions of the user, and based on the answers to those questions, carries back some important information to the application that owns it.
For reasons I'll explain shortly, you never subclass TDialog. You use it as it is, and flesh out the provided TDialog class by inserting views or groups into it. Mostly, what you insert are called controls: buttons, text entry fields, or check boxes, and other user-action input devices.
Dialog boxes differ from windows in a number of ways. One is that the size of a dialog box is set when its constructor is called, and cannot be changed thereafter. That is, you cannot zoom or resize a dialog box.
There are other more technical differences as well. But the biggest difference between windows and dialog boxes is that dialog boxes belong to a special class of objects called resources. A resource is a standard Turbo Vision object with a standard, random-access way of being written to or read from a special Turbo Vision stream. (Streams are the canonical way of storing Turbo Pascal objects in files -- and yet another another column.) If you have frequently used "standard parts" in your programs, you can store them in a resource file on disk and not have to initialize them when the application using them runs. Instead, you simply read them off the resource stream, whole and intact and ready to use.
You can arrange to get your custom objects onto or off of streams by providing custom code within the objects. With resources, there's no arranging to be done. All the Turbo Vision standard types may be used as resources, because the standard types know how to write themselves to and from streams. And that's why you never subclass a dialog box. Subclass it, and the subclass won't be able to work with TV's stream I/O system. It won't be "standard" anymore.
For similar reasons, you shouldn't try to insert a nonstandard view or group into a dialog box. Stick with the TButton, TCheckBoxes, TRadioButtons, and other standard controls if you intend to make your dialog box into a resource stored in a resource life.
HCALC doesn't use resources, and its one dialog box is instantiated at run-time, every time the program is run. (I saved a considerable amount of code by using a nonstandard control in my dialog box, preventing me from easily considering the dialog box a resource.) However, once the dialog box is created, it is "tethered" by a global pointer variable and the same dialog box may be used again and again for the duration of the application session. If you're curious to see how this is done, look at the code implementing the constructor for the HCALC application object, THouseCalcApp.Init.
This is a good place to jump in and mention a new product from Blaise Computing, the Turbo Vision Development Toolkit. The greater part of the TVDT is in fact a resource editing and management system for Turbo Vision.
The idea is that you can create standard dialog boxes and other elements that may be used by any number of Turbo Vision applications, just by reading them from their resource file. The TVDT gives you interactive design tools for creating dialog boxes, menus, and strings, along with all the machinery you'll need to quickly read them from disk.
The difference between using the TVDT and doing it by hand is astonishing. For one thing, setting up a dialog box in a program requires code to initialize the box in the right size, insert all the controls, etc., etc. If you do that in a resource editor, you can carve all the setup code out of your apps, and bring in your resources from the resource file with a single easily documentable Pascal statement.
But mostly, futzing dialog boxes by hand is a miserable, trial-and-error kind of ordeal in which you set up a rectangle, set up the coordinates for the various controls, then compile and run the app to see what sort of mess you've made. With the TVDT, you just draw the dialog box on the screen, drag controls to their correct positions, then save it all out to a resource file when it looks the way you want it.
The TVDT has one additional trick up its sleeve: It can convert a Turbo Vision resource to a Microsoft Windows resource -- which, of course, means a Turbo Pascal for Windows resource. So while it's not practical to create a single application source file that serves both Turbo Pascal platforms, you can at least automatically convert a Turbo Vision resource to a TPW resource, and thus the more elements of your TV system you place in a resource file, the more easily you'll be able to port a TV application to TPW.
The TVDT manual is clear and complete, and the software hasn't revealed any important bugs. The product is unique, and if you intend to do any work at all in Turbo Vision, it's just plain essential.
I'll return to resources in a later column. This month I've been able to give you an overview of Turbo Vision structure. We're about a quarter of the way there. The best and worst of TV is tied up in how it operates behind the scenes: its event-driven programming mode. We'll take a hacksaw to the corner of the black box next column, and see what we can see. I hate to say it, but it won't be all that much -- you may find (as I'm finding) that programming in Turbo Vision is as much an act of faith as it is a matter of skill.
The Turbo Vision Development Toolkit Blaise Computing Inc. 819 Bancroft Way Berkeley, CA 94710 415-540-5441 $149.00
Copyright © 1991, Dr. Dobb's JournalLine-by-line and Part-by-part
The Downside of Black Boxes
The Stuff Apps are Made Of
An Example: TApplication
Ownership
Commanding the Troops
Z Law and Z Order
Views and Their Children
Resources
The Turbo Vision Development Toolkit
Partway There
Products Mentioned