You are on page 1of 6

08/09/2014

Navigating and Editing a ClientDataSet

EMBAR C ADER O HO ME

LO C ATIO N | ENGLISH | LO G O N

Watch, Follow, &


Connect with Us
Share This

COMMUNITIES

ARTICLES

BLOGS

RESOURCES

DOWNLOADS

HELP

EDN Delphi Components Using Components

Navigating and Editing a ClientDataSet

RATING

By: Cary Jensen


Abstract: You navigate and edit a ClientDataSet in a manner similar to how you navigate and edit almost
another other dataset. This article provides an introductory look at basic ClientDataSet navigation and editing.
I usually try to start from the beginning, covering the more basic techniques before continuing to the more
advanced, and that has been my plan with this series. In the articles that precede this one I have provided
a general introduction to the use and behaviors of a ClientDataSet, as well as how to create its structure
and indexes. In this installment I will take an introductory look at the manipulation of data stored in a
ClientDataSet. Topics to be covered include basic programmatic navigation of the data in a ClientDataSet, as
well as simple editing operations. The next two articles in this series will demonstrate record searching and
ranges and filters. Only after these foundation topics are covered will I continue to the more interesting
things that you can do with a ClientDataSet, such as creating nested datasets, cloning cursors, defining
aggregate fields, and more.
For those of you who are already well versed in working with datasets, you will only need to quickly skim
through this article to see if there is something that you find interesting. If you are fairly new to dataset
programming, however, this article will provide you with essential information on the use of ClientDataSets.
As an added benefit, most of these techniques are appropriate for any other datasets that you may have a
chance to use.

Download Trial
Buy Now
Download Delphi XE7
now!
Get Free Trial
Special Offer

While this article focuses primarily on the use of code to navigate and edit data in a ClientDataSet, a natural
place to begin this discussion is with Delphi data-aware controls and the navigation and editing features
they provide.

Navigating with Data-Aware Controls


There are two classes of controls that provide data navigation. The first class is navigation-specific controls.
Delphi provides you with one control in this category, the DBNavigator.
The DBNavigator, shown in the following image, provides a VCR-like interface for navigating data and
managing records. Record navigation is provided by the First, Next, Prior, and Last buttons. Record
management is provided by the Edit, Post, Cancel, Delete, Insert, and Refresh buttons. You can control
which buttons are displayed by a DBNavigator through its VisibleButtons property. For example, if you are
using the DBNavigator in conjunction with a ClientDataSet that reads and writes its data from a local file
(Borland calls this technology MyBase), you will want to remove the nbRefresh flag from the VisibleButtons
property, since attempting to Refresh a ClientDataSet that uses MyBase raises an exception.

RAD Studio Tech Preview

Another DBNavigator property whose default value you may want to change is ShowHint. Some users have
difficulty interpreting the glyphs on the DBNavigator's buttons. For those users, setting ShowHint to True
supplements the glyphs with popup help hints. You can control the text of these hints by editing the Hints
property.
The second category of controls that provide navigation is the multi-record controls. Delphi includes two: the
DBGrid and DBCtrlGrid. A DBGrid displays data in a row/column format. By default, all fields of the
ClientDataSet are displayed in the DBGrid. You can control which fields are displayed, as well as specific
column characteristics, such as color, by editing the DBGrid's Columns collection property. The following is an
example of a DBGrid.
Delphi Free Trial
A DBCtrlGrid, by comparison, is a limited, multi-record container. It is limited in that it can only hold certain
Delphi components, including Labels, DBEdits, DBLabels, DBMemos, DBImages, DBComboBoxes,
DBCheckBoxes, DBLookupComboBoxes, and DBCharts. It is also limited in that it is not available in Kylix. As
a result, the DBCtrlGrid is little used. An example of a two-row, one-column DBCtrlGrid is shown in the
following figure.

edn.embarcadero.com/article/29122

1/6

08/09/2014

Navigating and Editing a ClientDataSet

Developer Skill Sprints

