home *** CD-ROM | disk | FTP | other *** search
-
- {*******************************************************}
- { }
- { Delphi Text DataSet Sample }
- { }
- { Copyright (c) 1997,98 Borland International }
- { }
- {*******************************************************}
-
- unit TextData;
-
- {
- This file contains a very basic TDataSet implementation which works with
- text files. For simplicity, the text file is interpreted as a single column,
- multiple row table.
-
- Currently, the notes in this source file represent the only documnentation
- on how to create a TDataSet implentation. For more information you can
- refer to TBDEDataSet in DBTABLES.PAS which represents a complete TDataSet
- implementation and provides a good example of what methods can be overridden
- and what they should do.
-
- Any TDataSet implementation must provide Bookmark capabilities and
- implement all functions which directly access the record buffer. The base
- TDataSet manages a group of record buffers, but has no requirements regarding
- what is contained in each record buffer. The base TDataSet also manages
- the communcation with any attached TDataSource components and the respective
- data aware controls.
-
- }
-
- interface
-
- uses
- DB, Classes;
-
- const
- MaxStrLen = 240; { This is an arbitrary limit }
-
- type
-
- { TRecInfo }
-
- { This structure is used to access additional information stored in
- each record buffer which follows the actual record data.
-
- Buffer: PChar;
- ||
- \/
- --------------------------------------------
- | Record Data | Bookmark | Bookmark Flag |
- --------------------------------------------
- ^-- PRecInfo = Buffer + FRecInfoOfs
-
- Keep in mind that this is just an example of how the record buffer
- can be used to store additional information besides the actual record
- data. There is no requirement that TDataSet implementations do it this
- way.
-
- For the purposes of this demo, the bookmark format used is just an integer
- value. For an actual implementation the bookmark would most likely be
- a native bookmark type (as with BDE), or a fabricated bookmark for
- data providers which do not natively support bookmarks (this might be
- a variant array of key values for instance).
-
- The BookmarkFlag is used to determine if the record buffer contains a
- valid bookmark and has special values for when the dataset is positioned
- on the "cracks" at BOF and EOF. }
-
- PRecInfo = ^TRecInfo;
- TRecInfo = packed record
- Bookmark: Integer;
- BookmarkFlag: TBookmarkFlag;
- end;
-
- { TTextDataSet }
-
- TTextDataSet = class(TDataSet)
- private
- FData: TStrings;
- FRecBufSize: Integer;
- FRecInfoOfs: Integer;
- FCurRec: Integer;
- FFileName: string;
- FLastBookmark: Integer;
- FSaveChanges: Boolean;
- protected
- { Overriden abstract methods (required) }
- function AllocRecordBuffer: PChar; override;
- procedure FreeRecordBuffer(var Buffer: PChar); override;
- procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
- function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
- function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
- function GetRecordSize: Word; override;
- procedure InternalAddRecord(Buffer: Pointer; Append: Boolean); override;
- procedure InternalClose; override;
- procedure InternalDelete; override;
- procedure InternalFirst; override;
- procedure InternalGotoBookmark(Bookmark: Pointer); override;
- procedure InternalHandleException; override;
- procedure InternalInitFieldDefs; override;
- procedure InternalInitRecord(Buffer: PChar); override;
- procedure InternalLast; override;
- procedure InternalOpen; override;
- procedure InternalPost; override;
- procedure InternalSetToRecord(Buffer: PChar); override;
- function IsCursorOpen: Boolean; override;
- procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
- procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
- procedure SetFieldData(Field: TField; Buffer: Pointer); override;
- protected
- { Additional overrides (optional) }
- function GetRecordCount: Integer; override;
- function GetRecNo: Integer; override;
- procedure SetRecNo(Value: Integer); override;
- public
- function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
- published
- property FileName: string read FFileName write FFileName;
- property Active;
- end;
-
- procedure Register;
-
- implementation
-
- uses Windows, SysUtils, Forms;
-
- { TTextDataSet }
-
- { This method is called by TDataSet.Open and also when FieldDefs need to
- be updated (usually by the DataSet designer). Everything which is
- allocated or initialized in this method should also be freed or
- uninitialized in the InternalClose method. }
-
- procedure TTextDataSet.InternalOpen;
- var
- I: Integer;
- begin
- { Load the textfile into a stringlist }
- FData := TStringList.Create;
- FData.LoadFromFile(FileName);
-
- { Fabricate integral bookmark values }
- for I := 1 to FData.Count do
- FData.Objects[I-1] := Pointer(I);
- FLastBookmark := FData.Count;
-
- { Initialize our internal position.
- We use -1 to indicate the "crack" before the first record. }
- FCurRec := -1;
-
- { Initialize an offset value to find the TRecInfo in each buffer }
- FRecInfoOfs := MaxStrLen;
-
- { Calculate the size of the record buffers.
- Note: This is NOT the same as the RecordSize property which
- only gets the size of the data in the record buffer }
- FRecBufSize := FRecInfoOfs + SizeOf(TRecInfo);
-
- { Tell TDataSet how big our Bookmarks are (REQUIRED) }
- BookmarkSize := SizeOf(Integer);
-
- { Initialize the FieldDefs }
- InternalInitFieldDefs;
-
- { Create TField components when no persistent fields have been created }
- if DefaultFields then CreateFields;
-
- { Bind the TField components to the physical fields }
- BindFields(True);
- end;
-
- procedure TTextDataSet.InternalClose;
- begin
- { Write any edits to disk and free the managing string list }
- if FSaveChanges then FData.SaveToFile(FileName);
- FData.Free;
- FData := nil;
-
- { Destroy the TField components if no persistent fields }
- if DefaultFields then DestroyFields;
-
- { Reset these internal flags }
- FLastBookmark := 0;
- FCurRec := -1;
- end;
-
- { This property is used while opening the dataset.
- It indicates if data is available even though the
- current state is still dsInActive. }
-
- function TTextDataSet.IsCursorOpen: Boolean;
- begin
- Result := Assigned(FData);
- end;
-
- { For this simple example we just create one FieldDef, but a more complete
- TDataSet implementation would create multiple FieldDefs based on the
- actual data. }
-
- procedure TTextDataSet.InternalInitFieldDefs;
- begin
- FieldDefs.Clear;
- FieldDefs.Add('Line', ftString, MaxStrLen, False);
- end;
-
- { This is the exception handler which is called if an exception is raised
- while the component is being stream in or streamed out. In most cases this
- should be implemented useing the application exception handler as follows. }
-
- procedure TTextDataSet.InternalHandleException;
- begin
- Application.HandleException(Self);
- end;
-
- { Bookmarks }
- { ========= }
-
- { In this sample the bookmarks are stored in the Object property of the
- TStringList holding the data. Positioning to a bookmark just requires
- finding the offset of the bookmark in the TStrings.Objects and using that
- value as the new current record pointer. }
-
- procedure TTextDataSet.InternalGotoBookmark(Bookmark: Pointer);
- var
- Index: Integer;
- begin
- Index := FData.IndexOfObject(TObject(PInteger(Bookmark)^));
- if Index <> -1 then
- FCurRec := Index else
- DatabaseError('Bookmark not found');
- end;
-
- { This function does the same thing as InternalGotoBookmark, but it takes
- a record buffer as a parameter instead }
-
- procedure TTextDataSet.InternalSetToRecord(Buffer: PChar);
- begin
- InternalGotoBookmark(@PRecInfo(Buffer + FRecInfoOfs).Bookmark);
- end;
-
- { Bookmark flags are used to indicate if a particular record is the first
- or last record in the dataset. This is necessary for "crack" handling.
- If the bookmark flag is bfBOF or bfEOF then the bookmark is not actually
- used; InternalFirst, or InternalLast are called instead by TDataSet. }
-
- function TTextDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
- begin
- Result := PRecInfo(Buffer + FRecInfoOfs).BookmarkFlag;
- end;
-
- procedure TTextDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
- begin
- PRecInfo(Buffer + FRecInfoOfs).BookmarkFlag := Value;
- end;
-
- { These methods provide a way to read and write bookmark data into the
- record buffer without actually repositioning the current record }
-
- procedure TTextDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
- begin
- PInteger(Data)^ := PRecInfo(Buffer + FRecInfoOfs).Bookmark;
- end;
-
- procedure TTextDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
- begin
- PRecInfo(Buffer + FRecInfoOfs).Bookmark := PInteger(Data)^;
- end;
-
- { Record / Field Access }
- { ===================== }
-
- { This method returns the size of just the data in the record buffer.
- Do not confuse this with RecBufSize which also includes any additonal
- structures stored in the record buffer (such as TRecInfo). }
-
- function TTextDataSet.GetRecordSize: Word;
- begin
- Result := MaxStrLen;
- end;
-
- { TDataSet calls this method to allocate the record buffer. Here we use
- FRecBufSize which is equal to the size of the data plus the size of the
- TRecInfo structure. }
-
- function TTextDataSet.AllocRecordBuffer: PChar;
- begin
- GetMem(Result, FRecBufSize);
- end;
-
- { Again, TDataSet calls this method to free the record buffer.
- Note: Make sure the value of FRecBufSize does not change before all
- allocated buffers are freed. }
-
- procedure TTextDataSet.FreeRecordBuffer(var Buffer: PChar);
- begin
- FreeMem(Buffer, FRecBufSize);
- end;
-
- { This multi-purpose function does 3 jobs. It retrieves data for either
- the current, the prior, or the next record. It must return the status
- (TGetResult), and raise an exception if DoCheck is True. }
-
- function TTextDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
- DoCheck: Boolean): TGetResult;
- begin
- if FData.Count < 1 then
- Result := grEOF else
- begin
- Result := grOK;
- case GetMode of
- gmNext:
- if FCurRec >= RecordCount - 1 then
- Result := grEOF else
- Inc(FCurRec);
- gmPrior:
- if FCurRec <= 0 then
- Result := grBOF else
- Dec(FCurRec);
- gmCurrent:
- if (FCurRec < 0) or (FCurRec >= RecordCount) then
- Result := grError;
- end;
- if Result = grOK then
- begin
- StrLCopy(Buffer, PChar(FData[FCurRec]), MaxStrLen);
- with PRecInfo(Buffer + FRecInfoOfs)^ do
- begin
- BookmarkFlag := bfCurrent;
- Bookmark := Integer(FData.Objects[FCurRec]);
- end;
- end else
- if (Result = grError) and DoCheck then DatabaseError('No Records');
- end;
- end;
-
- { This routine is called to initialize a record buffer. In this sample,
- we fill the buffer with zero values, but we might have code to initialize
- default values or do other things as well. }
-
- procedure TTextDataSet.InternalInitRecord(Buffer: PChar);
- begin
- FillChar(Buffer^, RecordSize, 0);
- end;
-
- { Here we copy the data from the record buffer into a field's buffer.
- This function, and SetFieldData, are more complex when supporting
- calculated fields, filters, and other more advanced features.
- See TBDEDataSet for a more complete example. }
-
- function TTextDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
- begin
- StrLCopy(Buffer, ActiveBuffer, Field.Size);
- Result := PChar(Buffer)^ <> #0;
- end;
-
- procedure TTextDataSet.SetFieldData(Field: TField; Buffer: Pointer);
- begin
- StrLCopy(ActiveBuffer, Buffer, Field.Size);
- DataEvent(deFieldChange, Longint(Field));
- end;
-
- { Record Navigation / Editing }
- { =========================== }
-
- { This method is called by TDataSet.First. Crack behavior is required.
- That is we must position to a special place *before* the first record.
- Otherwise, we will actually end up on the second record after Resync
- is called. }
-
- procedure TTextDataSet.InternalFirst;
- begin
- FCurRec := -1;
- end;
-
- { Again, we position to the crack *after* the last record here. }
-
- procedure TTextDataSet.InternalLast;
- begin
- FCurRec := FData.Count;
- end;
-
- { This method is called by TDataSet.Post. Most implmentations would write
- the changes directly to the associated datasource, but here we simply set
- a flag to write the changes when we close the dateset. }
-
- procedure TTextDataSet.InternalPost;
- begin
- FSaveChanges := True;
- { For inserts, just update the data in the string list }
- if State = dsEdit then FData[FCurRec] := ActiveBuffer else
- begin
- { If inserting (or appending), increment the bookmark counter and
- store the data }
- Inc(FLastBookmark);
- FData.InsertObject(FCurRec, ActiveBuffer, Pointer(FLastBookmark));
- end;
- end;
-
- { This method is similar to InternalPost above, but the operation is always
- an insert or append and takes a pointer to a record buffer as well. }
-
- procedure TTextDataSet.InternalAddRecord(Buffer: Pointer; Append: Boolean);
- begin
- FSaveChanges := True;
- Inc(FLastBookmark);
- if Append then InternalLast;
- FData.InsertObject(FCurRec, PChar(Buffer), Pointer(FLastBookmark));
- end;
-
- { This method is called by TDataSet.Delete to delete the current record }
-
- procedure TTextDataSet.InternalDelete;
- begin
- FSaveChanges := True;
- FData.Delete(FCurRec);
- if FCurRec >= FData.Count then
- Dec(FCurRec);
- end;
-
- { Optional Methods }
- { ================ }
-
- { The following methods are optional. When provided they will allow the
- DBGrid and other data aware controls to track the current cursor postion
- relative to the number of records in the dataset. Because we are dealing
- with a small, static data store (a stringlist), these are very easy to
- implement. However, for many data sources (SQL servers), the concept of
- record numbers and record counts do not really apply. }
-
- function TTextDataSet.GetRecordCount: Longint;
- begin
- Result := FData.Count;
- end;
-
- function TTextDataSet.GetRecNo: Longint;
- begin
- UpdateCursorPos;
- if (FCurRec = -1) and (RecordCount > 0) then
- Result := 1 else
- Result := FCurRec + 1;
- end;
-
- procedure TTextDataSet.SetRecNo(Value: Integer);
- begin
- if (Value >= 0) and (Value < FData.Count) then
- begin
- FCurRec := Value - 1;
- Resync([]);
- end;
- end;
-
- { This procedure is used to register this component on the component palette }
-
- procedure Register;
- begin
- RegisterComponents('Data Access', [TTextDataSet]);
- end;
-
- end.
-