一、概述 在用Delphi編寫數(shù)據(jù)庫程序時(shí),經(jīng)常涉及到數(shù)據(jù)的導(dǎo)入和導(dǎo)出操作,如:將大型數(shù)據(jù)庫中的數(shù)據(jù)存儲(chǔ)為便攜文件,以便于出外閱讀;將存儲(chǔ)在文件中的數(shù)據(jù)信息,導(dǎo)入到另外的數(shù)據(jù)庫中;而且,通過將數(shù)據(jù)庫中的數(shù)據(jù)存儲(chǔ)為數(shù)據(jù)文件,更便于程序內(nèi)部和程序間交換數(shù)據(jù),避免通過內(nèi)存交換數(shù)據(jù)的煩瑣步驟,例如在筆者編寫的通用報(bào)表程序中即以該控件作為數(shù)據(jù)信息傳遞的載體。 二、基本思路 作為數(shù)據(jù)報(bào)存儲(chǔ)控件,應(yīng)能夠存儲(chǔ)和讀入數(shù)據(jù)集的基本信息(如:字段名,字段的顯示名稱,字段的數(shù)據(jù)類型,記錄數(shù),字段數(shù),指定記錄指定字段的當(dāng)前值等),應(yīng)能夠提供較好的封裝特性,以便于使用。 基于此,筆者利用Delphi5.0面向?qū)ο蟮奶攸c(diǎn),設(shè)計(jì)開發(fā)了數(shù)據(jù)報(bào)存儲(chǔ)控件。 三、實(shí)現(xiàn)方法 編寫如下代碼單元: unit IbDbFile; interface Uses Windows, SysUtils, Classes, Forms, Db, DbTables, Dialogs; Const Flag = '數(shù)據(jù)報(bào)-吉星軟件工作室'; Type TDsException = Class(Exception); TIbStorage = class(TComponent) private FRptTitle: string; //存儲(chǔ)數(shù)據(jù)報(bào)說明 FPageHead: string; //頁頭說明 FPageFoot: string; //爺腳說明 FFieldNames: TStrings; //字段名表 FStreamIndex: TStrings; //字段索引 FStream: TStream; //存儲(chǔ)字段內(nèi)容的流 FFieldCount: Integer; //字段數(shù) FRecordCount: Integer; //記錄數(shù) FOpenFlag: Boolean; //流是否創(chuàng)建標(biāo)志 protected procedure Reset; //復(fù)位---清空流的內(nèi)容 procedure SaveHead(ADataSet: TDataSet; Fp: TStream); //存儲(chǔ)報(bào)表頭信息 procedure LoadTableToStream(ADataSet: TDataSet); //存儲(chǔ)記錄數(shù)據(jù) procedure IndexFields(ADataSet: TDataSet); //將數(shù)據(jù)集的字段名保存到列表中 procedure GetHead(Fp: TFileStream); //保存報(bào)表頭信息 procedure GetIndex(Fp: TFileStream); //建立記錄流索引 procedure GetFieldNames(Fp: TFileStream); //從流中讀入字段名表 function GetFieldName(AIndex: Integer): string; //取得字段名稱 function GetFieldDataType(AIndex: Integer): TFieldType; function GetDisplayLabel(AIndex: Integer): string; //取得字段顯示名稱 procedure SaveFieldToStream(AStream: TStream; AField: TField); //將字段存入流中 function GetFieldValue(ARecordNo, FieldNo: Integer): string; //字段的內(nèi)容 public Constructor Create(AOwner: TComponent); Destructor Destroy; override; procedure Open; //創(chuàng)建流以準(zhǔn)備存儲(chǔ)數(shù)據(jù) procedure SaveToFile(ADataSet: TDataSet; AFileName: string); //存儲(chǔ)方法 procedure LoadFromFile(AFileName: string); //裝入數(shù)據(jù) procedure FieldStream(ARecordNo, FieldNo: Integer; var AStream: TStream); property FieldNames[Index: Integer]: string read GetFieldName; //字段名 property FieldDataTypes[Index: Integer]: TFieldType read GetFieldDataType; property FieldDisplayLabels[Index: Integer]: string read GetDisplayLabel; property Fields[RecNo, FieldIndex: Integer]: string read GetFieldValue; //property FieldStreams[RecNo, FieldIndex: Integer]: TStream read GetFieldStream; property RecordCount: Integer read FRecordCount write FRecordCount; property FieldCount: Integer read FFieldCount write FFieldCount; published property RptTitle: string read FRptTitle write FRptTitle; property PageHead: string read FPageHead write FPageHead; property PageFoot: string read FPageFoot write FPageFoot; end;
function ReadAChar(AStream: TStream): Char; function ReadAStr(AStream: TStream): string; function ReadBStr(AStream: TStream; Size: Integer): string; function ReadAInteger(AStream: TStream): Integer; procedure WriteAStr(AStream: TStream; AStr: string); procedure WriteBStr(AStream: TStream; AStr: string); procedure WriteAInteger(AStream: TStream; AInteger: Integer);
procedure Register; implementation
procedure Register; begin RegisterComponents('Data Access', [TIbStorage]); end;
function ReadAChar(AStream: TStream): Char; Var AChar: Char; begin AStream.Read(AChar, 1); Result := AChar; end;
function ReadAStr(AStream: TStream): string; var Str: String; C : Char; begin Str := ''; C := ReadAChar(AStream); While C <> #0 do begin Str := Str + C; C := ReadAChar(AStream); end; Result := Str; end;
function ReadBStr(AStream: TStream; Size: Integer): string; var Str: String; C : Char; I : Integer; begin Str := ''; For I := 1 to Size do begin C := ReadAChar(AStream); Str := Str + C; end; Result := Str; end;
function ReadAInteger(AStream: TStream): Integer; var Str: String; C : Char; begin Result := MaxInt; Str := ''; C := ReadAChar(AStream); While C <> #0 do begin Str := Str + C; C := ReadAChar(AStream); end; try Result := StrToInt(Str); except Application.MessageBox(' 當(dāng)前字符串無法轉(zhuǎn)換為整數(shù)!', '錯(cuò)誤', Mb_Ok + Mb_IconError); end; end;
procedure WriteAStr(AStream: TStream; AStr: string); begin AStream.Write(Pointer(AStr)^, Length(AStr) + 1); end;
procedure WriteBStr(AStream: TStream; AStr: string); begin AStream.Write(Pointer(AStr)^, Length(AStr)); end;
procedure WriteAInteger(AStream: TStream; AInteger: Integer); var S : string; begin S := IntToStr(AInteger); WriteAstr(AStream, S); end;
Constructor TIbStorage.Create(AOwner: TComponent); begin inherited Create(AOwner); FOpenFlag := False; //確定流是否創(chuàng)建的標(biāo)志 end;
Destructor TIbStorage.Destroy; begin if FOpenFlag then begin FStream.Free; FStreamIndex.Free; FFieldNames.Free; end; inherited Destroy; end;
procedure TIbStorage.Open; begin FOpenFlag := True; FStream := TMemoryStream.Create; FStreamIndex := TStringList.Create; FFieldNames := TStringList.Create; Reset; end;
procedure TIbStorage.Reset; //復(fù)位 begin if FOpenFlag then begin FFieldNames.Clear; FStreamIndex.Clear; FStream.Size := 0; FRptTitle := ''; FPageHead := ''; FPageFoot := ''; FFieldCount := 0; FRecordCount := 0; end; end;
//-------保存數(shù)據(jù)部分 procedure TIbStorage.SaveToFile(ADataSet: TDataSet; AFileName: string); var Fp: TFileStream; I : Integer; Ch: Char; T1, T2: TDateTime; Str: string; begin if Not FOpenFlag then begin showmessage(' 對(duì)象沒有打開'); Exit; end; try if FileExists(AFileName) then DeleteFile(AFileName); Fp := TFileStream.Create(AFileName, fmCreate); Reset; SaveHead(ADataSet, Fp); //保存頭部信息---附加說明 IndexFields(ADataSet); //將數(shù)據(jù)集的字段信息保存到FFieldName LoadTableToStream(ADataSet); //保存數(shù)據(jù)集的數(shù)據(jù)信息 WriteAStr(Fp, FFieldNames.Text); //存儲(chǔ)字段名信息 Ch := '@'; Fp.Write(Ch, 1); WriteAStr(Fp, FStreamIndex.Text); //存儲(chǔ)字段索引列表 Ch := '@'; Fp.Write(Ch, 1); Fp.CopyFrom(FStream, 0); finally Fp.Free; end; end;
procedure TIbStorage.SaveHead(ADataSet: TDataSet; Fp: TStream); Var I : Integer; Ch: Char; begin if Not ADataSet.Active then ADataSet.Active := True; WriteAStr(Fp, Flag); WriteAStr(Fp, FRptTitle); WriteAStr(Fp, FPageHead); WriteAStr(Fp, FPageFoot); FFieldCount := ADataSet.Fields.Count; FRecordCount := ADataSet.RecordCount; WriteAStr(Fp, IntToStr(ADataSet.Fields.Count)); WriteAStr(Fp, IntToStr(ADataSet.RecordCount)); Ch := '@'; Fp.Write(Ch, 1); end;
procedure TIbStorage.IndexFields(ADataSet: TDataSet); var I : Integer; AField: TField; begin For I := 0 to ADataSet.Fields.Count - 1 do begin AField := ADataSet.Fields[I]; //不用FFieldNames.Values[AField.FieldName] := AField.DisplayLabel;是考慮效率 FFieldNames.Add(AField.FieldName + '=' + AField.DisplayLabel); FFieldNames.Add(AField.FieldName + 'DataType=' + IntToStr(Ord(AField.DataType))); end; end;
procedure TIbStorage.LoadTableToStream(ADataSet: TDataSet); var No: Integer; I, J, Size: Integer; Tmp, Id, Str : string; //id=string(RecNO) + string(FieldNo) Len: Integer; Ch : Char; BlobStream: TBlobStream; begin if Not FOpenFlag then begin showmessage(' 對(duì)象沒有打開'); Exit; end; try ADataSet.DisableControls; ADataSet.First; No := 0; FStreamIndex.Clear; FStream.Size := 0; While Not ADataSet.Eof do begin No := No + 1; For J := 0 to ADataSet.Fields.Count - 1 do begin Id := Inttostr(NO) + '_' + IntToStr(J); //建立流的位置的索引, 索引指向: Size#0Content FStreamIndex.Add(Id + '=' + IntToStr(FStream.Position)); //存儲(chǔ)字段信息到流中 SaveFieldToStream(FStream, ADataSet.Fields[J]); end; ADataSet.Next; end; finally ADataSet.EnableControls; end; end;
//如果一個(gè)字段的當(dāng)前內(nèi)容為空或者BlobSize<=0,則只寫入字段大小為0, 不寫入內(nèi)容 procedure TIbStorage.SaveFieldToStream(AStream: TStream; AField: TField); var Size: Integer; Ch: Char; XF: TStream; Str: string; begin if AField.IsBlob then begin //如何把一個(gè)TBlobField字段的內(nèi)容存儲(chǔ)為流 Xf := TBlobStream.Create(TBlobField(AField), bmread); try if Xf.Size > 0 then begin Size := Xf.Size; WriteAInteger(AStream, Size); AStream.CopyFrom(Xf, Xf.Size); end else WriteAInteger(AStream, 0); finally XF.Free; end; end else begin Str := AField.AsString; Size := Length(Str); WriteAInteger(AStream, Size); if Size <> 0 then AStream.Write(Pointer(Str)^, Size); //WriteAstr(AStream, Str); end; Ch := '@'; AStream.Write(Ch, 1); end;
//------------Load Data procedure TIbStorage.LoadFromFile(AFileName: string); var Fp: TFileStream; Check: string; begin Reset; try if Not FileExists(AFileName) then begin showmessage(' 文件不存在:' + AFileName); Exit; end; Fp := TFileStream.Create(AFileName, fmOpenRead); Check := ReadAStr(Fp); if Check <> Flag then begin Application.MessageBox(' 非法文件格式', '錯(cuò)誤', Mb_Ok + Mb_IconError); Exit; end; GetHead(Fp); GetFieldNames(Fp); GetIndex(Fp); FStream.CopyFrom(Fp, Fp.Size-Fp.Position); finally Fp.Free; end; end;
procedure TIbStorage.GetHead(Fp: TFileStream); begin FRptTitle := ReadAStr(Fp); FPageHead := ReadAstr(Fp); FPageFoot := ReadAstr(Fp); FFieldCount := ReadAInteger(Fp); FRecordCount := ReadAInteger(Fp); if ReadAChar(Fp) <> '@' then showmessage('GetHead File Error'); end;
procedure TIbStorage.GetFieldNames(Fp: TFileStream); var Ch: Char; Str: string; begin Str := ''; Str := ReadAStr(Fp); FFieldNames.CommaText := Str; Ch := ReadAChar(Fp); if Ch <> '@' then Showmessage('When get fieldnames Error'); end;
procedure TIbStorage.GetIndex(Fp: TFileStream); var Ch: Char; Str: string; begin Str := ''; Str := ReadAStr(Fp); FStreamIndex.CommaText := Str; Ch := ReadAChar(Fp); if Ch <> '@' then Showmessage('When Get Field Position Index Error'); end;
//---------Read Field's Value Part function TIbStorage.GetFieldValue(ARecordNo, FieldNo: Integer): string; var Id, T : string; Pos: Integer; Len, I : Integer; Er: Boolean; begin Result := ''; Er := False; if ARecordNo > FRecordCount then Er := true; //ARecordNo := FRecordCount; if ARecordNo < 1 then Er := True; // ARecordNo := 1; if FieldNo >= FFieldCount then Er := True; // FieldNo := FFieldCount - 1; if FieldNo < 0 then Er := True; //FieldNo := 0; if Er then begin Showmessage('記錄號(hào)或者字段標(biāo)號(hào)越界'); Exit; end; if FFieldCount = 0 then Exit; Id := Inttostr(ARecordNO) + '_' + IntToStr(FieldNo); Pos := StrToInt(FStreamIndex.Values[Id]); FStream.Position := Pos; //取得字段內(nèi)容的長(zhǎng)度 Len := ReadAInteger(FStream); if Len > 0 then Result := ReadBStr(FStream, Len); if ReadAChar(FStream) <> '@' then Showmessage('When Read Field, Find Save Format Error'); end;
procedure TIbStorage.FieldStream(ARecordNo, FieldNo: Integer; var AStream: TStream); var Id, T : string; Pos: Integer; Len, I : Integer; Er: Boolean; begin Er := False; if ARecordNo > FRecordCount then Er := true; //ARecordNo := FRecordCount; if ARecordNo < 1 then Er := True; // ARecordNo := 1; if FieldNo >= FFieldCount then Er := True; // FieldNo := FFieldCount - 1; if FieldNo < 0 then Er := True; //FieldNo := 0; if Er then begin TDsException.Create('GetFieldValue函數(shù)索引下標(biāo)越界'); Exit; end; if FFieldCount = 0 then Exit; Id := Inttostr(ARecordNO) + IntToStr(FieldNo); Pos := StrToInt(FStreamIndex.Values[Id]); FStream.Position := Pos; Len := ReadAInteger(FStream); AStream.CopyFrom(FStream, Len); end;
function TIbStorage.GetFieldName(AIndex: Integer): string; //取得字段名稱 begin //存儲(chǔ)的字段和數(shù)據(jù)類型各占一半 if ((AIndex < 0) or (AIndex >= FFieldNames.Count div 2)) then Application.MessageBox(' 取字段名索引越界', '程序 錯(cuò)誤', Mb_Ok + Mb_IconError) else Result := FFieldNames.Names[AIndex*2]; end;
function TIbStorage.GetFieldDataType(AIndex: Integer): TFieldType; //取得字段名稱 begin //存儲(chǔ)的字段和數(shù)據(jù)類型各占一半 if ((AIndex < 0) or (AIndex >= FFieldNames.Count div 2)) then Application.MessageBox(' 取字段數(shù)據(jù)類型索引越界', '程序 錯(cuò)誤', Mb_Ok + Mb_IconError) else Result := TFieldType(StrToInt(FFieldNames.Values[FFieldNames.Names[AIndex*2+1]])); end;
function TIbStorage.GetDisplayLabel(AIndex: Integer): string; //取得字段顯示名稱 begin if ((AIndex < 0) or (AIndex >= FFieldNames.Count)) then Application.MessageBox(' 取字段名索引越界', '程序 錯(cuò)誤', Mb_Ok + Mb_IconError) else Result := FFieldNames.Values[GetFieldName(AIndex)]; end;
end. 通過測(cè)試,該控件對(duì)Ttable,Tquery, TaodTable, TadoQuery, TibTable, TibQuery等常用的數(shù)據(jù)集控件等都能較好的支持,并且具有較好的效率(測(cè)試:1100條人事記錄,23個(gè)字段存儲(chǔ)為文件約用時(shí)2秒鐘)。
四、控件的基本使用方法 1.存儲(chǔ)數(shù)據(jù)集中的數(shù)據(jù)到文件 IbStorage1.Open; //創(chuàng)建存儲(chǔ)流 IbStorage1.SaveToFile(AdataSet, Afilename); 2.從文件中讀出數(shù)據(jù)信息 IbStorage1.Open; IbStorage1.LoadFromFile(AfileName); 3.對(duì)數(shù)據(jù)報(bào)存儲(chǔ)控件中數(shù)據(jù)的訪問 Value := IbStorage1.Fields[ArecNo, AfieldNo]; //字符串類型 其它略。 五、結(jié)束語 通過編寫此數(shù)據(jù)報(bào)存儲(chǔ)控件,較好地解決了數(shù)據(jù)庫程序中數(shù)據(jù)的存儲(chǔ)和交換問題,為數(shù)據(jù)庫程序的開發(fā)提供了一種實(shí)用的控件。 該控件在Windows98,Delphi5開發(fā)環(huán)境下調(diào)試通過。
|