Depending on which multi-record control you are using, you can navigate between records using UpArrow,
DownArrow, Tab, Ctrl-End, Ctrl-Home, PgDn, PgUp, among others. These key presses may produce the
same effect as clicking the Next, Prior, Last, First, and so on, buttons in a DBNavigator. It is also possible to
navigate the records of a dataset using the vertical scrollbar of these controls.
How you edit a record using these controls also depends on which type of control you are using, as well as
their properties. Using the default properties of these controls, you can typically press F2 or click twice on a
field in one of these controls to begin editing. Posting a record occurs when you navigate off an edited
record. Inserting and deleting records, depending on the control's property settings, can also be achieved
using Ins and Ctrl-Del, respectively. Other operations, such as Refresh, are not directly supported.
Consequently, in most cases, multi-record controls are combined with a DBNavigator to provide a complete
set of record management options.

Webinars on demand!

Delphi
Like

Detecting Changes to Record State


Changes that occur when a user navigates or manages a record using a data-aware control is something
that you may want to get involved with, programmatically. For those situations, there are a variety of event
handlers that you can use to evaluate what a user is doing, and provide a customized response.
ClientDataSets, as well as all other TDataSet descendents, posses the following event handlers:
AfterCancel, AfterClose, AfterDelete, AfterEdit, AfterInsert, AfterOpen, AfterPost, AfterRefresh, AfterScroll,
BeforeCancel, BeforeClose, BeforeDelete, BeforeEdit, BeforeInsert, BeforeOpen, BeforePost, BeforeRefresh,
BeforeScroll, OnCalcFields, OnDeleteError, OnEditError, OnFilterRecord, OnNewRecord, and OnPostError.

16,487 people like Delphi.

Facebook social plugin

There are additional event handlers that are available in most situations where a ClientDataSet is being
navigated and edited, and which are always available when data-aware controls are concerned. These are
the event handlers associated with a DataSource. Since all data-aware controls must be connected to at
least one DataSource, the event handlers of a DataSource provide you with another source of customization
when a user navigates and edits records. These event handlers are OnDataChange, OnStateChange, and
OnUpdateData.
OnDataChange triggers whenever a ClientDataSet arrives at a new records, as well as when a
ClientDataSet arrives at the first record when it is initially opened. OnStateChange triggers when a
ClientDataSet changes betw een state, such as when it changes from dsBrowse to dsEdit (when a user
enters the edit mode), or when it changes from dsEdit to dsBrowse (following the posting or cancellation of
a change). Finally, OnUpdateData triggers when the dataset to which the DataSource points is posting its
data.

Navigating Programmatically
Whether data-aware controls are involved or not, it is sometimes necessary to use code to navigate and
edit data in a ClientDataSet, or any DataSet descendent for that matter. For a ClientDataSet, these core
navigation methods include First, Next, Prior, Last, MoveBy, and RecNo. The use of First, Next, Prior, and
Last are pretty much self-explanatory. Each one produces an effect similar to the corresponding buttons on
a DBNavigator.

More social media choices:


Delphi on Google+
@RADTools on Twitter

ARTICLE TAGS
ClientDataSet

MoveBy permits you to move forw ard and backw ard in a ClientDataSet, relative to the current record. For
example, the following statement moves the current cursor 5 records forward in the dataset (if possible):
ClientDataSet1.MoveBy(5);
To move backwards in a dataset, pass MoveBy a negative number. For example, the following statement will
move the cursor to the record that is 100 records prior to the current records (again, if possible):
ClientDataSet1.MoveBy(-100);
The use of RecNo to navigate might come as a surprise. This property, which is always returns -1 in the
TDataSet class, can be used for two purposes. You can read this property to learn the position of the
current record in the current record order (based on which index is currently selected). In the ClientDataSet
you can also write to this property. Doing so moves the cursor to the record in the position defined by the
value you assign to this property. For example, the following statement will move the cursor to the record in
the 5th position of the current index order (if possible):
ClientDataSet1.RecNo := 5;
Each of the preceding examples has been qualified by the statement that the operation will succeed if
possible. This qualification has two aspects to it. First, the cursor movement will not take place if the current
record has been edited, but cannot be posted. For example, if data that cannot pass at least one the
ClientDataSet's Contraints has been added to a record. When you attempt to navigate off a record that
cannot be posted, an exception is raised.
The second situation where the record navigation might not be possible is related to the current record

