0% found this document useful (0 votes)
200 views9 pages

ClientDataSet in Detail3

This article demonstrates how to define a ClientDataSet's Structure using TFields. How to create virtual and nested dataset fields is also demonstrated. You can define a ClientDataSet's Structure either at design-time or at runtime.

Uploaded by

yc1965
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
200 views9 pages

ClientDataSet in Detail3

This article demonstrates how to define a ClientDataSet's Structure using TFields. How to create virtual and nested dataset fields is also demonstrated. You can define a ClientDataSet's Structure either at design-time or at runtime.

Uploaded by

yc1965
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

Defining a ClientDataSet's Structure Using TFields http://dn.codegear.

com/print/29001

Defining a ClientDataSet's Structure Using


TFields
By: Cary Jensen

Abstract: This article demonstrates how to define a ClientDataSet's structure at both design-time
and runtime using TFields. How to create virtual and nested dataset fields is also demonstrated.
In the last installment of The Professional Developer, I described how to define the structure of a
ClientDataSet using the ClientDataSet's FieldDefs property. This structure is used to create the
in-memory data store when you call the ClientDataSet's CreateDataSet method. The metadata
describing this structure, and any data subsequently entered into the ClientDataSet, will be saved to
disk when the ClientDataSet's SaveToFile method is invoked.
While the FieldDefs property provides you with a convenient and valuable mechanism for defining a
ClientDataSet's structure, it has several short-comings. Specifically, you cannot use FieldDefs to
create virtual fields, which include calculated fields, lookup fields, and aggregate fields. In addition,
creating nested datasets (one-to-many relationships) through FieldDefs is problematic. Specifically,
while I have found it possible to create nested datasets using FieldDefs, I have not been able to
successfully save and then later reload these nested datasets into a ClientDataSets. Only the TFields
method appears to create nested datasets that can be reliably saved to the ClientDataSet's native local
file formats and later re-loaded into memory.
Like the FieldDefs method of defining the structure of a ClientDataSet, you can define a
ClientDataSet's structure using TFields either at design-time or at runtime. Since the design-time
technique is the easiest to demonstrate, this article with start with it. Defining a ClientDataSet's
structure using TFields at runtime is shown later in this article.

Defining a ClientDataSet's Structure at Design-Time


You define the TFields that represent the structure of a ClientDataSet at design-time using the Fields
Editor. Unfortunately, this process is a bit more tedious than that using FieldDefs. Specifically, using
the FieldDefs collection editor you can quickly add one or more FieldDef definitions, each of which
defines the characteristic of a corresponding field in the ClientDataSets's structure. Using the TFields
method, you must add one field at a time. All this really means is that it takes a little longer to define
a ClientDataSet's structure using TFields than it does using FieldDefs.
Although using the TFields method of defining a ClientDataSet's structure is more time consuming, it
has the advantage of permitting you to define both the fields of a table's structure for the purpose of
storing data, as well as to define virtual fields. Virtual fields are used define dataset fields whose
values are calculated at runtime -- the values are not physically stored.
The following steps demonstrate how to define a ClientDataSet's structure using TFields as
design-time:

1. Place a ClientDataSet from the Data Access page of the Component Palette onto a form.
2. Right-click the ClientDataSet and select Fields Editor. The empty Fields Editor is shown in the
following figure

1 of 9 20.11.2008 17:19
Defining a ClientDataSet's Structure Using TFields http://dn.codegear.com/print/29001

3. Right-click the Fields Editor and select New Field (or simply press the INS key). The New Field
dialog box is displayed, as shown in the following figure.

4. Enter PartNo in the Name field, and Integer in the Type field. Leave the Field Type radio
button set to the default, which is Data. Your New Field dialog box should now look something
like the following.

5. Click OK to accept this new field. The newly added field should now appear in the Fields Editor.
6. Repeat steps 3 through 5 to add three more fields to the table structure. For the first field, set
Name to Description, Type to String, and Size to 80. For the second field, set Name to Price
and Type to Currency. For the third field, set Name to Quantity and Type to Integer. When
you are done, the Fields Editor should look something like the following.

