home *** CD-ROM | disk | FTP | other *** search
- TBrowse FOCUS: A Few Cosmetics
- By Kathy Uchman
-
-
- Editor's Note: if you use the Aquarium Message Center regularly, then
- surely Kathy Uchman needs no introduction. I am proud to add her to our
- staff of Aquarium authors. She kicks off our new series, TBrowse
- Focus, which will plumb the depths of this fascinating and fantastic
- new 5.0 feature.
-
-
- Introduction
-
- There is no doubt that TBrowse is the greatest thing since sliced
- bread, and that we'll be spending years optimizing its capabilities.
- It didn't take me long to realize that passing just a few parameters
- to my Browser() function could really make the user interface snazzy.
-
-
- Some of the Data, All of the Time
-
- One of the truly great features of TBrowse is the ability to browse an
- entire database record, no matter how wide, and let the user get to
- all the fields by panning the browse right and left. The ability to
- freeze the leftmost columns enables us to further ensure that the user
- is always able to see critical fields while popping around the screen.
-
- Still, I find that there are times when I want to make sure that
- certain fields are always visible, or prefer to show the entire record
- onscreen. I found that by passing a code block to my Browser() and
- reserving space on screen for displaying its contents, it was easy to
- obtain the desired results.
-
-
- Visual Aids Would Help
-
- The accompanying source code creates two databases, VENDORS.DBF and
- INVOICES.DBF, and initially calls the Browser() to display
- vendors.dbf. The browse window includes the ID Key, Vendor Name, and
- Address, and (available by panning right) the City, State, and Zip
- Code. However, I wanted to let the user see the year-to-date figures
- for purchases and payments made to this vendor without having to pan
- the screen. The box below the browse window updates the dollar
- figures for these fields as the user moves within the browse window.
-
- ╔════════════════════════════════════════════════════════════════════╗
- ║ ID Key Vendor Name Address ║
- ║═══════════════╤══════════════════════╤═════════════════════════════║
- ║ 000010 │ Ajax Supplies │ P.O. Box 235 ║
- ║ 000015 │ Wally's Pizza │ 1300 Clay Rd. ║
- ║ 100123 │ Aardvaark City │ Bancroft & Main ║
- ╚════════════════════════════════════════════════════════════════════╝
-
- ╔════════════════════════════════════════════════════════════════════╗
- ║YTD Purchases YTD Payments (F10=View Invoices) ║
- ║$ 1,571.22 $ 1,255.98 ║
- ╚════════════════════════════════════════════════════════════════════╝
-
-
- Code Blocks Make It So Simple
-
- All it takes to make the browser display this box is the definition of
- a code block identifying the fields and accompanying picture
- statements, plus a simple character string variable containing the
- "header" text for each field in the box. I called the code block
- viewbox (as in "view contents of box" -- catchy, eh?) and the header
- variable viewhead. Here is how they were defined for the boxes
- shown above:
-
- local viewhead := "YTD Purchases" + space(13) + "YTD Payments" + ;
- space(3) + "(F10=View Invoices)"
- local viewbox := { | | "$ " + trans(vendors->ytdpur,"###,###.##") + ;
- space(14)+"$"+trans(vendors->ytdpay,"###,###.##") }
-
- These variables are then passed to the Browser() function, which acts
- upon them like this:
-
- - Prior to entering the "do while .t." loop of the browser, the
- function determines whether a viewhead has been passed. If so, it
- draws a box starting two rows below the bottom of the browse window
- and ending three rows below that, defaulting the left and right sides
- to the browse:nleft and browse:nright. Then it displays the contents
- of viewhead~n on the first row within this sub-browse box.
-
- - Within the "do while .t." loop, following the "do while !
- brow:stabilize()" loop, but before the "if brow:stable" statement, the
- function determines the existence of a viewbox code block. If one
- has been defined, it is displayed via a call to eval():
-
- if viewbox != NIL
- @ b+4, l+1 say eval(viewbox)
- endif
-
-
- Other Uses For the Viewbox
-
- Because the viewbox contains the eval() of a code block, the sky is
- the limit for what you can put into that box. With relations set to
- other databases, you can display critical information from other
- files, results of numeric calculations on multiple fields, or a choice
- of character expressions resulting from the evaluation of a series of
- logical comparisons. For instance:
-
- iif(invoices->paidamt == invoices->puramt, ;
- "Paid in Full", iif(invoices->paidamt == 0.00,;
- "Payment Pending", "Partly Paid") )
-
- would provide a very chatty evaluation of an invoice's status!
-
-
- With a Little More Design Effort, You Can Get REALLY Fancy!
-
- It doesn't take much imagination to figure out that in place of the
- viewbox code block parameter, you could be passing a multi-dimensional
- array containing several rows of information to appear in the box.
- For instance, you might want to browse the invoices.dbf in the lower
- half of the screen, but with a relation set to vendors.dbf display the
- entire contents of the corresponding vendor data in a big, multi-line
- box in the upper half of the screen. Why stop there? Pass another
- parameter to the Browser() to let it know whether you want the view
- box to appear above, below, to the left, or to the right of the browse
- window. Or maybe even little pieces of it appearing all around!
-
- Well, you get the idea.
-
-
- Running On Empty
-
- One thing that bugged me about my first browsing routines was the way
- they dealt with a subset of data when no data meeting the subset
- criteria yet existed. That is, if I were browsing my vendors
- database, and a message appeared on the screen exhorting me to "Press
- F10 to view invoices for highlighted vendor", the effect with a newly-
- added vendor would be a small browse window overlapping the vendor
- browse window with a highlight block sitting on a blank line.
-
- Similarly, if I were cleaning out a browse window full of records
- pointing to old disk-based reports and deleted the last entry, the
- browser would just sit there displaying nothing.
-
- Obviously, in the first situation we want to roll into Insert Data
- mode, and in the second we want to bail out of the browser when there
- is nothing left to browse.
-
- (Computers are so literal; you have to tell them absolutely
- everything!)
-
-
- Testing for nokey
-
- I added a local variable, nokey to my Browser() to deal with these
- situations. Nokey is a logical variable, the value of which is
- dependent upon the definition of a parameter to Browser(), called
- keydata.
-
-
- Segue: Explaining Browser()'s Parameter List
-
- At the risk of boring you with what may be obvious, this seems like a
- good time to discuss the parameters I'm passing to my Browser(). In
- order, they are:
-
- t,l,b,r: No surprises -- the top, left, bottom, and right coordinates
- for the browse window box; the brow:ntop ... brow:nright will be
- calculated to fall one position within these coordinates.
-
- fields_: An array of fields to appear in the browse window. Note,
- however, that you may pass an alias-> identifier with your field
- definition, or even some value which is not itself a field, but the
- result of a calculation or other evaluation. This is possible due to
- the dandy trick I learned from Mr. Grump in a discussion on the
- Aquarium Message Center, wherein he explained how to design a column
- block code block which compiles at run-time with the "&" operator.
-
- heads_: A corresponding array of column headers for the elements in
- the fields_ array.
-
- keyfunc: A character string containing the name of a hotkey intercept
- function. I like to keep my browser as generic as possible, so the
- case statement covers just the normal cursor-movement stuff. The
- otherwise portion of the case statement looks to see if a keyfunc has
- been defined; if so, Browser() "blockifies" the keyfunc variable,
- adding the last key pressed to the function as a parameter, and
- eval()'s the beast ... et voilà! A generic function caller! The
- keyfunc determines what, if anything is to be done with a keypress
- other than cursor movement. (Look at functions VendAction(key) and
- InvAction(key) in BrowDemo.Prg for examples.)
-
- viewhead, viewbox: Already discussed, above.
-
- keyfld, keydata: When I want to view a subset of data, I need to know
- two things: What field (keyfld) in the browsing data is supposed to
- match the contents of variable keydata? For instance, if I want to
- view all the invoices for a particular vendor, I need to ensure that
- the invoices.dbf index order is set so that "VENDID" is the first
- keyfld in the index key. Then, the keydata contains the value in
- vendors->vendid for which I'm trying to make a match.
-
-
- Back to the nokey Discussion:
-
- Passing parameters keyfld and keydata is an optional matter; if they
- aren't passed to Browser(), then we're going to view the entire
- contents of the database. But whether I'm looking at the full
- database or a subset, I still want to avoid looking at an empty browse
- window, so the value of nokey is determined like this:
-
- if keydata != NIL
- seek keydata
- nokey := ! found()
- else
- go top
- nokey := ( eof() )
- endif
-
- Once inside the browse "do while .t." loop, and having stabilized the
- window, rather than just wait for the user to press a key I have the
- Browser() act upon the condition nokey == .t.:
-
- if nokey
- key := K_INS
- else
- key = inkey(0)
- endif
-
- Since K_INS is not one of the keys being trapped by Browser(), the
- function will call the keyfunc with K_INS. It's up to the
- programmer to provide the appropriate handling of the K_INS keypress
- within the hotkey intercept function -- display a message, provide
- GETs for adding a record, or even do nothing (although that's pretty
- unfriendly for the hapless user). The keyfunc must return a
- logical value; if .t., the browser will invoke brow:refreshAll() to
- update the browse window with whatever action took place.
-
- In the event the user was given the opportunity to add a record to the
- data subset, but elected to <Esc> out of it, I wanted to avoid ending
- up with that blasted empty browse window. So after every keypress,
- Browser() tests for the value of nokey. If nokey was .t. at
- the top of the "do case...endcase" statement, Browser() will repeat
- the test to determine whether there is still no data meeting the
- specified criteria. And if nokey is still .t., Browser() will
- bail out and return to its calling function.
-
- I had to add one more little test to this post-"do case" test for
- nokey: The value may have been .f. when Browser() started out,
- but if the user has deleted all of the entries, he has created a
- nokey situation. So the code looks like this:
-
- if nokey .or. key == K_DEL
- if keydata != NIL
- seek keydata
- nokey := !found()
- else
- go top
- nokey := eof()
- endif
- if nokey
- exit
- endif
- endif
-
-
- All of This Looks Nicer Than It Sounds
-
- The full effect of these ideas comes across best if you see the
- program in action. If you take a moment to print out the accompanying
- source code to a text file (be sure to strip out control codes),
- compile, link, and run it, you'll be able to play with adding and
- deleting records to the invoices.dbf for any of three vendors (hint:
- only Wally's Pizza has any invoices when the program starts, but you
- can change that by pointing to any of the vendors and pressing F10).
- As you go in and out of the sub-browse of invoices for vendors, keep
- an eye on the vendors browse viewbox and see how the values are
- updated.
-