

About the TblInfoDlg Component...

The Delphi Component Writer's Guide recommends that you take 
frequently used dialogs and make them into components and 
that you create a 'wrapper' unit so that the form and it's
associated resources are only loaded into memory when they're
needed. They even provide an example with an 'About' dialog.
Fine as far as it goes, but most dialogs are a little more
complex. The TblInfoDlg component is a multi-page dialog that gives
a user lots of information about a Paradox, DBase or ODBC table.

This example (of sorts) consists of :

	The files for the wrapper unit: 
	TBLINFO.PAS
	TBLINFO.DCU
	TBLINFO.DCR

	The files for the TblInfoDlg dialog:
	TBLDLG.PAS
	TDLIDLG.DCU
	TBLDLG.DFM

	The files for the sample program:
	DBIT.DPR
	DBIT.OPT
	DBIT.RES
	DBITEST.DCU
	DBITEST.DFM
	DBITEST.PAS

To use all this, you have to install the TblInfoDlg component by
adding tblinfo.pas to the component palette, where it will show up
on the Data Controls page. Once this is done, the Dbit.dpr project 
file can be opened and run. What you'll see is a small form with four 
buttons-one pops up a File Open dialog that allows you to open a
table, and the second one pops up the TblInfoDlg dialog with information
about the aforementioned table, the third one closes the table and the
fourth one closes the sample program. We at SJHDesign have found this 
component quite handy, it's been pretty well tested and is currently 
hard at work in real world applications at two client sites, so the 
code is(to our knowledge) solid.


Using the TblInfoDlg component in your applications:
_________________________________________________

All you have to do to use this component is supply it with a
TTable component. This can be done at design time, by using the
Object Inspector to put the fully qualified filename of a DBase,
Paradox or ODBC table(which must already be opened before you
call the TblInfoDlg object) in the TABLE property field or at run 
time with code similar to this in your application:

          Table1 : TTable;
          TblInfoDlg1 : TTblInfoDlg;

          Table1.open;
	  {Note that the table MUST be open before
	   calling the TblInfoDlg object} 

	  TblInfoDlg1.table := table1;
  	  TblInfoDlg1.execute;

If the TTable object you pass to TblInfoDlg is invalid or inactive, an
error message pops up and it cleans up and terminates. Additional 
properties allow easy runtime access to the dialog's font, color and 
caption. Any change made in the font or color is also made for all of 
the dialog's subcomponents.


Why put form properties in a wrapper unit?
__________________________________________________________

Good question. Delphi's architecture makes it very easy for the
component user to change the properties of the 'wrapped' form
(in this case, Tbldlg.pas)visually in the Object Inspector. That's
great for design time, but some properties are useful to have around
at run time.  Since the TblDlg form is owned by the TblInfo unit,
the user can access it's properties at runtime, but it would take 
some classically convoluted OOP code to do it. For example, by placing 
all the code needed to change the fonts of TblDlg and it's subcomponents 
into the wrapper unit, it takes the component user one line of code to do 
what otherwise would have taken 20 or 30 lines(this is also the reasoning 
behind the Parentxxx properties in Borland's components). In general, it
helps to know how to add properties to a given component-non-visual 
components like TblInfoDlg(descended from TComponent) don't inherit 
very much in the way of properties. This listing shows all the code 
needed to let the user set TblDlg's font through the wrapper:

	interface 

	type
  	
	  private
             fFont: TFont;
             procedure DlgFont(value : TFont);

	  published
             property Font : TFont read FFont write DlgFont;
              {you don't specify a default value here-the program 
	       will use either the font defined in object inspector 
	       or the system default font}

          var
            MultPageDlg: TMultPageDlg;{the TblInfoDlg dialog form}

         implementation

         constructor TTblInfoDlg.Create(AOwner: TComponent);
         begin
           inherited Create(AOwner);
           fFont := TFont.Create; 
           {the font and all other added property fields are
                initialized to default values}


         function TTblInfoDlg.Execute : Boolean;
         begin
           MultPageDlg := TMultPageDlg.Create(Application);
           MultPageDlg.Font := fFont;
	   {this initializies the TblInfoDlg dialog and sets it's font
            to the default value}

	 procedure tTblInfoDlg.DlgFont(value : TFont);
	 begin
  	   if FFont <> Value then
  	   begin
    	     FFont.Assign(Value);
  	  {here is where the wrapper unit changes the value in the 
           font property field when the user changes the value in the
            object inspector} 

Note that when you install the component, it defaults to whatever your
system font is. Everybody's tastes are different, but I think it looks
best using MsSanSerif, 8, Bold. You can adjust this in TTblInfoDlg's
Font property at design time.

The pattern is pretty much the same for the other properties depending
on the property's data type.


Why use BDE in Delpi Applications?
___________________________________

With the exception of a few omissions , Delphi's implementation of BDE
will serve you pretty well. If however, you are developing anything out
of the ordinary, you'll eventually have to use BDE functions to fill in 
the gaps in Delphi's simplified(but very neatly encapsulated) version of 
BDE. To use BDE directly, you have to include DBIPROCS and DBITYPES in
your USES declaration.

Then there's the size issue-Delphi programs can get huge(I shouldn't
talk..using the TblInfoDlg component will add about 80k to a program, 
big even by Delphi standards). Judicious use of BDE calls instead of 
adding and creating more components can substantially cut down on the 
size, load time and memory overhead of your exe's.

