home *** CD-ROM | disk | FTP | other *** search
/ Liren Large Software Subsidy 5 / 05.iso / a / a012 / 1.ddi / README.EXE / TBF1.TXT < prev    next >
Encoding:
Text File  |  1991-05-01  |  11.7 KB  |  275 lines

  1.                         TBrowse FOCUS: A Few Cosmetics
  2.                                By Kathy Uchman
  3.  
  4.  
  5. Editor's Note: if you use the Aquarium Message Center regularly, then
  6. surely Kathy Uchman needs no introduction. I am proud to add her to our
  7. staff of Aquarium authors.  She kicks off our new series, TBrowse
  8. Focus, which will plumb the depths of this fascinating and fantastic
  9. new 5.0 feature.
  10.  
  11.  
  12. Introduction
  13.  
  14. There is no doubt that TBrowse is the greatest thing since sliced
  15. bread, and that we'll be spending years optimizing its capabilities.
  16. It didn't take me long to realize that passing just a few parameters
  17. to my Browser() function could really make the user interface snazzy.
  18.  
  19.  
  20. Some of the Data, All of the Time
  21.  
  22. One of the truly great features of TBrowse is the ability to browse an
  23. entire database record, no matter how wide, and let the user get to
  24. all the fields by panning the browse right and left.  The ability to
  25. freeze the leftmost columns enables us to further ensure that the user
  26. is always able to see critical fields while popping around the screen.
  27.  
  28. Still, I find that there are times when I want to make sure that
  29. certain fields are always visible, or prefer to show the entire record
  30. onscreen.  I found that by passing a code block to my Browser() and
  31. reserving space on screen for displaying its contents, it was easy to
  32. obtain the desired results.
  33.  
  34.  
  35. Visual Aids Would Help
  36.  
  37. The accompanying source code creates two databases, VENDORS.DBF and
  38. INVOICES.DBF, and initially calls the Browser() to display
  39. vendors.dbf.  The browse window includes the ID Key, Vendor Name, and
  40. Address, and (available by panning right) the City, State, and Zip
  41. Code.  However, I wanted to let the user see the year-to-date figures
  42. for purchases and payments made to this vendor without having to pan
  43. the screen.  The box below the browse window updates the dollar
  44. figures for these fields as the user moves within the browse window.
  45.  
  46. ╔════════════════════════════════════════════════════════════════════╗
  47. ║        ID Key   Vendor Name            Address                     ║
  48. ║═══════════════╤══════════════════════╤═════════════════════════════║
  49. ║        000010 │ Ajax Supplies        │ P.O. Box 235                ║
  50. ║        000015 │ Wally's Pizza        │ 1300 Clay Rd.               ║
  51. ║        100123 │ Aardvaark City       │ Bancroft & Main             ║
  52. ╚════════════════════════════════════════════════════════════════════╝
  53.                                                                            
  54. ╔════════════════════════════════════════════════════════════════════╗
  55. ║YTD Purchases             YTD Payments   (F10=View Invoices)        ║
  56. ║$   1,571.22              $  1,255.98                               ║
  57. ╚════════════════════════════════════════════════════════════════════╝
  58.  
  59.  
  60. Code Blocks Make It So Simple
  61.  
  62. All it takes to make the browser display this box is the definition of
  63. a code block identifying the fields and accompanying picture
  64. statements, plus a simple character string variable containing the
  65. "header" text for each field in the box.  I called the code block
  66. viewbox (as in "view contents of box" -- catchy, eh?) and the header
  67. variable viewhead.  Here is how they were defined for the boxes
  68. shown above:
  69.  
  70. local viewhead := "YTD Purchases" + space(13) + "YTD Payments" + ;
  71.                   space(3) + "(F10=View Invoices)"
  72. local viewbox  := { | | "$ " + trans(vendors->ytdpur,"###,###.##") + ;
  73.                   space(14)+"$"+trans(vendors->ytdpay,"###,###.##") }
  74.  
  75. These variables are then passed to the Browser() function, which acts
  76. upon them like this:
  77.  
  78. -  Prior to entering the "do while .t." loop of the browser, the
  79. function determines whether a viewhead has been passed.  If so, it
  80. draws a box starting two rows below the bottom of the browse window
  81. and ending three rows below that, defaulting the left and right sides
  82. to the browse:nleft and browse:nright.  Then it displays the contents
  83. of viewhead~n on the first row within this sub-browse box.
  84.  
  85. -  Within the "do while .t." loop, following the "do while !
  86. brow:stabilize()" loop, but before the "if brow:stable" statement, the
  87. function determines the existence of a viewbox code block.  If one
  88. has been defined, it is displayed via a call to eval():
  89.  
  90. if viewbox != NIL
  91.    @ b+4, l+1 say eval(viewbox)
  92. endif
  93.  
  94.  
  95. Other Uses For the Viewbox
  96.  
  97. Because the viewbox contains the eval() of a code block, the sky is
  98. the limit for what you can put into that box.  With relations set to
  99. other databases, you can display critical information from other
  100. files, results of numeric calculations on multiple fields, or a choice
  101. of character expressions resulting from the evaluation of a series of
  102. logical comparisons.  For instance:
  103.  
  104. iif(invoices->paidamt == invoices->puramt,    ;
  105. "Paid in Full", iif(invoices->paidamt == 0.00,;
  106. "Payment Pending", "Partly Paid") )
  107.  
  108. would provide a very chatty evaluation of an invoice's status!
  109.  
  110.  
  111. With a Little More Design Effort, You Can Get REALLY Fancy!
  112.  
  113. It doesn't take much imagination to figure out that in place of the
  114. viewbox code block parameter, you could be passing a multi-dimensional
  115. array containing several rows of information to appear in the box.
  116. For instance, you might want to browse the invoices.dbf in the lower
  117. half of the screen, but with a relation set to vendors.dbf display the
  118. entire contents of the corresponding vendor data in a big, multi-line
  119. box in the upper half of the screen.  Why stop there?  Pass another
  120. parameter to the Browser() to let it know whether you want the view
  121. box to appear above, below, to the left, or to the right of the browse
  122. window.  Or maybe even little pieces of it appearing all around!
  123.  
  124. Well, you get the idea.
  125.  
  126.  
  127. Running On Empty
  128.  
  129. One thing that bugged me about my first browsing routines was the way
  130. they dealt with a subset of data when no data meeting the subset
  131. criteria yet existed.  That is, if I were browsing my vendors
  132. database, and a message appeared on the screen exhorting me to "Press
  133. F10 to view invoices for highlighted vendor", the effect with a newly-
  134. added vendor would be a small browse window overlapping the vendor
  135. browse window with a highlight block sitting on a blank line.
  136.  
  137. Similarly, if I were cleaning out a browse window full of records
  138. pointing to old disk-based reports and deleted the last entry, the
  139. browser would just sit there displaying nothing.
  140.  
  141. Obviously, in the first situation we want to roll into Insert Data
  142. mode, and in the second we want to bail out of the browser when there
  143. is nothing left to browse.
  144.  
  145. (Computers are so literal; you have to tell them absolutely
  146. everything!)
  147.  
  148.  
  149. Testing for nokey
  150.  
  151. I added a local variable, nokey to my Browser() to deal with these
  152. situations.  Nokey is a logical variable, the value of which is
  153. dependent upon the definition of a parameter to Browser(), called
  154. keydata.
  155.  
  156.  
  157. Segue:  Explaining Browser()'s Parameter List
  158.  
  159. At the risk of boring you with what may be obvious, this seems like a
  160. good time to discuss the parameters I'm passing to my Browser().  In
  161. order, they are:
  162.  
  163. t,l,b,r: No surprises -- the top, left, bottom, and right coordinates
  164. for the browse window box; the brow:ntop ... brow:nright will be
  165. calculated to fall one position within these coordinates.
  166.  
  167. fields_: An array of fields to appear in the browse window. Note,
  168. however, that you may pass an alias-> identifier with your field
  169. definition, or even some value which is not itself a field, but the
  170. result of a calculation or other evaluation. This is possible due to
  171. the dandy trick I learned from Mr. Grump in a discussion on the
  172. Aquarium Message Center, wherein he explained how to design a column
  173. block code block which compiles at run-time with the "&" operator.
  174.  
  175. heads_: A corresponding array of column headers for the elements in
  176. the fields_ array.
  177.  
  178. keyfunc: A character string containing the name of a hotkey intercept
  179. function.  I like to keep my browser as generic as possible, so the
  180. case statement covers just the normal cursor-movement stuff.  The
  181. otherwise portion of the case statement looks to see if a keyfunc has
  182. been defined; if so, Browser() "blockifies" the keyfunc variable,
  183. adding the last key pressed to the function as a parameter, and
  184. eval()'s the beast ... et voilà!  A generic function caller! The
  185. keyfunc determines what, if anything is to be done with a keypress
  186. other than cursor movement. (Look at functions VendAction(key) and
  187. InvAction(key) in BrowDemo.Prg for examples.)
  188.  
  189. viewhead, viewbox: Already discussed, above.
  190.  
  191. keyfld, keydata: When I want to view a subset of data, I need to know
  192. two things:  What field (keyfld) in the browsing data is supposed to
  193. match the contents of variable keydata?  For instance, if I want to
  194. view all the invoices for a particular vendor, I need to ensure that
  195. the invoices.dbf index order is set so that "VENDID" is the first
  196. keyfld in the index key.  Then, the keydata contains the value in
  197. vendors->vendid for which I'm trying to make a match.
  198.  
  199.  
  200. Back to the nokey Discussion:
  201.  
  202. Passing parameters keyfld and keydata is an optional matter; if they
  203. aren't passed to Browser(), then we're going to view the entire
  204. contents of the database.  But whether I'm looking at the full
  205. database or a subset, I still want to avoid looking at an empty browse
  206. window, so the value of nokey is determined like this:
  207.  
  208. if keydata != NIL
  209.    seek keydata
  210.    nokey := ! found()
  211. else
  212.    go top
  213.    nokey := ( eof() )
  214. endif
  215.  
  216. Once inside the browse "do while .t." loop, and having stabilized the 
  217. window, rather than just wait for the user to press a key I have the 
  218. Browser() act upon the condition nokey == .t.:
  219.  
  220. if nokey
  221.     key := K_INS
  222. else
  223.     key = inkey(0)
  224. endif
  225.  
  226. Since K_INS is not one of the keys being trapped by Browser(), the
  227. function will call the keyfunc with K_INS.  It's up to the
  228. programmer to provide the appropriate handling of the K_INS keypress
  229. within the hotkey intercept function -- display a message, provide
  230. GETs for adding a record, or even do nothing (although that's pretty
  231. unfriendly for the hapless user).  The keyfunc must return a
  232. logical value; if .t., the browser will invoke brow:refreshAll() to
  233. update the browse window with whatever action took place.
  234.  
  235. In the event the user was given the opportunity to add a record to the
  236. data subset, but elected to <Esc> out of it, I wanted to avoid ending
  237. up with that blasted empty browse window.  So after every keypress,
  238. Browser() tests for the value of nokey.  If nokey was .t. at
  239. the top of the "do case...endcase" statement, Browser() will repeat
  240. the test to determine whether there is still no data meeting the
  241. specified criteria.  And if nokey is still .t., Browser() will
  242. bail out and return to its calling function.
  243.  
  244. I had to add one more little test to this post-"do case" test for 
  245. nokey:  The value may have been .f. when Browser() started out,
  246. but if the user has deleted all of the entries, he has created a 
  247. nokey situation.  So the code looks like this:
  248.  
  249. if nokey .or. key == K_DEL
  250.    if keydata != NIL
  251.       seek keydata
  252.       nokey := !found()
  253.    else
  254.       go top
  255.       nokey := eof()
  256.    endif
  257.    if nokey
  258.       exit
  259.    endif
  260. endif
  261.  
  262.  
  263. All of This Looks Nicer Than It Sounds
  264.  
  265. The full effect of these ideas comes across best if you see the
  266. program in action.  If you take a moment to print out the accompanying
  267. source code to a text file (be sure to strip out control codes),
  268. compile, link, and run it, you'll be able to play with adding and
  269. deleting records to the invoices.dbf for any of three vendors (hint:
  270. only Wally's Pizza has any invoices when the program starts, but you
  271. can change that by pointing to any of the vendors and pressing F10).
  272. As you go in and out of the sub-browse of invoices for vendors, keep
  273. an eye on the vendors browse viewbox and see how the values are
  274. updated.
  275.