2 of 9 20.11.2008 17:19
Defining a ClientDataSet's Structure Using TFields http://dn.codegear.com/print/29001

Adding a Calculated Virtual Field


Adding a virtual field to a ClientDataSet's structure at design-time is only slightly more complicated
than adding a data field. This added complexity involves setting additional properties and/or adding
additional event handlers.
Let's begin by adding a calculated field. Calculated fields require both a new field whose type is
Calculated, and an OnCalcFields event handler, which is associated with the ClientDataSet itself. This
event handler is used to calculate the value that will be displayed in this virtual field.
Note: This example demonstrates the addition of a calculated virtual field, which is available for most
TDataSet descendents. Alternatively, these same basic steps can be used to add an InternalCalc field,
which is a special calculated field associated with ClientDataSets. InternalCalc virtual fields can be
more efficient than Calculated virtual fields, since they need to be re-calculated less often than
calculated fields.

1. Begin by right-clicking the Fields Editor and selecting New Field (or press INS).
2. Using the New Fields dialog box, set Name to Total Price, Type to Currency, and Field Type to
Calculated. Click OK to add the new field.

3. Now select the ClientDataSet in the Object Inspector or the Object TreeView, and display the
Events page of the Object Inspector.
4. Double-click the OnCalcFields event handler to add this event handler. In Delphi or Kylix,
complete this event handler as shown here
code:

procedure TDataModule2.ClientDataSet1CalcFields(DataSet: TDataSet);


begin
if (not ClientDataSet1.FieldbyName('Price').IsNull) and

3 of 9 20.11.2008 17:19
Defining a ClientDataSet's Structure Using TFields http://dn.codegear.com/print/29001

(not ClientDataSet1.FieldbyName('Quantity').IsNull) then


ClientDataSet1.FieldByName('Total Price').Value :=
ClientDataSet1.FieldbyName('Price').Value *
ClientDataSet1.FieldByName('Quantity').Value;
end;

Adding a Virtual Aggregate Field


Aggregate fields, which can be used to perform a number of automatic calculations across one or more
records of your data, do not require event handlers, but do require that the ClientDataSet have at
least one index. The following steps will walk you through adding an index, as well as an aggregate
field that will use the index. A more complete discussion of ClientDataSet indexes will appear in a later
article in this series.

1. With the ClientDataSet selected in the Object Inspector, choose the IndexDefs property and
double-click the ellipsis button that appears. Using the IndexDefs collection editor, click the Add
New button once.
2. With this newly adding IndexDef selected in the IndexDefs collection editor, use the Object
Inspector to set its Name property to PNIndex, and its Fields property to PartNo.
3. Select the ClientDataSet in the Object Inspector once again. Set its IndexName property to
PNIndex and its AggregatesActive property to True.
4. We are now ready to add the aggregate field. Double-click the ClientDataSet to display the
Fields Editor (alternatively, you can right-click the ClientDataSet and select Fields Editor from
the displayed context menu).
5. Right-click the Fields Editor and select New Field.
6. Set Name to Total Parts and Data Type to Aggregate. Select OK to close the New Field dialog
box. The aggregate virtual field is displayed in its own section of the Fields Editor, as shown in
the following figure.

7. Select the Total Parts aggregate field in the Fields Editor. Then, using the Object Inspector, set
the Expression property to Sum(Quantity), the IndexName property to PXIndex, and Active to
True.
That's all it takes. All you need to do now is call the CreateDataSet method at runtime (or
alternatively, right-click the ClientDataSet at design-time and select Create DataSet). Of course, if you
want to actually see the resulting ClientDataSet, you will also have to hook it up to one or more
data-aware controls.
The use of the TField definitions described here are demonstrated in the FieldDemo project, which you
can download from Code Central. The following is the main form of this project.