edn.embarcadero.com/article/29122

2/6

08/09/2014

Navigating and Editing a ClientDataSet

position and the number of records in the dataset. For example, if the current record is the last in the
dataset, it makes no sense to move 5 records forward. Similarly, if the current record is the 99th in the
dataset, an attempt to move backwards by 100 records will fail. You can determine whether an attempt to
navigate succeeded or failed by reading the Eof and Bof properties of the ClientDataSet. Eof (end-of-file) will
return True if a navigation method attempted to move beyond the end of the table. When Eof returns True,
the current record is the last record in the dataset.
Similarly, Bof will return True if a backwards navigation attempted to move before the beginning of the
dataset. In that situation the current record is the first record in the dataset.
RecNo behaves differently. Attempting to set RecNo to a record beyond the end of the table, or prior to the
beginning of the table, raises an exception.

Scanning a ClientDataSet
Combining several of the methods and properties described so far provides you with a mechanism for
scanning a ClientDataSet. Scanning simply means the systematic navigation from one record to the next,
until all records in the dataset have been visited. The following code segment demonstrates how to scan a
ClientDataSet.
procedure TForm1.Button1Click(Sender:
TObject);
begin
if not ClientDataSet1.Active then ClientDataSet1.Open;
ClientDataSet1.First;
while not ClientDataSet1.EOF do
begin
//perform some operation based on one or
//more fields of the ClientDataSet
ClientDataSet1.Next;
end;
end;

Editing a ClientDataSet
You edit a current record in a ClientDataSet by calling its Edit method, after which you change the values of
one or more of its fields. Once your changes have been made, you can either move off the record to attempt
to post the new values, or you can explicitly call the ClientDataSet's Post method. In most cases, navigating
off the record and calling Post produce the same effect. But there are two instances where they do not, and
it is due to these situations that an explicit call to Post should be considered essential. In the first instance,
if you are editing the last record in a dataset and then call Next or Last, the edited record is not posted. The
second situation is similar, and involves editing the first record in a dataset followed by a call to either Prior
to First. So long as you always call Post prior to attempting to navigate, you can be assured that your
edited record will be posted (or raise an exception due to a posting failure).
If you modify a record, and then decide not to post the change, or discover that you cannot post the
change, you can cancel all changes to the record by calling the ClientDataSet's Cancel method. For example,
if you change a record, and then find that calling Post raises an exception, you can call Cancel to cancel the
changes and return the dataset to the dsBrowse state.
To insert and post a record you have several options. You can call Insert or Append, after which your cursor
will be on a newly inserted record (assuming that you started from the dsBrowse state. If you were editing
a record prior to calling Insert or Append, a new record will not be inserted if the record being edited can
not be posted). Once it is inserted, assign data to the fields or that record and call Post to post those
changes.
The alternative to calling Insert or Append is to call InsertRecord or AppendRecord. These methods insert a
new record, assign data to one or more fields, and attempt to post, all in a single call. The following is the
syntax of the InsertRecord method. The syntax of AppendRecord is identical.
procedure InsertRecord(const Values: array of const);
You include in the constant array the data values you want to assign to each field in the dataset. If you
want to leave particular field unassigned, include the value null in the variant array. Fields you want to leave
unassigned at the end of the record can be omitted from the constant array. For example, If you are
inserting and posting a new record into a four-field ClientDataSet, and you want to assign the first field the
value 1000 (a field associated with a unique index), leave the second and fourth fields unassigned, but
assign a value of 'new' to the third record, your InsertRecord invocation may look something like this:
ClientDataSet1.InsertRecord([1001, null,
'new']);
The following code segment demonstrates another instance of record scanning, this time with edits that
need to be posted to each record. In this example, Edit and Post are performed within try blocks. If the
record was placed in the edit mode (which corresponds to the dsEdit state), and cannot be posted, the
change is canceled. If the record cannot even be placed into edit state (which for a ClientDataSet should
only happen if the dataset has its ReadOnly property set to True), the attempt to post changes is skipped.
procedure TForm1.Button1Click(Sender:
TObject);
begin
if not ClientDataSet1.Active then ClientDataSet1.Open;
ClientDataSet1.First;
while not ClientDataSet1.EOF do
begin
try
ClientDataSet1.Edit;
try
ClientDataSet1.Fields[0].Value :=
UpperCase(ClientDataSet1.Fields[0].Value);
ClientDataSet1.Post;
except
//record cannot be posted. Cancel;
ClientDataSet1.Cancel;
end;
except
//Record cannot be edited. Skip
end;
ClientDataSet1.Next;

