ClientDataSet in Detail3
ClientDataSet in Detail3
com/print/29001
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.
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
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:
3 of 9 20.11.2008 17:19
Defining a ClientDataSet's Structure Using TFields http://dn.codegear.com/print/29001
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:
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.
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
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.
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.
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.
9 of 9 20.11.2008 17:19