Structured Programming

Until now, Jeff never paid much attention to the notion of Turbo Vision resources--something akin to an ISAM manager for an ordinary Turbo Vision stream. He also uses Blaise's Turbo Vision Development Toolkit to explore resources.


December 01, 1992
URL:http://www.drdobbs.com/tools/structured-programming/184408903

DEC92: STRUCTURED PROGRAMMING

For those (few) of you whose neighborhoods haven't been invaded yet, let me introduce you to a phenomenon: Retailing Writ Large. Price Club and its clones like Sam's Club and Price Savers are popping up everywhere you look, with 60-foot corrugated steel ceilings and boxes of Honey Nut Cheerios so big that when you get them home you realize they don't fit in any of your kitchen cabinets.

On Sunday, it's the 21st century bazaar, with throngs of people pushing massive carts down aisles wider than the street I grew up on, feverishly grabbing toilet paper by the 64-pak and five-gallon buckets of barbecue sauce. At every turn are line reps plying you with free samples of 36-grain Energy Bars, Endless Rainbow Gourmet Jelly Beans, and Mamacita Rosita's Quik-Frozen Taco Mix. Forget lunch. You'll be stuffed before you get halfway down Aisle 1.

This all may or may not be a good idea; I keep thinking a lot of barbecue sauce must go bad before your typical family of four can get through it. On the other hand, it's plainly the future: low prices, pleasant and disciplined (if somewhat scarce) young personnel, and most of the necessities of life amidst a sprinkle of its luxuries in a highly calculated mix. It works like this: You go into Price Club just to get a gallon of milk and a box of Cheerios, cheap. But in the process, you pass by a multitude of other good things that you use every day at astonishing prices, and one by one they start flying off the shelves into your cart. Then before you know it, you start tossing in jugs of Chivas Regal, wristwatches, Madonna CDs, a silk shirt or two, and then a streamlined resin chaise lounge recliner. I've managed to get out of there for $50.00 on a good day, but my friend Pat Thurman WA9NGP says he rarely makes it to the door for less than $100.00. It is to boggle.

Like almost anything else you examine closely on this most-interesting planet, there is a lesson in the warehouse clubs: Get people in the door with good prices, put them in a buying frenzy, and they'll buy lots more stuff than they ever intended to. And I point it out here because it looks like software has begun to be sold in much the same way.

I've been speaking with a company called SofSource, which distributes software in an interesting fashion. They take simple, self-explanatory, mostly horizontal applications and video games and package them up in minimalist fashion for display in mass-market outlets. The package price on these items is typically $5.95 or $6.95. The idea is that if you've already thrown 64 rolls of toilet paper and a silk shirt in your shopping cart, what's another $6.00 for a piece of software? Toss it in the cart!

It seems to work. According to SofSource, they sell thousands of copies of a package every month for the first 18 months or so that it's on the shelves. Depending on what the package is, they tell me authors get between $3000.00 and $5000.00 per month on their royalty scale while the package is selling briskly. That's not riches, but riches aren't the idea anymore--the way I see it, programmers should quit killing themselves trying to become millionaires and perhaps spend a little more time with their kids instead.

The Distribution Problem

I get a lot of mail from software authors, asking me to look at their program on the enclosed disk and suggest how they could market it. The programs are often astonishingly good, but with zero resources to get them to market, the authors are right in assuming they don't have much of a chance. I have to grit my teeth and tell them so, most of the time. Then I suggest they release the product as shareware.

I sometimes hear back from them, telling me bitterly that shareware simply doesn't work--they've tried it. The only response is that shareware does work--statistically--but that not everybody wins, and the factors that dictate who wins and who doesn't are most obscure. Worse, many of the most significant factors, like whether or not John Dvorak plugs a shareware product in one of his columns, are utterly up to fate and dumb luck.

