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.
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:
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.
procedure TDataModule2.ClientDataSet1CalcFields(DataSet: TDataSet);
if (not ClientDataSet1.FieldbyName('Price').IsNull) and
(not ClientDataSet1.FieldbyName('Quantity').IsNull) then
ClientDataSet1.FieldByName('Total Price').Value :=
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
more complete discussion of ClientDataSet indexes will appear in a later article
in this series.
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
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.
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);
ExtractFilePath(Application.ExeName) + 'parts.xml';
if not FileExists(DataModule2.ClientDataSet1.FileName) then
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
Nested datasets represent one-to-many relationships. Imagine, for instance,
that you have a 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.
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
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
procedure TForm1.FormCreate(Sender: TObject);
with ClientDataSet1 do
with TStringField.Create(Self) do
Name := 'ClientDataSet1FirstName';
FieldKind := fkData;
FieldName := 'FieldName';
Size := 72;
DataSet := ClientDataSet1;
with TMemoField.Create(Self) do
Name := 'ClientDataSet1LastName';
FieldKind := fkData;
FieldName := 'Last Name';
DataSet := ClientDataSet1;
end; //Last Name
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.
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
When your ClientDataSet's structure is defined using TFields at design-time,
metadata in a previously 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
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
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.
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).
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.
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 firstname.lastname@example.org, or
Web site at www.JensenDataSystems.com.
here for a
listing of upcoming seminars, workshops, and conferences where Cary Jensen is
) 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.
Download Delphi XE6 now!
Get Free Trial
Webinars on demand!
More social media choices:
Delphi on Google+
@RADTools on Twitter
Server Response from: ETNASC04