Dbbrowse(in delphi\demos\db\tools) is the only Delphi demo program with
direct calls to BDE. Borland takes the approach of creating 4 components
to handle the 4 BDE calls used in this program. While this approach works 
fine, it's not mandatory (the advantages to this approach are simplicity 
and the fact that Delphi handles most exceptions). Calling BDE functions
from your program instead of componentizing them is a little harder, but
could result in significant gains in speed and smaller exe size. With good
error checking, any BDE function will work safely in a Delphi program, and a 
number of them are used in the procedures GetTableDate_Size, DisplayFields
and Display Indexes. With the exception of GetTableDate_Size, the same 
information could have been gathered from Delphi's component methods and 
data structures, but the required overhead would be much greater considering 
that this is just a dialog that displays the stuff and disappears. In the
case of GetTableDate_Size, the choice is between using a specialized BDE 
function like dbiOpenTableList(which walks the BDE handle chain to get 
physical file information that was stored at the time the file was opened)
or using the runtime library routines FileSize and FileGetDate(which close 
and reset the file and could wreak all sorts of havoc on the Delphi data 
controls that are linked to the table). The former just seems simpler and 
safer.

BDE Error Checking
___________________

Anyone who has had the experience of watching their Database program set off
a long chain of excepetions and crash has probably learned this the hard way
and can skip this part. Those who are new to BDE, though, might take some well
meaning advice. Exception handling is an area where Delphi truly shines, making
the Visual Basics and PowerBuilders of the world look feeble by comparison. When
you use a Data Component, you get the benefit of Delphi's strong exception and 
error handling, but if you use direct BDE calls, you have to provide your own.

Let's look at Dbbrowse again-the bdetable.pas unit(which contains the 3 BDE 
calls) wraps the calls in a CHECK routine which uses BDE's error stack to
return the success or failure of direct BDE calls. While this is adequate
for debugging and development, all you're doing here is getting information
on whether your call went ok or why it failed. If the call DID fail, CHECK
returns the error value and the program merrily goes on to the next call.
If for example, the failed BDE call was supposed to return a cursor handle
(hdbicur), the subsequent calls which access the data fields pointed to by 
this cursor will return a nil pointer(if you're lucky) or fail. To see this 
in action, load the Dbbrowse project(you have to install the three components
in bdetable.pas first) and use the IDE Run command to run the Dbbrowse program.
If you choose the View Table command without having the outline cursor on a
table, you'll see a whole chain of error messages and then a GP fault as the
BDE functions repeatedly try to access data in a nonexistent table.

What we're trying to say here is that there are many cases whre your function 
that uses BDE calls must clean up and bail out if the call wasn't successful.
Calls which return a handle of any type (like DbiOpenTableList, DbiOpenIndexList,
or DbiOpenFieldList) fall into this category. Calls which return pointers to
BDE data structures(like dbiGetCursorProps) can also be dangerous if execution
continues after they fail. Table and field level calls like DBIGetNext Record
or DBIGetField will simply return nil pointers or empty buffers if they fail-
this alone won't cause your program to crash, but it won't endear you to the
users of your program when they see garbage or blank fields instead of data.

This is the reason for the paranoid-type code in DisplayIndexes, DisplayFields
and GetTableDate_Size. To use BDE's error stack, you need to include DBIERRS in
your uses clause. All BDE functions return DBIERR_NONE if successful and the
three functions just mentioned show how to utilize this return value. Here's
an example:

var
  hCur : hDBICur;{BDE Cursor handle}
  name : array[0..80]of Char;{names passed to BDE functions can't be strings}
  recbuf :pbyte;{record buffer}
  tblprops :curprops;{table properties struct}

begin
  recs := table.FieldCount-1;
  strPcopy(name,Table.TableName);

  {get a cursor handle to the field list table}
  if DbiOpenFieldList(table.DBHandle, name, nil, False, hCur) = DBIERR_NONE then
  {call was successful-continue}

  begin 
    if dbiGetCursorProps(hCur,tblprops)= DBIERR_NONE then
    begin
      {call was successful-continue}

      dbiSetToBegin(hCur);
      for i := 0 to recs do
      begin
      {$I+}
      {use Delphi's exception handling routine to handle failed allocations gracefully}
      try 
        GetMem(RecBuf, tblProps.iRecBufSize*sizeof(BYTE));
      except
        on EOutOfMemory do
        {bail out and clean up}
	begin
          DbiCloseCursor(hCur);
          {show error message here if you want}
          exit;
        end;
      {$I-}
      end;
        {various record and field level calls here}     
        if recbuf <> nil then freemem(recbuf, tblprops.iRecBufSize * sizeof(BYTE));
      end;
      DbiCloseCursor(hCur);
    end
    else {call to dbiGetCursorProps failed-bail out and clean up}
    begin
      DbiCloseCursor(hCur);
      {show error message}
    end;
  end
  else ;{call to DbiOpenFieldList failed-show error message here}

Borland's advice that you get the BDE devleloper's guide if you want to do
this kind of stuff is very well taken. Contact them for info if you want this-
I've found the Borland people on their various CIS development forums to be
quite helpful.

Anyway, I've said enough-you're better off looking at the code. Feel free to 
use this component in your programs or do whatever you like with it.
Comments, improvements, opposing points of view, questions and the like are
always welcome.


Scott Hanrahan
SJHDesign, INC
CIS 70144,3033


PS : The lawyer said I should always put in a disclaimer, even for sample code
uploaded for the edification of others. This is example code for instructional
purposes only and we do not make any warranties as to it's quality or suitability 
for your purposes-that's up to you to determine. 

Enjoy!