edn.embarcadero.com/article/29122

3/6

08/09/2014

Navigating and Editing a ClientDataSet


end; //while
end;

Note: Rather than simply canceling changes that cannot be posted, an alternative except clause would
identify why the record could not post, and produce a log which can be used to apply the change at a later
date. Also note that if these changes are being cached, for update in a subsequent call to ApplyUpdates,
the ClientDataSet provides an OnReconcileError event handler that can be used to process failed postings.

Disabling Controls While Navigating


If the ClientDataSet that you are navigating programmatically is attached to data-aware controls through a
DataSource, and you take no other precautions, the data-aware controls will be affected by the navigation.
In the simplest case, where you move directly to another record, the update is welcome, causing the
controls to repaint w ith the data of the newly arrived at record. However, when your navigation involves
moving to two or more records in rapid succession, such as is the case when you scan a ClientDataSet, the
updates can have severe results.
There are two reasons for this. First, the flicker caused by the data-aware controls repainting as the
ClientDataSet arrives at each record is distracting. More importantly, however, is the overhead associated
with a repaint. Repainting visual controls is one of the slowest processes in most GUI (graphic user
interface) applications. If your navigation involves visiting many records, as often the case when you are
scanning, the repaints of your data-aware controls represents a massive amount of unnecessary overhead.
To prevent your data-aware controls from repainting w hen you need to programmatically change the
current record more than once you need to call the ClientDataSet's DisableControls method (this is generally
try of any dataset, as DisableControls is implemented in the TDataSet class). When DisableControls is
called, the ClientDataSet stops communicating with any DataSources that point to it. As a result, the dataaware controls that point to those DataSources are never made aware of the navigation. Once you are
done navigating, call the ClientDataSet's EnableControls. This will resume the communication between the
ClientDataSets and any DataSources that point to it. It will also result in the data-aware controls being
instructed to repaint themselves. However, this repaint occurs only once, in response to the call to
EnableControls, and not due to any of the individual navigations that occurred since DisableControls was
called.
Is it important to recognized that between the time you call DisableControls and EnableControls, the
ClientDataSet is in an abnormal state. In fact, if you call DisableControls and never call a corresponding
EnableControls, the ClientDataSet will appear to the user to have stopped functioning, based on the lack of
activity in the data-aware controls. As a result, it is essential that if you call DisableControls, you structure
your code in such a way that a call to EnableControls is guaranteed. One way to do this it to enter a tryfinally after a call to DisableControls, invoking the corresponding EnableControls in the finally block.
The following is an example of a scan where the user interface is not updated until all record navigation has
completed.
procedure TForm1.Button1Click(Sender:
TObject);
begin
if not ClientDataSet1.Active then ClientDataSet1.Open;
ClientDataSet1.DisableControls;
try
ClientDataSet1.First;
while not ClientDataSet1.EOF do
begin
try
ClientDataSet1.Edit;
try
ClientDataSet1.Fields[0].Value :=
UpperCase(ClientDataSet1.Fields[0].Value);
ClientDataSet1.Post;
except
//record cannot be posted. Cancel;
ClientDataSet1.Cancel;
end;
except
//Record cannot be edit. Skip
end;
ClientDataSet1.Next;
end; //while
finally
ClientDataSet1.EnableControls;
end; //try-finally
end;