(I've never released anything as shareware per se. I have released a couple of things as "swapware;" that is, if you like my software, don't send me money--send me ten bucks' worth of something I can use. Usually I specify nuts, bolts, tools, or electronic parts, and sunuvugun, every so often someone mails me five pounds of resistors or a broken cordless phone. The swapware concept works well for resistors, which I use a lot of. Would it work for food? That is, if you like my software, please send me a case of Honey Nut Cheerios, or ten cans of chunk light tuna. Electronic barter. Might be worth a try!)

What shareware addresses is the difficulty of trying out software before you buy it. Basically you take it, try it, and if you like it you pay for it. The weakness here is that the distribution system is utterly automatic and accidental: People passing your software around, uploading it to BBSs, giving it to their friends, and so on. Worse, the number of people who use shareware without paying for it is very high by most estimates.

What SofSource is doing is putting the price point of software so far below the threshold of pain that if you don't like it, hey, what the hell. You're only out six bucks. And everybody who tries it pays. The distribution system is deliberate and methodical, not flukily automatic, and it taps into that very human ability to go into a spending frenzy when surrounded by too many goods piled too high in the air.

I honestly don't know how well SofSource works yet. But by the time you read this, my mortgage-calculator application (a seriously mutated and fleshed-out descendent of HCALC) will be heading into the SofSource distribution channel. I'll let you know what happens. In the meantime, if you'd like to hear more about SofSource, contact Bob Falk in El Paso, who does most of their acquisitions. (See the product box included in this column.)

Turbo Vision Resources

In the process of turning HCALC.PAS into a commercial application, I learned a lot more about Turbo Vision than I had intended to. Some was good, some was ... well, marginal, but I have to keep weighing my difficulties against the challenge of duplicating what TV does on my own.

One of the truly good things about Turbo Vision that I hadn't paid much attention to before is the notion of resources. A resource sounds mysterious (and the TV Guide gives the idea five pages, period), but it's far less mysterious and potentially far more useful than the Borland documentation lets on.

Some people have characterized resources as random-access streams, but that's only about a third of the truth. A resource is a random-access stream keyed by a text string. You can think of a resource as a black box containing named objects, and when you pass the resource a string "P_F_SLOAN" you will get back an object that had earlier been stored under the name "P_F_SLOAN," or else you will be told that no such object exists in the resource. You don't have to fuss with the search or worry about the internal representation of any of the data in question. You simply have to call the resource's Get method, and the search is done for you.

Beneath the surface, a resource is something like an ISAM manager for an ordinary Turbo Vision stream. The string keys are stored in a special string-collection class used only to index streams. The resource stores its objects on a stream, and therefore it is polymorphic: The stream can contain any object type ultimately descended from Borland's standard TObject type. All the objects stored in a resource do not have to be of the same type, nor do you necessarily need to know the type of the object when you request it--or get it back. You only need to know its name.

One nonobvious requirement is that the application that reads an object in from a resource must contain the code comprising any object stored in a resource. The code proper is not stored out to the resource file; only the object's state--the contents of its fields--is stored on disk. The code containing an object's methods must have been linked into the application when the program was compiled, if the application is later to read an object from any stream or resource. I've made this point before, but people still seem to be confused about it. Registration of types with streams and resources is how a stream or a resource connects the state of an object read in from disk with the object's code that already exists in memory.

Gee, Am I Doing OODBM?

As I'll begin explaining shortly, resources are mostly used to contain program user-interface elements like menu bars, dialog boxes, and string lists. However, there's nothing to limit you to programming UI elements. There's no reason you can't build a simple database application around resources, using some selected unique string field in an object as the index name.

The advantage there would be that you could sculpt a database "record"--an object class, actually--to precisely fit the needs of the data it contains, rather than having to massage the data to fit some sort of all-things-to-all-data record format.

For example: Suppose you wanted to create a database to log your collection of books, records, and videotapes. The three categories are similar enough to allow a single database to make sense, but some significant differences make representation of all three categories in a single record format close to impossible. Books have an ISBN number and a single byline. A record is almost always an anthology of works with two different types of "author," the creator and the performer, that may be different for each work on the record. Records have no standard registration number like the ISBN. Videotapes have a running time figure that books do not. CDs have the "AAD" designator that should be stored somewhere. You get my drift. You can define three classes to model books, records, and videotapes separately, and then store out objects of those classes at random to the same resource file. The only element that they must have in common is a text string to act as the access key.

The limitation to this concept is that all key strings must be present in memory at all times that a resource file is to be accessed. The key strings are stored in a Turbo Vision collection, and the collection has no "virtual" capability; that is, it can't keep parts of itself on disk and only load a portion of itself into memory at one time. The longer your key strings are, the more memory the resource as a whole will occupy, and pretty quickly you're going to have a monster object eating up a major slice of your heap.

This, I suspect, is why Borland hasn't really mentioned the use of resources as database objects, even though that's pretty much what they are. A sharp person could certainly derive a virtual resource object in which the string collection portion of the resource was kept on disk and intelligently buffered to memory. If any of you have done this, or have seen a commercial or shareware library that does this, I'd like to hear about it.

Nor is a resource really object-oriented database management. At most I would call resources polymorphic ISAM. The research people haven't yet come to any crisp consensus as to what OODBM really is, and until they do, I suspect I'm going to keep my databases relational.

A Bin of Interchangeable Parts

Borland's intended use of resources is to add flexibility to applications by allowing them to load user-interface objects from disk files. A menu bar is an object, and can be stored in a resource under a name like BEGINNER. The same file can contain two similar menu bar objects named INTERMED and ADVANCED. You can at any time during an application's execution dispose of the current menu bar, load any of the three menu bars from the resource file, and then insert the new menu bar into the desktop. With almost no hassle at all, you're able to present three different levels of menus on command: one for beginners, one for intermediate users, and one for wizards. (I've seen the FastBack backup utility do this sort of thing most effectively, though not using Turbo Vision.)