4 of 9 20.11.2008 17:19
Defining a ClientDataSet's Structure Using TFields http://dn.codegear.com/print/29001

Notice that just below the main menu there is a Label and a DBLabel. The DBLabel is associated with
the Total Parts aggregate field, and it is used to display the sum of the values entered in the Quantity
field of the ClientDataSet. The DBNavigator and the DBGrid that appear on this main form are
associated with the ClientDataSet through a DataSource. This ClientDataSet is created at runtime, if it
does not already exist. This is done from code executed from the main form's OnCreate event handler,
shown here:

procedure TForm1.FormCreate(Sender: TObject);


begin
DataModule2.ClientDataSet1.FileName :=
ExtractFilePath(Application.ExeName) + 'parts.xml';
if not FileExists(DataModule2.ClientDataSet1.FileName) then
DataModule2.ClientDataSet1.CreateDataSet
else
DataModule2.ClientDataSet1.Open;
end;

As you can see from this code, the ClientDataSet in this example resides on a data module. Upon
startup, this form calculates the name of the file in which the ClientDataSet's data can be stored. It
then tests to see if this file already exists. If it does not, CreateDataSet is called, otherwise the
ClientDataSet is opened.
The following figure shows this form at runtime, after some records have been added.

Creating Nested DataSet


Nested datasets represent one-to-many relationships. Imagine, for instance, that you have a

5 of 9 20.11.2008 17:19
Defining a ClientDataSet's Structure Using TFields http://dn.codegear.com/print/29001

ClientDataSet designed to hold information about your customers. Imagine further that for each
customer you want to be able to store one or more phone numbers. There are three techniques that
developers often use to provide this feature. The first, and least flexible technique, is to add a fixed
number of fields to the ClientDataSet to hold the possible phone numbers. For example, one for a
business number, another for the a home number, and a third for a mobile phone number. The
problem with this approach is that you have to decide, in advance, the maximum number of phone
numbers that you can store for any given customer.
The second technique is to create a separate file to hold customer phone numbers. This file would
have to include one or more fields that define a link between a given customer and their phone
numbers (such as a unique customer identification number), as well as fields for holding the type of
phone number and the phone number itself. Using this approach, you can store any number of phone
numbers for each customer.
The third technique is to create a nested dataset. A nested dataset is created by adding a Field of
DataSet type to a ClientDataSet's structure. This dataset field is then assigned to the DataSetField
property of a second client dataset. Using this second ClientDataSet, you can define fields to store the
one or more records of related data. In this example it might make sense to add two fields, one to hold
the type of phone number (such as, home, cell, fax, and so forth), and a second to hold the phone
number itself. Similar to the second technique, nested datasets permit a customer to have any
number of phone numbers. On the other hand, unlike the second technique, in which phone numbers
are stored in a separate file, there is no need for any fields to link phone numbers to customers, since
the phone numbers are actually "nested" within each customer's record.
Here is how you create a nested dataset at design-time.

1. Using the technique outlined earlier in this article (using the Fields Editor), create one field of
data type Data for each regular field in the dataset (such as Customer Name, Title, Address1,
Address2, and so forth).
2. For each nested dataset, add a new field, using the same technique that you use for the other
data fields, but set its Data Type to DataSet.
3. For each DataSet field that you add to your first ClientDataSet, add an additional ClientDataSet.
Associate each of these secondary ClientDataSets with one of the primary ClientDataSet's
DataSet fields using the secondary ClientDataSet's DataSetField property.
4. To define the fields of each nested dataset, add fields to each secondary ClientDataSet using its
Fields Editor, just as you added fields to the primary ClientDataSet. For example, following the
customer/phone numbers example discussed here, the nested dataset fields would include phone
type and phone number.
For an example of a project that demonstrates how to create nested datasets at design-time, download
the NestedDataSetFields project from Code Central. This project provides an example of how the
customer/phone numbers application might be implemented. This project contains a data module that
includes two ClientDataSets. One is used to hold the customer information, and it includes a DataSet
field called PhoneNumbers. This DataSet field is associated with a second ClientDataSet through the
second ClientDataSet's DataSetField property. The Fields Editor for this second ClientDataSet, shown
in the following figure, displays its two String fields, one for Phone Type and the other for Phone
Number.