Navigation Demonstration
The Navigation project, which you can download from Code Central by clicking this link Navi gation Project,
demonstrates the various methods and properties described in this article. The following figure shows this
project when it is running.

edn.embarcadero.com/article/29122

4/6

08/09/2014

Navigating and Editing a ClientDataSet

Each of the Buttons on this form is associated with an event handler that performs the indicated type of
navigation. In addition, this project includes OnDataChange and OnStateChange DataSource event
handlers that are used to update the panels in the StatusBar at the bottom of the form. These event
handlers are shown in the following code listing.

procedure TForm1.SelectDataFile;
begin
if OpenDialog1.Execute then
begin
if ClientDataSet1.Active then ClientDataSet1.Close;
ClientDataSet1.FileName := OpenDialog1.FileName;
ClientDataSet1.Open;
end
else
Halt;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
SelectDataFile;
end;
procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);
begin
StatusBar1.Panels[0].Text := 'Record ' +
IntToStr(ClientDataSet1.RecNo) + ' of ' +
IntToStr(ClientDataSet1.RecordCount);
StatusBar1.Panels[2].Text :=
'BOF = ' + BoolToStr(ClientDataSet1.Bof, True) +
'. ' +
'EOF = ' + BoolToStr(ClientDataSet1.Eof, True) +
'. ';
end;
procedure TForm1.DataSource1StateChange(Sender: TObject);
begin
StatusBar1.Panels[1].Text :=
'State = ' + GetEnumName(TypeInfo(TDataSetState),
Ord(ClientDataSet1.State));
end;
procedure TForm1.FirstBtnClick(Sender: TObject);
begin
ClientDataSet1.First;
end;
procedure TForm1.NextBtnClick(Sender: TObject);
begin
ClientDataSet1.Next;
end;
procedure TForm1.PriorBtnClick(Sender: TObject);
begin
ClientDataSet1.Prior;
end;
procedure TForm1.LastBtnClick(Sender: TObject);
begin
ClientDataSet1.Last;
end;

edn.embarcadero.com/article/29122

5/6

08/09/2014

Navigating and Editing a ClientDataSet


procedure TForm1.ScanForwardBtnClick(Sender: TObject);
begin
if ControlsStateBtnGrp.ItemIndex = 1 then
ClientDataSet1.DisableControls;
try
ClientDataSet1.First;
while not ClientDataSet1.Eof do
begin
//do something with a record
ClientDataSet1.Next;
end;
finally
if ControlsStateBtnGrp.ItemIndex = 1 then
ClientDataSet1.EnableControls;
end;
end;
procedure TForm1.ScanBackwardBtnClick(Sender: TObject);
begin
if ControlsStateBtnGrp.ItemIndex = 1 then
ClientDataSet1.DisableControls;
try
ClientDataSet1.Last;
while not ClientDataSet1.Bof do
begin
//do something with a record
ClientDataSet1.Prior;
end;
finally
if ControlsStateBtnGrp.ItemIndex = 1 then
ClientDataSet1.EnableControls;
end;
end;
procedure TForm1.MoveByBtnClick(Sender: TObject);
begin
ClientDataSet1.MoveBy(UpDown1.Position);
end;
procedure TForm1.RecNoBtnClick(Sender: TObject);
begin
ClientDataSet1.RecNo := UpDown2.Position;
end;
procedure TForm1.Open1Click(Sender: TObject);
begin
SelectDataFile;
end;
procedure TForm1.Close1Click(Sender: TObject);
begin
ClientDataSet1.Close;
end;

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
cjensen@jensendatasystems.com, 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.

LATEST COMMENTS
Move mouse over comment to see the full text

Could not retrieve comments. Please try again later.


Serve r Re sponse fro m : ETNASC 02

Copyright 1994 - 2013 Embarcadero Technologies, Inc. All rights reserved.

edn.embarcadero.com/article/29122

Site Map

6/6

You might also like