Teach Yourself Borland Delphi 4 in 21 Days

Previous chapterNext chapterContents


- 18 -

Building Database Applications


This chapter is about building database applications. The truth is, I cannot tell you everything there is to know about writing database applications in one short chapter. What I can do is point out some issues you are likely to encounter when writing database applications.

Because you already know how to create database forms, you will spend most of this day dealing with lower level aspects of database programming. You'll start with creating and populating a database entirely through code, and then you'll move on to data modules. Toward the end of the day you'll look at creating database reports with the QuickReport components. Finally, the day ends with a discussion on deploying database applications.

Nonvisual Database Programming

Up to this point, you have examined almost exclusively the visual aspects of database programming with Delphi. That side of database programming is fun and easy, but sooner or later you have to do some real database programming. The kinds of programs you have written so far have dealt primarily with simple viewing and editing of data. In this section, you learn how to perform certain database operations through code.


TIP: I will mention only database operations that pertain to the TTable class. You could also use SQL to perform database operations in code, but I won't present that aspect of databases today.

Reading from a Database

This section is short because reading from an existing database isn't difficult. As an example exercise, you will take the CUSTOMER.DB table and create a comma-delimited text file from the data. You won't write every field in the table to the file, but you'll write most of them.

The first step is to create a TTable object. Next, attach it to a database and to a particular table within the database. Here's how the code looks:

var
  Table : TTable;
begin
  Table := TTable.Create(Self);
  Table.DatabaseName := `DBDEMOS';
  Table.TableName := `Customer.db';
end;

This code emulates what Delphi does when you set the DatabaseName and TableName properties at design time. The next step is to read each record of the database and write it to a text file. First, I'll show you the basic structure without the actual code to write the fields to the text file. After that, I'll be more specific. The basic structure looks like this:

Table.Active := True;
while not Table.Eof do begin
  { Code here to read some fields from the } 
  { current record and write them to a text file. } 
  Table.Next;
end;
Table.Free;

This code is straightforward. First, the table is opened by setting the Active property to True (you could also have called the Open method). Next, a while loop reads each record of the table. When a record is written to the text file, the Next method is called to advance the database cursor to the next record. The while loop's condition statement checks the Eof property for the end of the table's data and stops the loop when the end of the table is reached. Finally, the TTable object is freed after the records have been written to the text file.


NOTE: You don't specifically have to delete the TTable object. When you create a TTable object in code, you usually pass the form's Self pointer as the owner of the object--for example,
Table := TTable.Create(Self);

This sets the form as the table's owner. When the form is deleted, VCL will automatically delete the TTable object. For a main form this happens when the application is closed. Although you are not required to delete the TTable object, it's always a good idea to delete the object when you are done with it.

Naturally, you need to extract the information out of each field in order to write it to the text file. To do that, you use the FieldByName method and the AsString property of TField. I addressed this briefly on Day 16, "Delphi Database Architecture," in the section "Accessing Fields." In the CUSTOMER.DB table, the first field you want is the CustNo field. Extracting the value of this field would look like this:

var
  S : string;
begin
  S := Table.FieldByName(`CustNo').AsString + `,';

Notice that a comma is appended to the end of the string obtained so that this field's data is separated from the next. You will repeat this code for any fields for which you want to obtain data. The entire sequence is shown in Listings 18.1 and 18.2. These listings contain a program called MakeText, which can be found with the book's code. This short program takes the CUSTOMER.DB table and creates a comma-delimited text file called CUSOMTER.TXT. Listing 18.1 shows the main form's unit (MakeTxtU.pas). The form contains just a button and a memo. Look over these listings, and then I'll discuss how the program works.

LISTING 18.1. MakeTxtU.pas.

unit MakeTxtU;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, ÂDialogs,
  StdCtrls, DbTables;
type
  TForm1 = class(TForm)
    CreateBtn: TButton;
    Memo: TMemo;
    procedure CreateBtnClick(Sender: TObject);
  private
    { Private declarations } 
  public
    { Public declarations } 
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM} 
procedure TForm1.CreateBtnClick(Sender: TObject);
var
  Table : TTable;
  S     : string;