6 of 9 20.11.2008 17:19
Defining a ClientDataSet's Structure Using TFields http://dn.codegear.com/print/29001

Creating a ClientDataSet's Structure at Runtime using TFields


In the previous article in this series, where a ClientDataSet's structure was defined using FieldDefs,
you learned that you can define the structure of a ClientDataSet both at design-time as well as at
runtime. As explained in that article, the advantage of using design-time configuration is that you can
use the features of the Object Inspector to assist in the definition of the ClientDataSet's structure. This
approach, however, is only useful if you know the structure of your ClientDataSet in advance. If you
do not, your only option is to define your structure at runtime.
You define your TFields at runtime using the methods and properties of the appropriate TField or
TDataSetField class. Specifically, you call the constructor of the appropriate TField or TDataSetField
object, setting the properties of the created object to define its nature. Among the properties of the
constructed object, one of the most important is the DataSet property. This property defines to which
TDataSet descendant you want the object associated (which will be a ClientDataSet in this case, since
we are discussing this type of TDataSet). After creating all of the TFields or TDataSetFields, you call
the ClientDataSet's CreateDataSet method. Doing so creates the ClientDataSet's structure based on
the TFields to which it is associated.
The following is a simple example of defining a ClientDataSet's structure using TFields.

procedure TForm1.FormCreate(Sender: TObject);


begin
with ClientDataSet1 do
begin
with TStringField.Create(Self) do
begin
Name := 'ClientDataSet1FirstName';
FieldKind := fkData;
FieldName := 'FieldName';
Size := 72;
DataSet := ClientDataSet1;
end; //FieldName
with TMemoField.Create(Self) do
begin
Name := 'ClientDataSet1LastName';
FieldKind := fkData;
FieldName := 'Last Name';
DataSet := ClientDataSet1;
end; //Last Name
ClientDataSet1.CreateDataSet
end;
end;

You can test this code for yourself easy enough. Simply create a project and place on the main form a
ClientDataSet, a DataSource, a DBGrid, and a DBNavigator. Assign the DataSet property of the DBGrid
and the DBNavigator to the DataSource, assign the DataSet property of the DataSource to
ClientDataSet, and ensure that the ClientDataSet is named ClientDataSet1. Finally, add the preceding
code to the OnCreate event handler of the form to which these components appear, and run the
project.

TFields and FieldDefs are Different


When your structure is defined using TFields, there is an important behavior that might not be
immediately obvious. Specifically, the TFields specified at design-time using the Fields Editor define
objects that are created automatically when the form, data module, or frame to which they are
associated is created. These objects define the ClientDataSet's structure, which in turn defines the
value of the ClientDataSet's FieldDefs property.
This same behavior does not apply when a ClientDataSet's structure is defined using FieldDefs at
design-time. Specifically, the TFields of a ClientDataSet whose structure is defined using FieldDefs is
defined when the ClientDataSet's CreateDataSet method is invoked. But they are also created when
metadata is read from a previously saved ClientDataSet file. If a ClientDataSet is loaded from a saved
file, the structure defined in the metadata of the saved file takes precedence. In other words, the
FieldDefs property created at design-time is replaced by FieldDefs defined by the saved metadata, and
this is used to create the TFields.
When your ClientDataSet's structure is defined using TFields at design-time, metadata in a previously

7 of 9 20.11.2008 17:19
Defining a ClientDataSet's Structure Using TFields http://dn.codegear.com/print/29001

saved ClientDataSet is not used to define the TFields, since they already exist. As a result, when a
ClientDataSet's structure is defined using TFields, and you attempt to load previously save data, it is
essential that the metadata in the file being loaded be consistent with the defined TFields.

Creating a ClientDataSet's Structure Using TFields at Runtime


