TBINFO.TXT by Hannes Ziegler CIS 100142,302 ********* Contains: A description of the internal TBrowse-structure as far as I know it. (I don't work with CA. I hacked it ). ************* Introduction: TBrowse is the most flexible object in Clipper and is widely used. However, it's design does not fit into my OOP-understanding: too many things are unflexible/lacking and too many things are hidden to the programmer. With Class(y) we can inherit from TBrowse and we can add missing features, but Class(y) still does not give full access to TBrowse. I'm not speaking of TBrowse methods, this is code which can be overridden using Class(y). This file describes instance variables (iVars), that are not accessible in normal situations. **************** Basic structure: A programmer who is using Class(y) knows that iVars are being stored in an array. The same is true for TBrowse or any Clipper-object. The basic structure of Tbrowse's iVar array can be examined in CLD using array := acopy( oTBrowse , array( len(oTBrowse) ) ) Here len(oTBrowse) is essential because the structure of TBrowse changed from Clipper 5.01 to 5.2 . Since iVars are being stored in an array it seems to be logical, that the array-element-operator [] can be applied to an object. As a matter of fact it is possible and yields following results if applied to TBrowse: Element is iVar in version 5.01 oTBrowse[1] -> Cargo oTBrowse[2] -> { nTop, nLeft, nBottom, nRight } oTBrowse[3] -> { TBColumn-objects } oTBrowse[4] -> ColorSpec oTBrowse[5] -> { HeadSep, ColSep, FootSep } oTBrowse[6] -> SkipBlock oTBrowse[7] -> GoTopBlock oTBrowse[8] -> GoBottomBlock oTBrowse[9] -> Internals (virtual screen, RowPos, ColPos aso. ) Element is iVar in version 5.2 oTBrowse[ 1] -> Cargo oTBrowse[ 2] -> nTop oTBrowse[ 3] -> nLeft oTBrowse[ 4] -> nBottom oTBrowse[ 5] -> nRight oTBrowse[ 6] -> { TBColumn-objects } oTBrowse[ 7] -> HeadSep oTBrowse[ 8] -> ColSep oTBrowse[ 9] -> FootSep oTBrowse[10] -> ColorSpec oTBrowse[11] -> SkipBlock oTBrowse[12] -> GoTopBlock oTBrowse[13] -> GoBottomBlock oTBrowse[14] -> Internals The changes from 5.01 to 5.2 are the two subarrays for coordinates and separator-chars: they don't exist anymore, and TBColumn-objects have moved in the array structure. The real interesting (hidden) part of TBrowse is stored in the last element of the iVar array, and this can be accessed using atail(oTBrowse) regardless of the Clipper version a program has been compiled with. So this is what I am looking into: the last element of a TBrowse! **************** Atail(oTBrowse): The most interesting part of atail(oTbrowse) is: it contains a string that can be assigned to a LOCAL or STATIC or whatever variable. But you don't get a string-copy (as usual with strings) you get an assignment by reference. Whatever TBrowse is changing in atail(oTbrowse) will be reflected in the variable that holds the reference to atail(oTBrowse). Keep in mind, if you have a xyz:=atail(oTBrowse), then xyz is of valtype()=="C" but it is being changed by oTBrowse:stabilize(), which can occur elsewhere in a program. xyz contains a reference to a string (or array-element)! Now, what's inside atail(oTbrowse)? Basically the last element of TBrowse has five major parts: A. 84 bytes containing exported iVars and internal information B. x bytes virtual screen C. y bytes one empty data row (ghost record) D. 12*ColPos Bytes column descriptions E. ColCount*RowCount*2 bytes color information for ColorRect() Part A has the real interesting information, it's 42 pairs of bytes that can be decoded with bin2i(). I don't know them all, and most of them don't enhance the knowledge about TBrowse. But with some of these bytes nice things are possible. I just give the offset for bin2i( substr( atail(oTBrowse), *nOffSet* ) ) because this is required to read the info anyway. What I don't know is missing, what I'm not sure about has a question mark: 1 - len( atail( oTBrowse ) ) 5 - first column ? 7 - last column ? 9 - first unfrozen column 11 - last column ? 13 - len( virtual screen string ) 21 - rightmost screen column for display (oTBrowse:nRight) 23 - len( one line of data from 1 to ColCount ) [referred to as BlockSize] 25 - len( one line of data for frozen columns only ) 27 - TB : RowCount 29 - BlockNo for string that is HeadSep 31 - BlockNo for string that is FootSep 33 - BlockNo first data row in virtual screen 35 - BlockNo last data row in virtual screen 37 - BlockNo last row with data in virtual screen [eg: if RowCount() > lastrec()] 39 - TB : ColPos 41 - TB : RowPos 43 - BlockNo current data row 45 - BlockNo first footing row 47 - pending skip 51 - no of spaces left of first unfrozen column 53 - offset in virtual screen for data row of visible unfrozen columns 55 - len( data row of visible unfrozen columns ) 57 - TB : Freeze 59 - TB : leftVisible 61 - TB : rightVisible 63 - Contains chr(2) if configure() is needed. example: TB:DelColumn(n) does not *!* rebuildt the virtual screen instead byte 63 is set to chr(2). 65 - TB : HitTop chr(1) => .T. 67 - TB : HitBottom chr(1) => .T. 69 - TB : Stable chr(1) => .T. 71 - TB : AutoLite chr(1) => .T. 73 - TB : HiLite chr(1) => .T. 75 - BlockNo to start from for RefreshAll() 85 - from here on you find Part B You see that many so called 'exported' iVars of TBrowse reside in atail(oTbrowse) which are being accessed via a method (We get fooled from the compiler. This is what I don't like in the CA TBrowse-design. Ok, an object hides it's information, but why the heck is HitBottom named as an exported iVar in NG files? It's a method! Compare it with nTop: it has hungarian notation and is stored in the iVar array). What can be done with the first 84 bytes? Well, I have not yet discovered it all but for sure there are two real interesting bytes: No 47-48. TBrowse knows how many skips are pending, how many records have not been displayed at screen yet. Some other valuable information can be retrieved, like: How many rows are being used for Heading/Footing? What are the exact screen coordinates for the data area only? (see TBINTE.ZIP for code) How many blanks are being padded left to the first unfrozen column? Do all columns fit into the browse area? There are lots of clumsy code examples flying around just to answer such questions. It's easy if atail(oTBrowse) is being queried. Next is part B which does the real Tbrowse-overhead to a program. Part B == virtual screen Beginning at byte 85 you find the virtual screen that is being maintained by TBrowse. This screen is being buildt during stabilize() and it's a string that contains all data of all columns and rows. The size of the string depends on ColCount and RowCount. The larger they are, the longer stabilize() will take. Right after an AddColumn() the virtual screen contains Heading / Footing / HeadSep / FootSep of all columns (including the new one), plus one empty data row at the end. During a stabilize() only the data rows in the virtual screen are being updated, whereas after configure() or invalidate() Heading and Footing areas are being refreshed also. The virtual screen is being buildt row by row and for this, bytes No 23-24 in Part A are essential: they contain the length of one row of data in the virtual screen (it's one Block of data => BlockSize). All other bytes that point into the virtual screen must be multiplied with i2bin( byte 23-24 ). With this, the access to the virtual screen is a snap. (I do it to draw ColSep in the Heading/Footing area of TBrowse, see TBINTE.ZIP). The length of the virtual screen is bin2i(substr(atail(oTBrowse),13))). This contains a ghost record which is Part C of atail(oTBrowse). It is being used to fill the browse area at screen if eof() has been encountered. After the virtual screen is one single byte (I don't know what it's good for) but it is important to know the additional offset in order to access Part D. **************************** Part D == Column information This part is a string after the virtual screen and it contains 12 bytes for each TBColumn-object in a TBrowse. To my knowledge the meaning is as follows: 1-2 ColumnWidth 3-4 Offset in virtual screen string 5-6 ?? 7-8 len( data to display ) 9 DefColor low byte 10 DefColor high byte 11-12 ?? ****************************** Part E is used for ColorRect() It indicates in binary mode a low/hi color for each single cell in the whole virtual screen. For each cell 2 bytes are used. So if you have a TBrowse with 20 columns and 20 rows this part will take 20*20*2=800 bytes on its own, not to speak from the visrtual screen (as an example: I once saved a TBrowse to disk that displayed 21 columns of an address DBF. It was approximately 6500 Bytes for the virtual screen plus 840 bytes for the ColorRect() information. That is TBrowse's overhead) ********** Conclusion Well, that's what I know. If you have questions, feel free to ask. All is for your information only! You can access TBrowse internals with this description, but if you do it, you know it's a NO-NO in OOP-world and you do it on your own risk. I do it, because TBrowse knows more than CA is willing to let me know. I don't give a damn and I never had a problem so far. If you do it and you have a problem, don't blame me . atail(oTBrowse) is *not* supposed to be accessed! (I suppose ) Now I add some lines of code which I use: Get pending skips (great for mouse control) nPending := bin2i(substr(scTBInternal,47)) Determine number of headlines before first data row (includes Headsep) nHeadLines := bin2i(substr(atail(oTbrowse),29)) Determine number of footlines after last data row (includes Footsep) nStart :=bin2i(substr(atail(oTbrowse),37)) nFootLines :=nStart-bin2i(substr(atail(oTbrowse),31)) Many of you know the last two examples already, they are in TBINTE.ZIP : I draw ColSep in the heading/footing area by accessing the virtual screen of TBrowse. Looks nice and works fine, unless... Well, there is a real drawback in the whole stuff (and this I don't like because it's a flaw in TBrowse design). It's byte No 63 that indicates if a configure() is needed. The point is: if you issue a TBrowse:DelColumn(1) and the first column contributes let's say 25 bytes to the virtual screen I would expect, that the virtual screen would be reduced by 25*RowCount bytes. This does not happen. A deleted Column still exists in the virtual screen until you issue a oTbrowse:configure(). So all calculations based on byte 23 may be invalid if byte 63 contains chr(2). In this case a oTBrowse:configure() is necessary. That's it. If you can fill in the gaps in the description, I would be glad if you'd let me know. Hannes