Dataset Without Databases
Dataset Without Databases
Login
http://www.borland.com/
AppServer BorCon C# C++ CORBA CaliberRM Delphi InterBase JDataStore Java StarTeam Together
Abstract: How do you access non-relational data (such as a disk directory) with data-
aware controls? Rely on a key element of Delphi: the TDataSet class. By Marco
Cantù. Link to code corrected June 7, 2000.
By Marco Cantù
A favorite subject, of late, among Delphi programmers is the merit of ADO. During the last couple of
weeks, friends have been praising the power of ADO to access data through OLE -DB providers you
can write. For example, you can access mail folders or the Windows 2000 Active Directory using the
ADSI API. This struck me, not because I've decided to write an OLE-DB provider in Delphi, but
because I've realized that Delphi already has a much more powerful tool than ADO offers: the
TDataSet class of the VCL.
Developers use BDE or ADO as database engines to abstract access to data in different formats and
from database servers. Although both approaches are used in Delphi, the real abstraction Delphi
provides for data access is the TDataSet class. This class provides a high-level interface to the data
used by various data-aware components. Since Delphi 3, this structure has really been opening up.
Borland has begun to introduce TDataSet-derived classes (including TClientDataSet, TADODataSet,
and TIBDataSet for InterBase support), as have a number of third-party vendors.
Data diversions
The custom dataset I wrote is capable of reading file data and allowing a user to display it in a data-
aware component, as shown in the following figure:
file://C:\Temp\Datasets%20without%20databases.htm 12-04-2005
Datasets without databases Página 2 de 6
You might have heard that writing a custom dataset in Delphi is a lot of work. It generally is, mainly
because documentation on the topic has always been sparse. The three books I know of that address
custom dataset writing are Using Delphi 3, Delphi Developer's Guide, and my Delphi Developer's
Handbook. Also, The Delphi Magazine has featured two articles on the subject. Each of these
references provides a complete description of the development of a custom dataset.
Writing a complete database-oriented custom dataset can be tricky. Writing a simple one to access a
directory, however, is something you can easily do once you grasp the basics of dataset
programming. Though the component I've written is too detailed to fully describe here, I can give you
an overview of the key concepts.
A custom dataset has to override over 20 virtual abstract methods of the base class, and it generally
overrides some additional virtual methods as well. To save some extra coding in the future, I've split
my work into two steps. The first is the TListDataSet component, a dataset supporting navigation
among the data of a TObjectList. This is one of the new Delphi 5 container classes, which only slightly
differs from TList. It uses objects instead of pointers (nothing really different, from a technical point of
view) and has the ability to destroy the objects in the list when the list is destroyed or cleared.
Dataset basics
In short, the TListDataSet class contains a list of objects created in the constructor and destroyed by
the destructor. The dataset works with record buffers, which store the item numbers in the Index field:
type
PRecInfo = ^TRecInfo;
TRecInfo = record
Index: Integer;
Bookmark: Longint;
BookmarkFlag: TBookmarkFlag;
file://C:\Temp\Datasets%20without%20databases.htm 12-04-2005
Datasets without databases Página 3 de 6
end;
These records are created in the AllocRecordBuffer method, cleared in InternalInitRecord, and
destroyed in the FreeRecordBuffer method. It is a responsibility of the dataset class to determine how
many buffers to allocate and call these three methods. The bookmark is basically the same value as
the Index, so I could have skipped one of the two. It is handled by the GetBookmarkData and
SetBookmarkData methods, while GetBookmarkFlag and SetBookmarkFlag read and write the final
field of the TRecInfo structure above. The GetRecNo and SetRecNo methods simply work on the
number of the current list item, name FCur, plus one. (The one is added because the record number
should start from one while the internal counter is zero-based.)
The only two methods with code are GetRecord and InternalOpen. GetRecord browses the dataset,
moving back and forth while simultaneously placing data into the record buffer (in practice, only the
list index plus the bookmark information). Here's the complete code for this method:
function TListDataSet.GetRecord
(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
begin
Result := grOK; // default
case GetMode of
gmNext: // move on
if fCurrent < fList.Count - 1 then
Inc (fCurrent)
else
Result := grEOF; // end of file
gmPrior: // move back
if fCurrent &mt; 0 then
Dec (fCurrent)
else
Result := grBOF; // start of file
gmCurrent: ; // nothing to do
end;
The InternalOpen method performs a standard sequence of operations: initialize the field definitions,
create the field objects, bind them, and initialize the internal structures. It also calls an additional
virtual function I've defined, ReadListData, which stores data in the list. This operation will be
performed later by the specific subclass.
procedure TListDataSet.InternalOpen;
begin
// initialize field definitions
// and create fields
InternalInitFieldDefs;
if DefaultFields then
CreateFields;
BindFields (True);
file://C:\Temp\Datasets%20without%20databases.htm 12-04-2005
Datasets without databases Página 4 de 6
// initialize
FRecordSize := sizeof (TRecInfo);
FCurrent := -1;
BookmarkSize := sizeOf (Integer);
FIsTableOpen := True;
end;
Classic derivations
Because I am dealing with a list of objects, the first thing to do in the derived class is to define those
objects. In this case, I am working with file data extracted by a TSearchRec buffer by the TFileData
class constructor:
TFileData = class
public
ShortFileName: string;
Time: TDateTime;
Size: Integer;
Attr: Integer;
constructor Create(var FileInfo:
TSearchRec);
end;
constructor TFileData.Create
(var FileInfo: TSearchRec);
begin
ShortFileName := FileInfo.Name;
Time := FileDateToDateTime
(FileInfo.Time);
Size := FileInfo.Size;
Attr := FileInfo.Attr;
end;
This constructor is called within the ReadListData method, mentioned earlier, to store data for the files
of the current directory:
procedure TDirDataSet.ReadListData;
var
Attr: Integer;
FileInfo: TSearchRec;
FileData: TFileData;
begin
// scan all files
Attr := faAnyFile;
FList.Clear;
if SysUtils.FindFirst(fDirectory,
Attr, FileInfo) = 0 then
repeat
FileData := TFileData.Create(FileInfo);
FList.Add(FileData);
until SysUtils.FindNext(FileInfo) <> 0;
SysUtils.FindClose(FileInfo);
file://C:\Temp\Datasets%20without%20databases.htm 12-04-2005
Datasets without databases Página 5 de 6
end;
At this point I need to perform two more steps. First, define the fields of the dataset, which in this case
are fixed and depend on the available directory data:
procedure TDirDataset.InternalInitFieldDefs;
begin
// TODO: set proper exception...
if fDirectory = '' then
raise Exception.Create('Missing directory');
// field definitions
FieldDefs.Clear;
FieldDefs.Add('FileName', ftString, 40, True);
FieldDefs.Add('TimeStamp', ftDateTime);
FieldDefs.Add('Size', ftInteger);
FieldDefs.Add('Attributes', ftString, 3);
FieldDefs.Add('Folder', ftBoolean);
end;
Second, move the data from the object of the list referenced by the current record buffer (the
ActiveBuffer value) to each field of the dataset, as requested by the GetFieldData method:
function TDirDataset.GetFieldData
(Field: TField; Buffer: Pointer):
Boolean;
var
FileData: TFileData;
Bool1: WordBool;
strAttr: string;
t: TDateTimeRec;
begin
FileData := fList
[PRecInfo(ActiveBuffer).Index]
as TFileData;
case Field.Index of
0: // filename
StrCopy(Buffer,
pchar(FileData.ShortFileName));
1: begin // timestamp
t := DateTimeToNative(ftdatetime,
FileData.Time);
Move(t, Buffer^, sizeof(TDateTime));
end;
2: // size
Move(FileData.Size, Buffer^,
sizeof(Integer));
3: begin // attributes
strAttr := ' ';
if (FileData.Attr and
SysUtils.faReadOnly) > 0 then
strAttr[1] := 'R';
if (FileData.Attr and
SysUtils.faSysFile) > 0 then
strAttr[2] := 'S';
if (FileData.Attr and
file://C:\Temp\Datasets%20without%20databases.htm 12-04-2005
Datasets without databases Página 6 de 6
The tricky part in writing this code was figuring out the internal format of dates stored within date/time
fields. This is not the common TDateTime format used by Delphi, but one I can convert to using the
DateTimeToNative function. Moving strings and integers is simple. The attributes codes (H for hidden,
R for read-only, and S for system) are extracted from the related flags, used also to determine if a file
is actually a folder.
Demo delight
With this dataset available, building the demo program was simply a matter of connecting a DBGrid
component to it and adding a folder selection (an old-fashioned component still available in Delphi).
Writing the component wasn't simple, but I doubt I could have written an OLE-DB provider in less
time. Writing a custom dataset, although complex, can be managed. I got faster with practice; I built
my fourth one quite quickly. To make things easier, use the base class presented in this article as a
framework for your own datasets based on lists. The only thing I've skipped is adding support for
editing, inserting, and deleting the records, which would require considerable extra effort. I avoided
this by defining the dataset as read-only (technically you have to return False from the GetCanModify
method).
The complete source code for this custom dataset can be found on my Web site: feel free to use it as
a starting point of your own work, and (if you are willing) share the results with me and the community.
Votes 0 0 0 0 4 Responses: 4
Average: 5.0 1 2 3 4 5
Add or View
1=Poor,
comments
Rate Article
Rating 1 2 3 4 5 5=Excellent
Article ID: 20587 Publish Date: June 07, 2000 Last Modified: June 07, 2000
Made in Borland® Copyright© 1994-2005 Borland Software Corporation. All rights reserved. Legal Notices, Privacy Policy
file://C:\Temp\Datasets%20without%20databases.htm 12-04-2005