As mentioned in the preceding section, TFields defined at design-time cause the automatic creation of
the corresponding TField instances at runtime (as well as FieldDefs). If you define your ClientDataSet's
structure at runtime, by calling the constructor of the various TField and TDataSetField objects that
you need, you must follow the call to these constructors with a call to the ClientDataSet's
CreateDataSet method before the ClientDataSet can be used. This is true even when you intend to
load the ClientDataSet from previously saved data.
The reason for this is that, as pointed out in the previous section, ClientDataSet structures defined
using TFields do not rely on the metadata of previously saved ClientDataSets. Instead, the structure
relies on the TFields and TDataSetFields that have been created for the ClientDataSet. This becomes
particularly obvious when you consider that virtual fields are not stored in the files saved by a
ClientDataSet. The only way that you can have virtual fields in a ClientDataSet whose structure is
defined at runtime is to create these fields using the appropriate constructors, and then call
CreateDataSet to build the ClientDataSet's in-memory data store. Only then can a compatible,
previously saved data file be loaded into the ClientDataSet.
Here is another way to put it. When you define your ClientDataSet's structure using FieldDefs, you call
CreateDataSet only if there is no previously saved data file. If there is a previously saved data file,
you simply load it into the ClientDataSet - CreateDataSet does not need to be invoked. The
ClientDataSet's structure is based on the saved metadata.
By comparison, when you define your ClientDataSet's structure using TFields at runtime, you always
call CreateDataSet (but only after creating and configuring the TField and TDataSetField instances that
define the ClientDataSet's structure). This must be done whether or not you want to load previously
saved data.

An Example
The VideoLibrary project, which can be downloaded from Code Central, includes code that
demonstrates how to create data, aggregate, lookup, and nested dataset fields at runtime using
TFields. This project, whose running main form is shown in the following figure, includes two primary
ClientDataSets. One is used to hold a list of videos and another holds a list of Talent (actors). The
ClientDataSet that holds the video information contains two nested datasets: one to hold the list of
talent for that particular video and another to hold a list of the video's special features (for instance, a
music video found on a DVD).

8 of 9 20.11.2008 17:19
Defining a ClientDataSet's Structure Using TFields http://dn.codegear.com/print/29001

This project is too complicated to describe adaquately in this limited space (I'll save that discussion for
a future article). Instead, I;ll leave it up to you to download the project. In particular, you will want to
examine the OnCreate event handler for this project's data module. There you will see how the
various data fields, virtual fields, dataset fields, and indexes are created and configured.

About the Author


Cary Jensen is President of Jensen Data Systems, Inc., a Texas-based training and consulting company
that won the 2002 Delphi Informant Magazine Readers Choice award for Best Training. He is the
author and presenter for Delphi Developer Days (www.DelphiDeveloperDays.com), an information-
packed Delphi (TM) seminar series that tours North America and Europe. Cary is also an award-
winning, best-selling co-author of eighteen books, including Building Kylix Applications (2001,
Osborne/McGraw-Hill), Oracle JDeveloper (1999, Oracle Press), JBuilder Essentials (1998,
Osborne/McGraw-Hill), and Delphi In Depth (1996, Osborne/McGraw-Hill). For information about
onsite training and consulting you can contact Cary at [email protected], or visit his
Web site at www.JensenDataSystems.com.
Click here for a listing of upcoming seminars, workshops, and conferences where Cary Jensen is
presenting.
Copyright ) 2002 Cary Jensen, Jensen Data Systems, Inc.
ALL RIGHTS RESERVED. NO PART OF THIS DOCUMENT CAN BE COPIED IN ANY FORM WITHOUT THE
EXPRESS, WRITTEN CONSENT OF THE AUTHOR.

Published on: 8/16/2002 1:20:08 PM


Server Response from: BDN9A

Copyright© 1994 - 2008 Embarcadero Technologies, Inc. All rights reserved.

9 of 9 20.11.2008 17:19

You might also like