Академический Документы
Профессиональный Документы
Культура Документы
com/print/29122
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.
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.
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.
1 of 9 20.11.2008 17:20
Navigating and Editing a ClientDataSet http://dn.codegear.com/print/29122
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.
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,
2 of 9 20.11.2008 17:20
Navigating and Editing a ClientDataSet http://dn.codegear.com/print/29122
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.
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.
MoveBy permits you to move forward and backward 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.
3 of 9 20.11.2008 17:20
Navigating and Editing a ClientDataSet http://dn.codegear.com/print/29122
The second situation where the record navigation might not be possible is related to the current record
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.
You include in the constant array the data values you want to assign to each field in the dataset. If
4 of 9 20.11.2008 17:20
Navigating and Editing a ClientDataSet http://dn.codegear.com/print/29122
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;
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.
5 of 9 20.11.2008 17:20
Navigating and Editing a ClientDataSet http://dn.codegear.com/print/29122
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 data-aware 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 try-finally 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.
6 of 9 20.11.2008 17:20
Navigating and Editing a ClientDataSet http://dn.codegear.com/print/29122
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;
'. ' +
'EOF = ' + BoolToStr(ClientDataSet1.Eof, True) +
'. ';
end;
7 of 9 20.11.2008 17:20
Navigating and Editing a ClientDataSet http://dn.codegear.com/print/29122
8 of 9 20.11.2008 17:20
Navigating and Editing a ClientDataSet http://dn.codegear.com/print/29122
end;
9 of 9 20.11.2008 17:20