home *** CD-ROM | disk | FTP | other *** search
/ Delphi 5 for Professionals / DELPHI5.iso / Runimage / Delphi50 / Demos / Db / Textdata / textdata.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-08-11  |  14.8 KB  |  462 lines

  1.  
  2. {*******************************************************}
  3. {                                                       }
  4. {       Delphi Text DataSet Sample                      }
  5. {                                                       }
  6. {       Copyright (c) 1997,98 Borland International     }
  7. {                                                       }
  8. {*******************************************************}
  9.  
  10. unit TextData;
  11.  
  12. {
  13.    This file contains a very basic TDataSet implementation which works with
  14.  text files.  For simplicity, the text file is interpreted as a single column,
  15.  multiple row table.
  16.  
  17.    Currently, the notes in this source file represent the only documnentation
  18.  on how to create a TDataSet implentation.  For more information you can
  19.  refer to TBDEDataSet in DBTABLES.PAS which represents a complete TDataSet
  20.  implementation and provides a good example of what methods can be overridden
  21.  and what they should do.
  22.  
  23.    Any TDataSet implementation must provide Bookmark capabilities and
  24.  implement all functions which directly access the record buffer.  The base
  25.  TDataSet manages a group of record buffers, but has no requirements regarding
  26.  what is contained in each record buffer.  The base TDataSet also manages
  27.  the communcation with any attached TDataSource components and the respective
  28.  data aware controls.
  29.  
  30. }
  31.  
  32. interface
  33.  
  34. uses
  35.   DB, Classes;
  36.  
  37. const
  38.   MaxStrLen = 240; { This is an arbitrary limit }
  39.  
  40. type
  41.  
  42. { TRecInfo }
  43.  
  44. {   This structure is used to access additional information stored in
  45.   each record buffer which follows the actual record data.
  46.  
  47.     Buffer: PChar;
  48.    ||
  49.    \/
  50.     --------------------------------------------
  51.     |  Record Data  | Bookmark | Bookmark Flag |
  52.     --------------------------------------------
  53.                     ^-- PRecInfo = Buffer + FRecInfoOfs
  54.  
  55.   Keep in mind that this is just an example of how the record buffer
  56.   can be used to store additional information besides the actual record
  57.   data.  There is no requirement that TDataSet implementations do it this
  58.   way.
  59.  
  60.   For the purposes of this demo, the bookmark format used is just an integer
  61.   value.  For an actual implementation the bookmark would most likely be
  62.   a native bookmark type (as with BDE), or a fabricated bookmark for
  63.   data providers which do not natively support bookmarks (this might be
  64.   a variant array of key values for instance).
  65.  
  66.   The BookmarkFlag is used to determine if the record buffer contains a
  67.   valid bookmark and has special values for when the dataset is positioned
  68.   on the "cracks" at BOF and EOF. }
  69.  
  70.   PRecInfo = ^TRecInfo;
  71.   TRecInfo = packed record
  72.     Bookmark: Integer;
  73.     BookmarkFlag: TBookmarkFlag;
  74.   end;
  75.  
  76. { TTextDataSet }
  77.  
  78.   TTextDataSet = class(TDataSet)
  79.   private
  80.     FData: TStrings;
  81.     FRecBufSize: Integer;
  82.     FRecInfoOfs: Integer;
  83.     FCurRec: Integer;
  84.     FFileName: string;
  85.     FLastBookmark: Integer;
  86.     FSaveChanges: Boolean;
  87.   protected
  88.     { Overriden abstract methods (required) }
  89.     function AllocRecordBuffer: PChar; override;
  90.     procedure FreeRecordBuffer(var Buffer: PChar); override;
  91.     procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
  92.     function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
  93.     function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
  94.     function GetRecordSize: Word; override;
  95.     procedure InternalAddRecord(Buffer: Pointer; Append: Boolean); override;
  96.     procedure InternalClose; override;
  97.     procedure InternalDelete; override;
  98.     procedure InternalFirst; override;
  99.     procedure InternalGotoBookmark(Bookmark: Pointer); override;
  100.     procedure InternalHandleException; override;
  101.     procedure InternalInitFieldDefs; override;
  102.     procedure InternalInitRecord(Buffer: PChar); override;
  103.     procedure InternalLast; override;
  104.     procedure InternalOpen; override;
  105.     procedure InternalPost; override;
  106.     procedure InternalSetToRecord(Buffer: PChar); override;
  107.     function IsCursorOpen: Boolean; override;
  108.     procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
  109.     procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
  110.     procedure SetFieldData(Field: TField; Buffer: Pointer); override;
  111.   protected
  112.     { Additional overrides (optional) }
  113.     function GetRecordCount: Integer; override;
  114.     function GetRecNo: Integer; override;
  115.     procedure SetRecNo(Value: Integer); override;
  116.   public
  117.     function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
  118.   published
  119.     property FileName: string read FFileName write FFileName;
  120.     property Active;
  121.   end;
  122.  
  123. procedure Register;
  124.  
  125. implementation
  126.  
  127. uses Windows, SysUtils, Forms;
  128.  
  129. { TTextDataSet }
  130.  
  131. { This method is called by TDataSet.Open and also when FieldDefs need to
  132.   be updated (usually by the DataSet designer).  Everything which is
  133.   allocated or initialized in this method should also be freed or
  134.   uninitialized in the InternalClose method. }
  135.  
  136. procedure TTextDataSet.InternalOpen;
  137. var
  138.   I: Integer;
  139. begin
  140.   { Load the textfile into a stringlist }
  141.   FData := TStringList.Create;
  142.   FData.LoadFromFile(FileName);
  143.  
  144.   { Fabricate integral bookmark values }
  145.   for I := 1 to FData.Count do
  146.     FData.Objects[I-1] := Pointer(I);
  147.   FLastBookmark := FData.Count;
  148.  
  149.   { Initialize our internal position.
  150.     We use -1 to indicate the "crack" before the first record. }
  151.   FCurRec := -1;
  152.  
  153.   { Initialize an offset value to find the TRecInfo in each buffer }
  154.   FRecInfoOfs := MaxStrLen;
  155.  
  156.   { Calculate the size of the record buffers.
  157.     Note: This is NOT the same as the RecordSize property which
  158.     only gets the size of the data in the record buffer }
  159.   FRecBufSize := FRecInfoOfs + SizeOf(TRecInfo);
  160.  
  161.   { Tell TDataSet how big our Bookmarks are (REQUIRED) }
  162.   BookmarkSize := SizeOf(Integer);
  163.  
  164.   { Initialize the FieldDefs }
  165.   InternalInitFieldDefs;
  166.  
  167.   { Create TField components when no persistent fields have been created }
  168.   if DefaultFields then CreateFields;
  169.  
  170.   { Bind the TField components to the physical fields }
  171.   BindFields(True);
  172. end;
  173.  
  174. procedure TTextDataSet.InternalClose;
  175. begin
  176.   { Write any edits to disk and free the managing string list }
  177.   if FSaveChanges then FData.SaveToFile(FileName);
  178.   FData.Free;
  179.   FData := nil;
  180.  
  181.   { Destroy the TField components if no persistent fields }
  182.   if DefaultFields then DestroyFields;
  183.  
  184.   { Reset these internal flags }
  185.   FLastBookmark := 0;
  186.   FCurRec := -1;
  187. end;
  188.  
  189. { This property is used while opening the dataset.
  190.   It indicates if data is available even though the
  191.   current state is still dsInActive. }
  192.  
  193. function TTextDataSet.IsCursorOpen: Boolean;
  194. begin
  195.   Result := Assigned(FData);
  196. end;
  197.  
  198. { For this simple example we just create one FieldDef, but a more complete
  199.   TDataSet implementation would create multiple FieldDefs based on the
  200.   actual data. }
  201.  
  202. procedure TTextDataSet.InternalInitFieldDefs;
  203. begin
  204.   FieldDefs.Clear;
  205.   FieldDefs.Add('Line', ftString, MaxStrLen, False);
  206. end;
  207.  
  208. { This is the exception handler which is called if an exception is raised
  209.   while the component is being stream in or streamed out.  In most cases this
  210.   should be implemented useing the application exception handler as follows. }
  211.   
  212. procedure TTextDataSet.InternalHandleException;
  213. begin
  214.   Application.HandleException(Self);
  215. end;
  216.  
  217. { Bookmarks }
  218. { ========= }
  219.  
  220. { In this sample the bookmarks are stored in the Object property of the
  221.   TStringList holding the data.  Positioning to a bookmark just requires
  222.   finding the offset of the bookmark in the TStrings.Objects and using that
  223.   value as the new current record pointer. }
  224.  
  225. procedure TTextDataSet.InternalGotoBookmark(Bookmark: Pointer);
  226. var
  227.   Index: Integer;
  228. begin
  229.   Index := FData.IndexOfObject(TObject(PInteger(Bookmark)^));
  230.   if Index <> -1 then
  231.     FCurRec := Index else
  232.     DatabaseError('Bookmark not found');
  233. end;
  234.  
  235. { This function does the same thing as InternalGotoBookmark, but it takes
  236.   a record buffer as a parameter instead }
  237.  
  238. procedure TTextDataSet.InternalSetToRecord(Buffer: PChar);
  239. begin
  240.   InternalGotoBookmark(@PRecInfo(Buffer + FRecInfoOfs).Bookmark);
  241. end;
  242.  
  243. { Bookmark flags are used to indicate if a particular record is the first
  244.   or last record in the dataset.  This is necessary for "crack" handling.
  245.   If the bookmark flag is bfBOF or bfEOF then the bookmark is not actually
  246.   used; InternalFirst, or InternalLast are called instead by TDataSet. }
  247.  
  248. function TTextDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
  249. begin
  250.   Result := PRecInfo(Buffer + FRecInfoOfs).BookmarkFlag;
  251. end;
  252.  
  253. procedure TTextDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
  254. begin
  255.   PRecInfo(Buffer + FRecInfoOfs).BookmarkFlag := Value;
  256. end;
  257.  
  258. { These methods provide a way to read and write bookmark data into the
  259.   record buffer without actually repositioning the current record }
  260.  
  261. procedure TTextDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
  262. begin
  263.   PInteger(Data)^ := PRecInfo(Buffer + FRecInfoOfs).Bookmark;
  264. end;
  265.  
  266. procedure TTextDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
  267. begin
  268.   PRecInfo(Buffer + FRecInfoOfs).Bookmark := PInteger(Data)^;
  269. end;
  270.  
  271. { Record / Field Access }
  272. { ===================== }
  273.  
  274. { This method returns the size of just the data in the record buffer.
  275.   Do not confuse this with RecBufSize which also includes any additonal
  276.   structures stored in the record buffer (such as TRecInfo). }
  277.  
  278. function TTextDataSet.GetRecordSize: Word;
  279. begin
  280.   Result := MaxStrLen;
  281. end;
  282.  
  283. { TDataSet calls this method to allocate the record buffer.  Here we use
  284.   FRecBufSize which is equal to the size of the data plus the size of the
  285.   TRecInfo structure. }
  286.  
  287. function TTextDataSet.AllocRecordBuffer: PChar;
  288. begin
  289.   GetMem(Result, FRecBufSize);
  290. end;
  291.  
  292. { Again, TDataSet calls this method to free the record buffer.
  293.   Note: Make sure the value of FRecBufSize does not change before all
  294.   allocated buffers are freed. }
  295.  
  296. procedure TTextDataSet.FreeRecordBuffer(var Buffer: PChar);
  297. begin
  298.   FreeMem(Buffer, FRecBufSize);
  299. end;
  300.  
  301. { This multi-purpose function does 3 jobs.  It retrieves data for either
  302.   the current, the prior, or the next record.  It must return the status
  303.   (TGetResult), and raise an exception if DoCheck is True. }
  304.  
  305. function TTextDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
  306.   DoCheck: Boolean): TGetResult;
  307. begin
  308.   if FData.Count < 1 then
  309.     Result := grEOF else
  310.   begin
  311.     Result := grOK;
  312.     case GetMode of
  313.       gmNext:
  314.         if FCurRec >= RecordCount - 1  then
  315.           Result := grEOF else
  316.           Inc(FCurRec);
  317.       gmPrior:
  318.         if FCurRec <= 0 then
  319.           Result := grBOF else
  320.           Dec(FCurRec);
  321.       gmCurrent:
  322.         if (FCurRec < 0) or (FCurRec >= RecordCount) then
  323.           Result := grError;
  324.     end;
  325.     if Result = grOK then
  326.     begin
  327.       StrLCopy(Buffer, PChar(FData[FCurRec]), MaxStrLen);
  328.       with PRecInfo(Buffer + FRecInfoOfs)^ do
  329.       begin
  330.         BookmarkFlag := bfCurrent;
  331.         Bookmark := Integer(FData.Objects[FCurRec]);
  332.       end;
  333.     end else
  334.       if (Result = grError) and DoCheck then DatabaseError('No Records');
  335.   end;
  336. end;
  337.  
  338. { This routine is called to initialize a record buffer.  In this sample,
  339.   we fill the buffer with zero values, but we might have code to initialize
  340.   default values or do other things as well. }
  341.  
  342. procedure TTextDataSet.InternalInitRecord(Buffer: PChar);
  343. begin
  344.   FillChar(Buffer^, RecordSize, 0);
  345. end;
  346.  
  347. { Here we copy the data from the record buffer into a field's buffer.
  348.   This function, and SetFieldData, are more complex when supporting
  349.   calculated fields, filters, and other more advanced features.
  350.   See TBDEDataSet for a more complete example. }
  351.  
  352. function TTextDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
  353. begin
  354.   StrLCopy(Buffer, ActiveBuffer, Field.Size);
  355.   Result := PChar(Buffer)^ <> #0;
  356. end;
  357.  
  358. procedure TTextDataSet.SetFieldData(Field: TField; Buffer: Pointer);
  359. begin
  360.   StrLCopy(ActiveBuffer, Buffer, Field.Size);
  361.   DataEvent(deFieldChange, Longint(Field));
  362. end;
  363.  
  364. { Record Navigation / Editing }
  365. { =========================== }
  366.  
  367. { This method is called by TDataSet.First.  Crack behavior is required.
  368.   That is we must position to a special place *before* the first record.
  369.   Otherwise, we will actually end up on the second record after Resync
  370.   is called. }
  371.   
  372. procedure TTextDataSet.InternalFirst;
  373. begin
  374.   FCurRec := -1;
  375. end;
  376.  
  377. { Again, we position to the crack *after* the last record here. }
  378.  
  379. procedure TTextDataSet.InternalLast;
  380. begin
  381.   FCurRec := FData.Count;
  382. end;
  383.  
  384. { This method is called by TDataSet.Post.  Most implmentations would write
  385.   the changes directly to the associated datasource, but here we simply set
  386.   a flag to write the changes when we close the dateset. }
  387.  
  388. procedure TTextDataSet.InternalPost;
  389. begin
  390.   FSaveChanges := True;
  391.   { For inserts, just update the data in the string list }
  392.   if State = dsEdit then FData[FCurRec] := ActiveBuffer else
  393.   begin
  394.     { If inserting (or appending), increment the bookmark counter and
  395.       store the data }
  396.     Inc(FLastBookmark);
  397.     FData.InsertObject(FCurRec, ActiveBuffer, Pointer(FLastBookmark));
  398.   end;
  399. end;
  400.  
  401. { This method is similar to InternalPost above, but the operation is always
  402.   an insert or append and takes a pointer to a record buffer as well. }
  403.  
  404. procedure TTextDataSet.InternalAddRecord(Buffer: Pointer; Append: Boolean);
  405. begin
  406.   FSaveChanges := True;
  407.   Inc(FLastBookmark);
  408.   if Append then InternalLast;
  409.   FData.InsertObject(FCurRec, PChar(Buffer), Pointer(FLastBookmark));
  410. end;
  411.  
  412. { This method is called by TDataSet.Delete to delete the current record }
  413.  
  414. procedure TTextDataSet.InternalDelete;
  415. begin
  416.   FSaveChanges := True;
  417.   FData.Delete(FCurRec);
  418.   if FCurRec >= FData.Count then
  419.     Dec(FCurRec);
  420. end;
  421.  
  422. { Optional Methods }
  423. { ================ }
  424.  
  425. { The following methods are optional.  When provided they will allow the
  426.   DBGrid and other data aware controls to track the current cursor postion
  427.   relative to the number of records in the dataset.  Because we are dealing
  428.   with a small, static data store (a stringlist), these are very easy to
  429.   implement.  However, for many data sources (SQL servers), the concept of
  430.   record numbers and record counts do not really apply. }
  431.  
  432. function TTextDataSet.GetRecordCount: Longint;
  433. begin
  434.   Result := FData.Count;
  435. end;
  436.  
  437. function TTextDataSet.GetRecNo: Longint;
  438. begin
  439.   UpdateCursorPos;
  440.   if (FCurRec = -1) and (RecordCount > 0) then
  441.     Result := 1 else
  442.     Result := FCurRec + 1;
  443. end;
  444.  
  445. procedure TTextDataSet.SetRecNo(Value: Integer);
  446. begin
  447.   if (Value >= 0) and (Value < FData.Count) then
  448.   begin
  449.     FCurRec := Value - 1;
  450.     Resync([]);
  451.   end;
  452. end;
  453.  
  454. { This procedure is used to register this component on the component palette }
  455.  
  456. procedure Register;
  457. begin
  458.   RegisterComponents('Data Access', [TTextDataSet]);
  459. end;
  460.  
  461. end.
  462.