begin
  { Create the Table and assign a database and Table name. } 
  Table := TTable.Create(Self);
  Table.DatabaseName := `DBDEMOS';
  Table.TableName := `Customer.db';
  { Change to the busy cursor. } 
  Screen.Cursor := crHourGlass;
  { We can use a Memo to show the progress as well as to    } 
  { save the file to disk. First clear the memo of any text.} 
  Memo.Lines.Clear;
  { Open the Table. } 
  Table.Active := True;
  CreateBtn.Enabled := False;
  { Loop through the records, writing each one to the memo. } 
  while not Table.Eof do begin
    { Get the first field and add it to the string S, } 
    { followed by the delimiter.                      } 
    S := Table.FieldByName(`CustNo').AsString + `,';
    { Repeat for all the fields we want. } 
    S := S + Table.FieldByName(`Company').AsString + `,';
    S := S + Table.FieldByName(`Addr1').AsString + `,';
    S := S + Table.FieldByName(`Addr2').AsString + `,';
    S := S + Table.FieldByName(`City').AsString + `,';
    S := S + Table.FieldByName(`State').AsString + `,';
    S := S + Table.FieldByName(`Zip').AsString + `,';
    S := S + Table.FieldByName(`Phone').AsString + `,';
    S := S + Table.FieldByName(`FAX').AsString;
    { Add the string to the Memo. } 
    Memo.Lines.Add(S);
    { Move to the next record. } 
    Table.Next;
  end;
  { Write the file to disk. } 
  Memo.Lines.SaveToFile(`customer.txt');
  { Turn the button back on and reset the cursor. } 
  CreateBtn.Enabled := True;
  Screen.Cursor := crDefault;
  Table.Free;
end;
end.
All the action takes place in the CreateBtnClick method. When you click the Create File button, the program extracts data from the database table and puts it into the Memo component. First, the value of the CustNo field is read and put into a 
string, followed by a comma. After that, each subsequent field's value is appended to the end of the string, again followed by a comma. After all the data has been extracted from a record, the string is added to the memo, using the Add method. When 
the end of the table is reached, the memo contents are saved to disk.

I used a Memo component in this example for two reasons. First, by displaying the results in a memo, you can see what the program produces. Second, the memo component's Lines property (a TStrings) provides an easy way of saving a text file to disk. Figure 18.1 shows the MakeText program after the file has been written.

FIGURE 18.1. The MakeText program running.

Creating a Database in Code

Most of what you read on database operations in Delphi tells you how to create a database with utilities such as Database Desktop. That's fine if your application has pre-built tables. But if your application has to dynamically create tables, that approach doesn't work. You must have a way of creating tables through code. For example, if your application enables users to specify the fields of a table, you won't know what the fields are until the user has supplied them.

Creating a table in code requires these steps:

1. Create a BDE alias for the database.

2. Create a TTable object.

3. Add field definitions to the FieldDefs property.

4. Add index definitions to the IndexDefs property if your table contains indexes.

5. Create the actual table with the CreateTable method.


Let's go over these steps individually so that you know what each step involves.

Creating a BDE Alias and a TTable Object

You already performed steps 1 and 2 on Day 16 when I talked about creating a BDE alias, and you created a TTable object in the preceding section. Here's a recap of those two steps:

var
  Table : TTable;
begin
  { Create the alias. } 
  CreateDirectory(`c:', nil);
  Session.AddStandardAlias(`MyDatabase', `c:', `PARADOX');
  Session.SaveConfigFile;
  { Create the table. } 
  Table := TTable.Create(Self);
  Table.DatabaseName := `MyDatabase';
  Table.TableName := `MyTable.db';
end;

This code first creates a BDE alias for a database called MyDatabase in the C: directory. After that, a TTable object is created, and the DatabaseName property is set to the alias created in the first part of the code. Finally, the TableName property is set to the name of the new table you are going to create. At this point the TTable object has been created, but the table itself doesn't exist on disk.

Creating the Field Definitions

The next step is to create the field definitions for the table's fields. As you might guess, the field definitions describe each field. A field definition contains the field name, its type, its size (if applicable), and whether the field is a required field. The field's type can be one of the TFieldType values. Table 18.1 lists the TFieldType values.

TABLE 18.1. DATABASE TABLE FIELD TYPES.

Field type Description
ftUnkown Unknown or undetermined
ftString Character or string field
ftSmallint 16-bit integer field
ftInteger 32-bit integer field
ftWord 16-bit unsigned integer field
ftBoolean Boolean field
ftFloat Floating-point numeric field
ftCurrency Money field
ftBCD Binary-Coded Decimal field
ftDate Date field
ftTime Time field
ftDateTime Date and time field
ftBytes Fixed number of bytes (binary storage)
ftVarBytes Variable number of bytes (binary storage)
ftAutoInc Auto-incrementing 32-bit integer counter field
ftBlob Binary large object field
ftMemo Text memo field
ftGraphic Bitmap field
ftFmtMemo Formatted text memo field
ftParadoxOle Paradox OLE field
ftDBaseOle dBASE OLE field

ftTypedBinary Typed binary field

You create a field definition by using the Add method of the TFieldDefs class. The FieldDefs property of TTable is a TFieldDefs object. Putting all this together, then, adding a new string field to a database would look like this:

Table.FieldDefs.Add(`Customer', ftString, 30, False);

This code line adds a string field called Customer with a size of 30 characters. The field is not a required field because the last parameter of the Add method is False. Add as many field definitions as you need, setting the appropriate data type and size for each field.

Creating the Index Definitions

If your table will be indexed, you also need to add one or more index definitions. Adding index definitions is much the same as adding field definitions. The IndexDefs property of TTable contains the index definitions. To add a new index definition, call the Add method of TIndexDefs:

Table.IndexDefs.Add(`', `CustNo', [ixPrimary]);

The first parameter of the Add method is used to specify the index name. If you are creating a primary index (as in this example), you don't need to specify an index name. The second field is used to specify the field or fields on which to index. If you have more than one field in the index, you separate each field with a semicolon--for example,

Table.IndexDefs.Add(`', `Room;Time', [ixPrimary]);

The last parameter of the Add method is used to specify the index type. This parameter takes a TIndexOptions set. Different index options can be specified in combination. For example, an index might be a primary index that is sorted in descending order. In that case, the call to Add might look like this:


Table.IndexDefs.Add(`', `CustNo', [ixPrimary, ixDescending]);

Creating the Table

After you add all the field and index definitions to the table, you create the table. So far you have been setting up the table's layout but haven't actually created the table. Creating the table is the easiest step of all:

Table.CreateTable;

The CreateTable method performs the actual creation of the table on disk. CreateTable takes the contents of the FieldDefs and IndexDefs properties and creates the table structure, based on those contents.

Now that the table is created, you can fill it with data by using any method required for your application. You can fill the table programmatically or allow your users to add or edit the data from a form.

The book's code contains a program called MakeTabl. This program first creates a table and then fills it with data. The program fills the table with data from the CUSTOMER.TXT file created with the MakeText program earlier in the chapter. Listing 18.2 contains the unit for the main form (MakeTblU.pas). Note that the uses list for this program includes the DBGrids, DBTables, and DB units.

LISTING 18.2. MakeTblU.pas.

unit MakeTblU;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Grids, DBGrids, DbTables, Db;
type
  TForm2 = class(TForm)
    DBGrid: TDBGrid;
    CreateBtn: TButton;
    FillBtn: TButton;
    procedure CreateBtnClick(Sender: TObject);
    procedure FillBtnClick(Sender: TObject);
  private
    { Private declarations } 
  public
    { Public declarations } 
  end;
var
  Form2: TForm2;
implementation
{$R *.DFM} 
procedure TForm2.CreateBtnClick(Sender: TObject);
const
  Dir : PChar = `c:';
var
  Table : TTable;
begin
{ Create a BDE alias, but only if it doesn't already exist. } 
  if not Session.IsAlias(`MyDatabase') then begin
    CreateDirectory(Dir, nil);
    try
      Session.AddStandardAlias(`MyDatabase', Dir, `PARADOX');
      Session.SaveConfigFile;
    except
      MessageDlg(`Error Creating Alias', mtError, [mbOk], 0);
      Exit;
    end;
  end;
  { Now create the Table. } 
  Screen.Cursor := crHourGlass;
  Table := TTable.Create(Self);
  try
    Table.DatabaseName := `MyDatabase';
    Table.TableName := `MyTable.db';
    { Add the field definitions for each field. } 
    Table.FieldDefs.Add(`CustNo', ftFloat, 0, True);
    Table.FieldDefs.Add(`Customer', ftString, 30, False);
    Table.FieldDefs.Add(`Addr1', ftString, 30, False);
    Table.FieldDefs.Add(`Addr2', ftString, 30, False);
    Table.FieldDefs.Add(`City', ftString, 15, False);
    Table.FieldDefs.Add(`State', ftString, 20, False);
    Table.FieldDefs.Add(`Zip', ftString, 10, False);
    Table.FieldDefs.Add(`Phone', ftString, 15, False);
    Table.FieldDefs.Add(`Fax', ftString, 15, False);
    { Add an index definition for the primary key. } 
    Table.IndexDefs.Add(`', `CustNo', [ixPrimary]);
    { Everything is set up, so create the Table. } 
    Table.CreateTable;
  except
    MessageDlg(`Error Creating Table', mtError, [mbOk], 0);
    Screen.Cursor := crDefault;
    Table.Free;
    Exit;
  end;
  { All done, so let the user know. } 
  Table.Free;
  Screen.Cursor := crDefault;
  CreateBtn.Enabled := False;
  FillBtn.Enabled := True;
  MessageDlg(
    `Table Created Successfully', mtInformation, [mbOk], 0);
end;
procedure TForm2.FillBtnClick(Sender: TObject);
var
  Table      : TTable;
  Datasource : TDataSource;
  Lines      : TStringList;
  S, S2      : string;
  I, P       : Integer;
begin
  { Create a TTable. } 
  Table := TTable.Create(Self);
  Table.DatabaseName := `MyDatabase';
  Table.TableName := `MyTable.db';
  { Create a data source and hook it up to the Table. } 
  { Then hook the DBGrid to the datasource. } 
  Datasource := TDataSource.Create(Self);
  Datasource.DataSet := Table;
  DBGrid.Datasource := Datasource;
  { Open the Table and the text file. } 
  Table.Active := True;
  Lines := TStringList.Create;
  Lines.LoadFromFile(`customer.txt');
  { Put the Table in edit mode. } 
  Table.Edit;
  { Process the Lines. } 
  for I := 0 to Pred(Lines.Count) do begin
    { Append a record to the end of the file. } 
    Table.Append;
    { Parse the string and get the first value. } 
    S := Lines[I];
    P := Pos(`,', S);
    S2 := Copy(S, 1, P - 1);
    Delete(S, 1, P);
    { Write the value to the CustNo field. } 
    Table.FieldByName(`CustNo').Value := StrToInt(S2);
    { Continue for each of the fields. } 
    P := Pos(`,', S);
    S2 := Copy(S, 1, P - 1);
    Delete(S, 1, P);
    Table.FieldByName(`Customer').Value := S2;
    P := Pos(`,', S);
    S2 := Copy(S, 1, P - 1);
    Delete(S, 1, P);
    Table.FieldByName(`Addr1').Value := S2;
    P := Pos(`,', S);
    S2 := Copy(S, 1, P - 1);
    Delete(S, 1, P);
    Table.FieldByName(`Addr2').Value := S2;
    P := Pos(`,', S);
    S2 := Copy(S, 1, P - 1);
    Delete(S, 1, P);
    Table.FieldByName(`City').Value := S2;
    P := Pos(`,', S);
    S2 := Copy(S, 1, P - 1);
    Delete(S, 1, P);
    Table.FieldByName(`State').Value := S2;
    P := Pos(`,', S);
    S2 := Copy(S, 1, P - 1);
    Delete(S, 1, P);
    Table.FieldByName(`Zip').Value := S2;
    P := Pos(`,', S);
    S2 := Copy(S, 1, P - 1);
    Delete(S, 1, P);
    Table.FieldByName(`Phone').Value := S2;
    P := Pos(`,', S);
    S2 := Copy(S, 1, P - 1);
    Delete(S, 1, P);
    Table.FieldByName(`FAX').Value := S2;
    { We might get a key violation exception if we try to add a } 
    { record with a duplicate customer number. If that happens, } 
    { we need to inform the user, cancel the edits, put the     } 
    { put the Table back in edit mode, and continue processing. } 
    try
      Table.Post;
    except
      on EDBEngineError do begin
        MessageBox(Handle,
          `Duplicate Customer Number', `Key Violation', 0);
        Table.Cancel;
        Table.Edit;
        Continue;
      end;
    end;
  end;
  { All done with the TStringList so it. } 
  Lines.Free;
  { We won't the Table so that the Table's data shows } 
  { in the DBGrid. VCL will the Table and Datasource  } 
  { for us. } 
  {Table.Free; } 
  {Datasource.Free; } 
end;
end. 

Using Data Modules

As you know by now, setting up database components is easy in Delphi. Still, there is some time involved, even for the simple examples you have been writing.

You have to place a Table or Query component on the form and choose the database name. Then you have to set the table name (for a Table) or SQL property (for a Query). You might have to set other properties, depending on how you are using the database. You might also have several events to handle. Next, you have to place a DataSource component on the form and attach it to the table or query. If your application makes use of a Database component, you have to set that component's properties and events as well. None of this is hard, but it would be nice if you could do it all just once for all your programs. Data modules enable you to do exactly that.

At the base level, data modules are really just specialized forms. To create a data module, open the Object Repository and double-click the Data Module icon. Delphi creates the data module and a corresponding source unit just as it does when you create a new form. You can set the Name property of the data module and save it to disk, again, just like a form.

After you create the data module, you place data access components on it. Then you set all the properties for the data access components as needed. You can even create event handlers for any components on the data module. When you have set everything just the way you want it, save the data module. Every form in your application can then access the data module.

Setting Up a Sample Data Module

A simple exercise will help you understand data modules better. First, you'll set up the data module, and then you'll put it to work:

1. Create a new application. Change the main form's Name property to MainForm. Save the project. Save the main form as DSExMain.pas and the project as DSExampl.dpr.

2. Choose File|New. When the Object Repository appears, double-click the Data Module icon to create a new data module. The Form Designer displays the data module. Change the Name property to DBDemos.

3. Click on the Data Access tab on the Component palette. Place a Table component on the data module. Change the DatabaseName property to DBDEMOS and the TableName to ANIMALS.DBF. Change the Name property to AnimalsTable.

4. Place a second Table component on the data module. Set the DatabaseName property to DBDEMOS and the TableName property to BIOLIFE.DB. Change the Name property to BiolifeTable.

5. Place a DataSource component on the data module. Change its Name property to Animals and its DataSet property to AnimalsTable.

6. Place another DataSource component on the data module. Change this data source's Name to Biolife and its DataSet property to BiolifeTable. Your data module now looks like Figure 18.2.

FIGURE 18.2. The finished data module.

7. Double-click on the background of the data module. An OnCreate event handler will be created for the data module. Type this code in the event handler:

AnimalsTable.Open;
BiolifeTable.Open;


8. Save the project. When prompted, save the data module as DataMod.pas.

Adding to Your Data Module

Now let's put the new data module to use. You are going to create two buttons for the application's main form. One button will display a form that shows the Animals table, and the other will display the Biolife table. Here goes:

1. Create a new form. Change the form's Caption property to Animals Form and the Name property to AnimalsForm.

2. Choose File|Use Unit. Choose the DataMod unit from the Use Unit dialog and click OK. The data module is now accessible from the main form.

3. Place a DBGrid component and a DBNavigator component on the form. Select both the DBGrid and DBNavigator components. Locate the DataSource property in the Object Inspector and click the drop-down arrow next to the property. You will see the following displayed in the list of available data sources:

DBDemos.Animals
DBDemos.Biolife


Choose the DBDemos.Animals data source.

4. Save the unit as DSExU2.pas (or a more meaningful name if you like).

5. Again, create a new form for the project. Repeat steps 1 through 3, but this time choose DBDEMOS.Biolife for the data source and change the Caption to BioLife Form. Change the Name property to BiolifeForm. Save the form as DSExU3.pas. Figure 18.3 shows the Delphi IDE after completing this step.

Running the Data Module

These steps demonstrate that after you create a data module, you can use the components on that data module from anywhere in your program. All you have to do is use the unit for the data module, and then all data-aware components will be capable of detecting the data module. Let's finish this application so that you can try it out:

1. Place a button on the main form. Change its Caption property to Show Animals.

2. Double-click the button to generate an OnClick handler for the button. Type this code in the event handler:

AnimalsForm.Show;


3. Drop another button on the form. Change its Caption property to Show Biolife.

4. Create an OnClick event handler for this button and type the following code in the event handler:

BiolifeForm.Show;
5. Choose File|Use Unit. Choose the DSExU2 unit and click OK.

6. Repeat step 5 to include the DSExU3 unit.

Now you can run the program. When you click a button, the appropriate form will appear. Figure 18.4 shows the program running.

FIGURE 18.3. The second form completed.

FIGURE 18.4. The program running with both forms displayed.

Data modules make it easy to set up your database components once and then reuse those components over and over. After you create a data module, you can save it to the Object Repository, where it is always available for your use.

Creating Reports

A database program is not complete without some way of viewing and printing data, and that is where reports enter the picture. Up to this point, you have been looking at ways to view individual records or multiple records with the DBGrid component. These methods might be perfect for some applications, but sooner or later you will need more control over the viewing of records.

Besides viewing the data onscreen, you will almost certainly need to print the data. A database application that can't print is not very useful. Delphi's QuickReport components enable you to view and print your data with ease.

QuickReport Overview

Before you can create a report, you need an overview of how the QuickReport components work.

The QuickRep Component

The base QuickReport component is the QuickRep component. This component acts as a canvas on which you place the elements that your report will display (I'll discuss the report elements soon).

The QuickRep component has properties that affect the way the report will appear when printed. For example, the Page property is a class containing properties called TopMargin, BottomMargin, LeftMargin, RightMargin, Columns, Orientation, PaperSize, and so on.

The PrinterSettings property is also a class. This property has its own properties, called Copies, Duplex, FirstPage, LastPage, and OutputBin. The ReportTitle property is used to display the print job description in the Windows Print Manager and in the title bar of the QuickReport preview window. The Units property controls whether margins are displayed in millimeters, inches, picas, or other choices. The DataSet property is used to set the dataset from which the report's data will be obtained.


NOTE: The DataSet property must be set and the associated dataset must be active before anything will show up in the report.

Primary QuickRep methods include Preview and Print. The Print method, as its name implies, prints the report. The Preview method displays a modal preview window. The preview window includes buttons for viewing options, first page, last page, previous page, next page, print, print setup, save report, open report, and close preview. Figure 18.5 shows the QuickReport preview window at runtime.

FIGURE 18.5. The QuickReport preview window.

QuickRep events of note include OnPreview and OnNeedData. You can use the OnPreview event, instead of the default preview window, to provide a custom preview window. When using a data source other than a VCL database, you use the OnNeedData event. For example, you can create a report from a string list, an array, or a text file.

Report Bands

A QuickReport is composed of bands. Bands come in many different types. A basic report has at least three types: a title band, a column header band, and a detail band. The title band contains the report title, which is displayed only on the first page of the report. The column header band is used to display the column headers for the fields in the dataset; the column header appears at the top of every page. Some reports, such as a report used to generate mailing labels, do not have a column headers band.

The detail band is the band that does all the work. On the detail band you place any data that you want in the report. You define the contents of the detail band, and QuickReport repeats the detail band for every record in the dataset. In a minute you'll do an exercise that illustrates how the different bands work.

Other commonly used band types include page header, page footer, group header, group footer, and summary bands. The QRBand component defines a QuickReport band. The BandType property is used to specify the band type (title, detail, header, footer, and so on).

The bands automatically arrange themselves on the QuickRep component, based on the band's type. For example, if you place a QRBand on the report and change its BandType to rbPageFooter, the band will be moved below all other bands. Likewise, a page header band will be placed above all other bands.

QuickReport Design Elements

QuickReport design elements come in three forms. The first form includes components for text labels, images, shapes, headers, footers, and so on. These components are primarily used to display static design elements. For example, the report title is usually set once and then doesn't change. Another example is a graphic used to display a company's logo on the report. The components in this group are very similar to the standard VCL components. The QRLabel component resembles a standard Label component, a QRImage is similar to the VCL Image component, the QRShape component is similar to the regular Shape component, and so on. Use these components to design the static portions of your reports.

The second category of elements contains QuickReport versions of the standard VCL data-aware components. These components are placed on the detail band of a report. Components in this group include the QRDBText, QRDBRichEdit, and QRDBImage components. Data is pulled from the dataset and placed into these components to fill the body of the report.

The third group of QuickReport components includes the QRSysData component and the QRExpr component. The QRSysData component is used to display page numbers, the report date, the report time, the report title, and so on. The QRExpr component is used to display calculated results. The Expression property defines the expression for the calculation. The Expression property has a property editor called the Expression builder that is used to define simple expressions. An expression might be simple, such as multiplying two fields, or complete with formulas such as AVERAGE, COUNT, or SUM.

Creating Reports by Hand

Certainly the best way to write truly custom reports is by hand. That might sound difficult, but fortunately the QuickReport components make it easy. The best way for me to explain how to create reports by hand is to take you through an exercise. This exercise creates a simple application that displays and prints a report in list form. I won't tell you to perform every single step in this exercise. For example, I won't tell you to save the project or give you filenames to use--you can figure those out for yourself. Also, you don't have to worry about making the report real pretty at this point. You can go back later and tidy up.

The first step is to create the main form of the application. After that's done, you create the basic outline of the report. Here goes:

1. Create a new application. Place two buttons on the main form. Change the caption of the first button to Preview Report and the caption of the second button to Print Report.

2. Choose File|New. Double-click the Report icon in the Object Repository. Delphi creates a new QuickReport form.

3. Place a Table component on the QuickReport form. Change the DatabaseName property to DBDEMOS and the TableName property to EMPLOYEE.DB. Set the Active property to True.

4. Select the QuickReport form. Change the DataSet property to Table1 and change the ReportTitle property to Employee Report.

5. Switch back to the main form. Double-click the button labeled Preview Report. Type this code in the OnClick event handler:

QuickReport2.Preview;


6. Double-click the Print Report button and type the following in the OnClick event handler:

QuickReport2.Print;


7. Choose File|Use Unit and include the QuickReport form.

Now you have a blank report. What you need to do next is add a title band, a column header band, and a detail band. For the next few steps you might want to look ahead to Figure 18.6 to see the final result. Follow these steps:

1. Select a QRBand component from the QReport tab of the Component palette and place it on the report. The band is a title band by default.

2. Select a QRLabel component and place it on the title band. Change the Caption property to Employee Report and change the Font property to your preference (I used Arial, 18 point, bold). Align the component so that it is centered on the band.

3. Place another band on the report. Change the BandType property to rbColumnHeader and change the Font to bold and underlined.

4. Place a QRLabel on the left side of the column header band and change the Caption property to Employee Number. Place a second QRLabel on the band to the right of the first and change the Caption property to Name. Place a third QRLabel on the band to the right of the last label and change the Caption to Salary.

5. Place another band on the report and change the BandType property to rbDetail. Notice that the band moves below the other bands after you change the band type.

6. Place a QRDBText component on the left edge of the detail band (align it with the Employee Number label on the column header band). Change the DataSet property to Table1 and the DataField property to EmpNo.

7. Place another QRDBText on the detail band and align it with the Name component on the column header band. Change the DataSet property to Table1 and the DataField property to FirstName. Place another QRDBText just to the right of the last one (see Figure 18.6). Attach it to the LastName field of the database table.

	8.	Add a final QRDBText component on the detail band. Place it below the Salary component on the column header band and attach it to the Salary field in the table. Your form now looks like Figure 18.6. 

FIGURE 18.6. Your QuickReport form.

You are probably wondering what the report will look like on paper. Guess what? You don't have to wait to find out. Just right-click on the QuickReport form and choose Preview from the context menu. The QuickReport preview window is displayed, and you can preview the report.

To print the report, click the Print button. When you are done with the report preview, click the Close button. You can now run the program and try out the Preview Report and Print Report buttons.


NOTE: The report you just created is double-spaced. To change the spacing, reduce the height of the detail band.

Before leaving this discussion of reports, let me show you one other nice feature of QuickReport. Right-click on the QuickReport form and choose Report settings from the context menu. The Report Settings dialog will be displayed, as shown in Figure 18.7. This dialog enables you to set the primary properties of the QuickRep component visually rather than use the Object Inspector.

FIGURE 18.7. The QuickReport Report Settings dialog.

Creating Reports the Easy Way

Delphi comes with three built-in QuickReport forms, which can be found on the Forms tab of the Object Repository. The forms are called Quick Report Labels, Quick Report List, and Quick Report Master/Detail. These forms give you a head start on creating reports. You can generate one of the forms from the Object Repository and then modify the form as needed.

Deploying a Delphi Database Application

As I said on Day 16, the Borland Database Engine (BDE) is a collection of DLLs and drivers that enable your Delphi application to talk to various types of databases. When you ship an application that uses the BDE, you need to make sure that you ship the proper BDE files and that they are properly registered on your users' machines.

The most sensible way to do this is with a Borland-approved installation program. Here at TurboPower Software, we use the Wise Install System from Great Lakes Business Solutions (http://www.glbs.com). Others include InstallShield and its little brother, InstallShield Express. Conveniently, InstallShield Express comes with Delphi Professional and Client/Server, so if you have one of those versions of Delphi, you don't have to rush right out and buy an installation program.

You might be wondering why Borland is involved in dictating how the BDE must be deployed. The reason is simple when you think about it: There are many BDE versions in use. Some folks might be writing and deploying applications that use the BDE from Delphi 1. Others might be using the BDE from C++Builder 1. Still others could be using the BDE that comes with Delphi 4 in their applications.

The important point to realize is that the BDE is backward compatible. Newer BDE versions are guaranteed to work with applications written for older BDE versions. This system will only work, however, as long as everyone plays by the rules. Part of the rules say, "Thou shalt not arbitrarily overwrite existing BDE files." Certified installation programs check the version number of any BDE files they find. If the file being installed is older than the existing file, the installation program leaves the existing file in place.

This ensures that the user will always have the latest BDE files on his or her system. Another service that these certified installation programs provide is to determine exactly which files you need to deploy for your application. You should read DEPLOY.TXT in the Delphi root directory for more details on deploying applications that use the BDE.

Summary

There's no question that creating database applications requires a lot of work. The good news is that Delphi makes the job much easier than other development environments. Today you found out something about the nonvisual aspects of database programming. You also found out about data modules and how to use them. You ended the day with a look at QuickReport. QuickReport makes creating reports for your database applications easy. I also explained what is required to deploy a database application.

Workshop

The Workshop contains quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you have learned. You can find answers to the quiz questions in Appendix A, "Answers to the Quiz Questions."

Q&A

Q I am trying to create a database at runtime. I have created a BDE alias and set all the field definitions, but the table is never created on my hard disk. What have I done wrong?

A You have probably failed to call the CreateTable method. You must call this method to physically create the table.

Q When designing a report, can I set all my report's properties at one time?

A Yes. Just right-click the QuickRep component and choose Report settings from the context menu. The Report Settings dialog is displayed, and you can set most of the report properties visually.

Q Can a data module include code as well as components?

A Yes. A data module can contain any code necessary to carry out operation of the data module. The data module can contain event handlers or methods that you create. The methods you create can be public or private (for the data module's use only).

Q When I preview my report, it is blank. What is wrong?

A More than likely you have not set the Active property of the dataset to True. The dataset must be open before the report will function.

Q Can I use more than one detail band on a report?

A No. You can place more than one detail band on a report, but only the first one will be used when the report is generated.

Q Why do I need a Borland-approved installation program to install my database application?

A The BDE is complicated to install, and an approved installation program is guaranteed to do the installation correctly.

Quiz

1. What method do you call to create a database table at runtime?
2. What does the Edit method of TTable do?

3. What method do you call when you want to apply the changes made to a record?

4. How do you create a new data module?

5. Is a data module a regular form?

6. What method do you call to print a QuickReport?

7. What type of QuickReport band displays the dataset's data?

8. What component is used to display the page number on a report?

9. How can you preview a report at design time?

10. What is the QRExpr component used for?

Exercises

1. Create a database (a BDE alias) and a table for the database, either through code or by using the Delphi database tools.

2. Create a data module containing the database table from exercise 1.
3. Generate a report that creates mailing labels. (Hint: Start with a QuickReport Labels object from the Forms page of the Object Repository.)

4. Modify the QuickReport you created in this chapter so that the employee's first and last names are displayed by a QRExpr component.

5. Read the DEPLOY.TXT file in your Delphi directory to understand what is involved in deploying a Delphi database application.


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.