A resource thus becomes a bin of interchangeable parts for an application. Menu bars graded by expertise, dialog boxes with "extra" controls for expert users or sysops, string lists for different human languages, all selectable anytime at run time--it's a heady concept that I've only begun to explore.

By storing UI components in a resource and loading them as needed, you can also exile the code that actually configures the resource off into a separate module or utility. If you still have my HCALC.PAS source code somewhere, take a look in the TMortgageApp.Init constructor. The bulk of the code in that constructor builds two dialog boxes, which are then tethered to pointers and held for further use. If those two dialog boxes had been stored out to a resource, the code to create them would be unnecessary. All you'd need is a couple of lines to open a resource and then load the dialog boxes from the resource. Add to that the ability to exile all those convoluted Lisp-like constructor calls that build menu bars, and you can get a lot of unnecessary and mysterious-looking stuff out of your application entirely.

There is a catch--and the catch, of course, is that you have to build the resources somewhere. You can lift the code that builds a resource out into an application-specific custom resource-creator program, or you can use a commercial resource editor. There are a number of these in the Windows programming marketplace, but so far only one for Turbo Vision: Blaise Computing's Turbo Vision Development Toolkit (TVDT).

Drag-and-Drop Resources

I commented on Blaise's TVDT shortly after it appeared as "nice to have." I'll change the perspective slightly by saying that if you intend to use resources with your TV apps, it becomes absolutely essential. In one evening's work, I replaced all of my programmatically generated UI objects with resources created in the TVDT, and exiled more than 370 lines of source code from my mortgage-calculator application.

TVDT lets you define a UI element visually by pulling boxes and controls around on the screen, and then saves the resource to a resource file when you've decided it's the way you want it. A dialog box, for example, begins as a plain rectangle. You can tug on the corner with the mouse cursor to change its size and proportions. You can drop controls like buttons, static text, and input lines onto the dialog box, then drag them around and change their labels until they meet your needs.

You define a menu bar by filling out a little form something like a spreadsheet, with the menu-item text, shortcut labels, shortcut-key codes, and the numeric value of the command to be generated by that menu item. Once you've filled out the form, you can "run" the menu to see how it will actually look and operate on your application's screen.

