Mastering Delphi 7 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Mastering Delphi 7 [Electronic resources] - نسخه متنی

Marco Cantu

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید

A Directory in a Dataset


An important idea related to datasets in Delphi is that they represent a set of data, regardless of where this data comes from. A SQL server and a local file are examples of traditional datasets, but you can use the same technology to show a list of a system's users, a list of a folder's files, the properties of objects, XML-based data, and so on.

As an example, the second dataset presented in this chapter is a list of files. I've built a generic dataset based on a list of objects in memory (using a TObjectList), and then derived a version in which the objects correspond to a folder's files. The example is simplified by the fact that it is a read-only dataset, so you might find it more straightforward than the previous dataset.





Note

Some of the ideas presented here were discussed in an article I wrote for the Borland Community website, published in June 2000 at the URL bdn.borland.com/article/0,1410,20587,00l.



A List as a Dataset


The generic list-based dataset is called TMdListDataSet and contains the list of objects, a list that is created when you open the dataset and freed when you close it. This dataset doesn't store the record data within the buffer; rather, it saves in the buffer only the position in the list of the entry corresponding to the record's data. This is the class definition:


type
TMdListDataSet = class (TMdCustomDataSet)
protected
// the list holding the data
FList: TObjectList;
// dataset virtual methods
procedure InternalPreOpen; override;
procedure InternalClose; override;
// custom dataset virtual methods
function InternalRecordCount: Integer; override;
procedure InternalLoadCurrentRecord (Buffer: PChar); override;
end;

You can see that by writing a generic custom data class, you can override a few virtual methods of the TDataSet class and of this custom dataset class, and have a working dataset (although this is still an abstract class, which requires extra code from subclasses to work). When the dataset is opened, you have to create the list and set the record size, to indicate you're saving the list index in the buffer:


procedure TMdListDataSet.InternalPreOpen;
begin
FList := TObjectList.Create (True); // owns the objects
FRecordSize := 4; // an integer, the list item id
end;

Further derived classes at this point should also fill the list with objects.





Tip

Like the ClientDataSet, my list dataset keeps its data in memory. However, using some smart techniques, you can also create a list of fake objects and then load the actual objects only when you are accessing them.


Closing is a matter of freeing the list, which has a record count corresponding to the list size:


function TMdListDataSet.InternalRecordCount: Integer;
begin
Result := fList.Count;
end;

The only other method saves the current record's data in the record buffer, including the bookmark information. The core data is the position of the current record, which matches the list index (and also the bookmark):


procedure TMdListDataSet.InternalLoadCurrentRecord (Buffer: PChar);
begin
PInteger (Buffer)^ := fCurrentRecord;
with PMdRecInfo(Buffer + FRecordSize)^ do
begin
BookmarkFlag := bfCurrent;
Bookmark := fCurrentRecord;
end;
end;


Directory Data


The derived directory dataset class has to provide a way to load the objects in memory when the dataset is opened, to define the proper fields, and to read and write the value of those fields. It also has a property indicating the directory to work on—or, to be more precise, the directory plus the file mask used for filtering the files (such as c:\docs\*.txt):


type
TMdDirDataset = class(TMdListDataSet)
private
FDirectory: string;
procedure SetDirectory(const NewDirectory: string);
protected
// TDataSet virtual methods
procedure InternalInitFieldDefs; override;
procedure SetFieldData(Field: TField; Buffer: Pointer); override;
function GetCanModify: Boolean; override;
// custom dataset virtual methods
procedure InternalAfterOpen; override;
public
function GetFieldData(Field: TField;
Buffer: Pointer): Boolean; override;
published
property Directory: string read FDirectory write SetDirectory;
end;

The GetCanModify function is another virtual method of TDataSet, used to determine if the dataset is read-only. In this case, it returns False. You don't have to write any code for the SetFieldData procedure, but you must define it because it is an abstract virtual method.

Because you are dealing with a list of objects, the unit includes a class for those objects. In this case, the file data is extracted from a TSearchRec buffer by the TFileData class constructor:


type
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 for each folder while opening the dataset:


procedure TMdDirDataset.InternalAfterOpen;
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);
end;

The next step is to define the fields of the dataset, which in this case are fixed and depend on the available directory data:


procedure TMdDirDataset.InternalInitFieldDefs;
begin
if fDirectory = '' then
raise EMdDataSetError.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;

Finally, the component has to move the data from the list object referenced by the current record buffer (the ActiveBuffer value) to each field of the dataset, as requested by the GetFieldData method. This function uses either Move or StrCopy, depending on the data type, and it does some conversions for the attribute codes (H for hidden, R for read-only, and S for system) extracted from the related flags and used to determine whether a file is a folder. Here is the code:


function TMdDirDataset.GetFieldData (Field:
TField; Buffer: Pointer): Boolean;
var
FileData: TFileData;
Bool1: WordBool;
strAttr: string;
t: TDateTimeRec;
begin
FileData := fList [Integer(ActiveBuffer^)] as TFileData;
case Field.Index of
0: // filename
StrCopy (Buffer, pchar(FileData.ShortFileName));
1: // timestamp
begin
t := DateTimeToNative (ftdatetime, FileData.Time);
Move (t, Buffer^, sizeof (TDateTime));
end;
2: // size
Move (FileData.Size, Buffer^, sizeof (Integer));
3: // attributes
begin
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 SysUtils.faHidden) > 0 then
strAttr [3] := 'H';
StrCopy (Buffer, pchar(strAttr));
end;
4: // folder
begin
Bool1 := FileData.Attr and SysUtils.faDirectory > 0;
Move (Bool1, Buffer^, sizeof (WordBool));
end;
end; // case
Result := True;
end;

The tricky part in writing this code was figuring out the internal format of dates stored in date/time fields. This is not the common TDateTime format used by Delphi, and not even the internal TTimeStamp, but what is internally called the native date and time format. I've written a conversion function by cloning one I found in the VCL code for the date/time fields:


function DateTimeToNative(DataType: TFieldType;
Data: TDateTime): TDateTimeRec;
var
TimeStamp: TTimeStamp;
begin
TimeStamp := DateTimeToTimeStamp(Data);
case DataType of
ftDate: Result.Date := TimeStamp.Date;
ftTime: Result.Time := TimeStamp.Time;
else
Result.DateTime := TimeStampToMSecs(TimeStamp);
end;
end;

With this dataset available, building the demo program (shown in Figure 17.7) was just a matter of connecting a DBGrid component to the dataset and adding a folder-selection component, the sample ShellTreeView control. This control is set up to work only on files, by setting its Root property to C:\. When the user selects a new folder, the OnChange event handler of the ShellTreeView control refreshes the dataset:


procedure TForm1.ShellTreeView1Change(Sender: TObject; Node: TTreeNode);
begin
MdDirDataset1.Close;
MdDirDataset1.Directory := ShellTreeView1.Path + '\*.*';
MdDirDataset1.Open;
end;


Figure 17.7: The output of the DirDemo example, which uses an unusual dataset that shows directory data





Warning

If your version of Windows has problems with the sample shell controls available in Delphi, you can use the DirDemoNoShell version of the example, which uses the old-fashioned Windows 3.1–compatible Delphi file controls.


/ 279