Using Blaise Resources

The TVDT is more than just a resource generator. Blaise includes a few Pascal units (with full source code, bravo!) that add a number of features to your applications when added to your USES statement. One is BApp, a unit that must be placed in the USES statement after Borland's App unit, and replaces some (but not all) of the code in App. App has to be there, and be there first! Among other things. BApp saves the underlying DOS text screen and screen mode when you execute your TV app, and before returning to DOS, it courteously restores what was there at invocation time.

But mostly, what the Blaise units do is handle TV resources efficiently and quickly. It's not difficult. Here are some pointers on doing it right:

Example 1: Using Blaise's Turbo Vision Development Toolkit: (a) Main program block; (b) working with a resource; (c) executing the dialog box; (d) calling a destructor.

  (a)

  BEGIN
    RegisterAllTypes;
    ResFile. Init(New (PResStream,
      Init ('MORTGAGE.BRS',
            stOpenRead, 1024)));
    MortCalc. Init;
    MortCalc. Run;
    MortCalc. Done;
    ResFile. Done;
  END.

  (b)

  ExtraRangeDialog :=
    PDialog
      (bAppResFile.Get ('EXTRA_RANGE'));

  (c)

  Control :=
    DeskTop^.ExecView(ExtraRangeDialog);

  (d)

  Dispose (ExtraRangeDialog,Done):

That's nearly all there is to it. The major difference in usage between dialog boxes constructed programmatically and dialog boxes read from a resource file is that when you can read them from the resource file at any time, you don't need to keep them hanging around on the heap, taking up memory you could use for other things. Pull 'em in, give 'em their default values (if any), execute 'em, pull out the user's responses, and then dispose of them.

The same general mechanism is used for menu bars, string lists, or other UI objects.

The Command Constant Problem

The TVDT resource editor has no way of examining your source code or compiled units. This leads to two logistical problems, one of which Blaise solved cleverly and another that they didn't solve at all.

Bad news first. When you define a menu bar using the resource editor, you must put the numeric literal value of all the commands you want the various menu items to issue when selected. In nearly every case, you'll define a command as a simple numeric constant somewhere in your application, for example, cmCloseAll=197;.

This definition isn't available to the resource editor. You must put the literal 197 on the line defining the menu item that issues the command when selected.

The problem here is that you're defining this command in two places: in your source and in the resource editor. If you change one and not the other, your menu may think it is issuing a CloseAll command and then issue a PrintWindowSummary command instead. Pray that this little item doesn't drive you nuts, like it drove me!

There's no easy answer to this problem, short of forcing the resource editor to parse Turbo Pascal source code. That's a tall order, and I don't expect to see it. I will, however, rejoice if I do.

Shoehorning

The other problem is that the resource editor can only assume the presence of the standard Borland controls like TButton, TInputLine, TCheckBoxes, and so on that everyone who owns Turbo Pascal 6.0 already has. It can't read source code or compiled units, so it can't take into account custom controls that you buy from others or write for yourself.

A good example is Allen Bauer's FInput formatted line-input control that I used in HCALC.PAS. I wanted to use the TFinputLine class from within the resource editor, but the editor had no way to know that TFInputLine existed.

What to do?

Blaise solved this one cleverly, with a technique they call "shoehorning." The idea is to "save room" in a dialog box for a custom control by laying out a standard Borland control in the same spot to hold its place. After you've loaded the dialog box into your application, you instantiate your desired custom control. You then call a special Blaise-supplied function that substitutes a pointer to the custom control for the dialog box's original pointer to the standard Borland control. This function, bShoeHorn, does the actual shoehorning. A method from my mortgage-calculator program demonstrates this process, and is given in Listing One.

The Blaise documentation is a little sparse, and it doesn't detail what assumptions are made about the custom control and what it can and should not do. I didn't push my luck , and I think a rule of thumb might be that you should only shoehorn a descendent of a standard control into the standard control's place in a dialog box. In other words, don't try to shoehorn a fancy input line into the space held by a TButton. I've gotten into enough trouble wildly polymorphing to suspect it's not quite as exact a science as we've been led to believe.

No Shortage of Vision

There's a number of other useful features in TVDT, including a special "beta test" version of BApp that builds some debugging features into your app that can be stripped out later on, simply by removing the BetabApp unit name from your USES statement. Elegant! All in all, it's a terrific package and a must-have if you're doing any serious development in Turbo Vision.

You probably know by now, but Borland has just announced Borland Pascal 7.0, replacing the second-longest-lived version of the compiler. (As best I can tell, Turbo Pascal 3.0 lived the longest.) I've only begun to look it over, and I'll have more to say in an upcoming column. But it's fair to say you won't be disappointed.

I've been sticking with Turbo Vision, in part because it's a complicated subject that nobody else seems to be talking about at all, and in part because once I set it aside, I don't expect to go back to it for awhile. There's a new Paradox Engine that I'm itching to experiment with and tell you about, and then, with some trepidation, I think I may move on to Windows programming for another long spell.

It used to be I could discuss six topics in one column. Now it takes six columns to do one topic. If programming didn't accomplish so much, I'd be complaining a lot more about how complicated it's become.

Products Mentioned

SoftSource 6285 Escondido Drive El Paso, TX 79912 915-584-7670

Turbo Vision Development Toolkit 2.0 Blaise Computing Inc. 819 Bancroft Way Berkeley, CA 94710 510-540-5441 $169.00



_STRUCTURED PROGRAMMING_
by Jeff Duntemann


[LISTING ONE]


PROCEDURE TMortgageView.ExtraPrincipalRange;

VAR
  ExtraRangeDialog   : PDialog;
  ExtraPrincipalData : ExtraPrincipalRangeDialogData;
  FromPaymentLine,
  ToPaymentLine,
  DollarsInputLine   : PFinputLine;
  R       : TRect;
  Control : Word;
  View    : PView;

BEGIN
  { Instantiate the resource-based EXTRA_RANGE dialog box from MORTGAGE.BRS:}
  ExtraRangeDialog := PDialog(bAppResFile.Get('EXTRA_RANGE'));

  { Create and shoehorn the three FInputLine controls: }
  R.Assign(0,0,0,0);
  DollarsInputLine := New(PFinputLine,Init(R,8,DRealSet,DReal,2));
  View := bShoeHorn(ExtraRangeDialog,DollarsInputLine);

  ToPaymentLine    := New(PFInputLine,Init(R,3,DUnSignedSet,DInteger,0));
  View := bShoeHorn(ExtraRangeDialog,ToPaymentLine);

  FromPaymentLine  := New(PFInputLine,Init(R,3,DUnSignedSet,DInteger,0));
  View := bShoeHorn(ExtraRangeDialog,FromPaymentLine);

  { Set the default values for the dialog through SetData: }
  ExtraPrincipalData.FromPaymentNumber := 0;
  ExtraPrincipalData.ToPaymentNumber   := 0;
  ExtraPrincipalData.ExtraDollars      := 0.00;
  ExtraRangeDialog^.SetData(ExtraPrincipalData);
  Control := Desktop^.ExecView(ExtraRangeDialog);

  IF Control <> cmCancel THEN  { Update the active mortgage window: }
    BEGIN
      { Get data from the extra principal dialog: }
      ExtraRangeDialog^.GetData(ExtraPrincipalData);
      WorkingBox^.Show;
      WITH ExtraPrincipalData DO
        Mortgage.RangeExtraPrincipal(FromPaymentNumber,
                                     ToPaymentNumber,
                                     ExtraDollars);
      WorkingBox^.Hide;
      Redraw;            { Redraw the mortgage window     }
    END;
  Dispose(ExtraRangeDialog,Done);
END;












Copyright © 1992, Dr. Dobb's Journal

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