Recent

Author Topic: TStringsDataset, help needed  (Read 8209 times)

jc99

  • Hero Member
  • *****
  • Posts: 553
    • My private Site
TStringsDataset, help needed
« on: July 08, 2015, 01:08:05 am »
This is something for the DB-Gurus:
I am working on a TDataset -Descendent that stores the information in a TStrings-Component.
at the moment, it looks like this:
Code: [Select]
UNIT Cmp_TStringsDataSet;

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

INTERFACE

USES
{$IFnDEF FPC}
  DSIntf,
{$ELSE}
{$ENDIF}
  SysUtils, Classes, DB;

TYPE

 { TStringsDataSet }

 TStringsDataSet = CLASS(TDataSet)
  Private
    { Private-Deklarationen }
    FStrings: TStrings;
    Fopened: Boolean;

  Protected
    { Protected-Deklarationen }
    function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult; Override;
    procedure InternalFirst; override;
    PROCEDURE InternalClose; Override;
    PROCEDURE InternalHandleException; Override;
    PROCEDURE InternalInitFieldDefs; Override;
    PROCEDURE InternalOpen; Override;
    FUNCTION IsCursorOpen: Boolean; Override;
    function GetFieldData(Field: TField; Buffer: Pointer): Boolean; Override;
    procedure SetFieldData(Field: TField; Buffer: Pointer); Override;

    // Optional.
    function GetRecordCount: Integer; override;

  Public
    { Public-Deklarationen }
  Published
    { Published-Deklarationen }
    PROPERTY Source: TStrings Read FStrings Write FStrings;
  END;

PROCEDURE Register;

IMPLEMENTATION

USES variants;

PROCEDURE Register;
BEGIN
  RegisterComponents('Data Access', [TStringsDataSet]);
END;

function TStringsDataSet.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
  DoCheck: Boolean): TGetResult;
BEGIN
   //
  result := grEOF;
END;

procedure TStringsDataSet.InternalFirst;
begin
  //
end;

procedure TStringsDataSet.InternalClose;
BEGIN
  //
END;

procedure TStringsDataSet.InternalHandleException;
BEGIN
  //
END;

procedure TStringsDataSet.InternalInitFieldDefs;
VAR
  I: Integer;
  Fieldnm: ansistring;
  Val: Variant;
  f: TField;
  n: integer;
  r: Extended;
  FieldID: Integer;
  {$IFNDEF FPC}
  FldDesc: DSFLDDesc;
  {$ENDIF}
  iFldType: TFieldType;
  iFldSize: Integer;


BEGIN
  //
  FieldDefs.Clear;
  FOR I := 0 TO Fstrings.Count - 1 DO
    try
      Fieldnm := fstrings.Names[i];
      val := FStrings.ValueFromIndex[i];
//      WITH FldDesc DO
        try
        //  strcopy(pansiChar(@szName) , @fieldnm[1]);
          IF (lowercase(val) = 'true') OR (Lowercase(val) = 'false') THEN
            BEGIN
          // Boolean field;

              iFldType := ftBoolean;
              iFldSize := 0;
             // iFldSubType := fldstBINARY;
            END
          ELSE IF TryStrToInt(val, n) THEN
          begin
              iFldType := ftInteger;
              iFldSize := 0;
             // iFldSubType := fldstBINARY;
            END
          ELSE IF TryStrToFloat(val, r) THEN
            BEGIN
              iFldType := ftFloat ;
              iFldSize := 0;
             // iFldSubType := fldstBINARY;
            END
          ELSE
            BEGIN
              iFldType:= ftString;
              iFldSize := 100;
            END;
        finally
        END;
      {$IFNDEF FPC}
      AddFieldDesc(fldDesc, i,Fielddefs);
      {$ELSE}
      TFieldDef.Create(FieldDefs,Fieldnm,iFldType,iFldSize,false,I+1);
      {$ENDIF}
    finally
    END;
  Fopened := true;
END;

procedure TStringsDataSet.InternalOpen;

BEGIN
  //
  internalinitFielddefs;
  CreateFields;
END;

function TStringsDataSet.IsCursorOpen: Boolean;
BEGIN
  //
  result := Fopened;
END;

function TStringsDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
  I: Integer;
  aStr:string;
begin
  I:= Field.FieldNo - 1;
  if assigned(Buffer) then // [EDIT2]
   begin
  case Field.DataType of
    ftBoolean:wordbool(Buffer^):=StrToBool(FStrings.Values[Field.FieldName]);
    ftinteger:LongInt(Buffer^):=25; // Todo:
    ftFloat:double(Buffer^):=pi;  // Todo:
    ftString:begin
      aStr :=  FStrings.Values[Field.FieldName];
      move(aStr[1],Buffer^,length(aStr)+1);
    end;
  end;
  result := true; // <-------
  end;
end;

procedure TStringsDataSet.SetFieldData(Field: TField; Buffer: Pointer);
begin
//  FStrings.Values[Field.FieldName]:=Field.value;

  inherited SetFieldData(Field, Buffer);
  if assigned(Buffer) then
  case Field.DataType of
    ftBoolean:FStrings.Values[Field.FieldName]:=BoolToStr( wordbool(Buffer^),true);
// [EDIT]:
    ftFloat: FStrings.Values[Field.FieldName]:=FloatToStr( double(Buffer^));
    ftInteger: FStrings.Values[Field.FieldName]:=IntToStr( integer(Buffer^));
    ftstring:FStrings.Values[Field.FieldName]:= PChar( Buffer);
  end;
end;

function TStringsDataSet.GetRecordCount: Integer;
begin
  Result:=inherited GetRecordCount;
end;

END.
It Works, that i can enter text, and set the Boolean , Both are written in the TString ,
But nothing comes out.
Here I need help.
I think the problem lies in the
TStringsDataSet.GetFieldData - Function
Any help is welcome.
« Last Edit: July 08, 2015, 08:27:31 am by jc99 »
OS: Win XP x64, Win 7, Win 7 x64, Win 10, Win 10 x64, Suse Linux 13.2
Laz: 1.4 - 1.8.4, 2.0
https://github.com/joecare99/public
'~|    /''
,_|oe \_,are
If you want to do something for the environment: Twitter: #reduceCO2 or
https://www.betterplace.me/klimawandel-stoppen-co-ueber-preis-reduzieren

LacaK

  • Hero Member
  • *****
  • Posts: 664
Re: TStringsDataset, help needed
« Reply #1 on: July 08, 2015, 07:24:58 am »
At least TStringsDataSet.GetRecord seems suspicious. It always returns grEof, so it signals to TDataSet that there are no records in underlaying "data storage".

jc99

  • Hero Member
  • *****
  • Posts: 553
    • My private Site
Re: TStringsDataset, help needed
« Reply #2 on: July 08, 2015, 08:40:23 am »
At least TStringsDataSet.GetRecord seems suspicious. It always returns grEof, so it signals to TDataSet that there are no records in underlaying "data storage".
Thanks LacaK,
I'm going to look into this, as I understand it grEof has to be send with the last record, My dataset has only one therefor i sent it all the time. (But that's maybe not correct)
I found another mistake:
  the result of  GetFieldData  was not set, with it (See [Edit2])
  it starts to work:

BTW should I update the code in the first post, or make a new one/a reply ?
« Last Edit: July 08, 2015, 08:52:19 am by jc99 »
OS: Win XP x64, Win 7, Win 7 x64, Win 10, Win 10 x64, Suse Linux 13.2
Laz: 1.4 - 1.8.4, 2.0
https://github.com/joecare99/public
'~|    /''
,_|oe \_,are
If you want to do something for the environment: Twitter: #reduceCO2 or
https://www.betterplace.me/klimawandel-stoppen-co-ueber-preis-reduzieren

rvk

  • Hero Member
  • *****
  • Posts: 4948
Re: TStringsDataset, help needed
« Reply #3 on: July 08, 2015, 09:34:43 am »
I'm going to look into this, as I understand it grEof has to be send with the last record, My dataset has only one therefor i sent it all the time. (But that's maybe not correct)
Looking at a normal loop you'll see that's not true:
Code: [Select]
dataset.First;
while dataset.Eof do
begin
  // do something
  dataset.Next;
end;
If with First the Eof was set you wouldn't enter the loop.
And if with the last record (after next) the Eof was set you wouldn't process the last record.

I do understand the confusion.
According to this: http://lazarus-ccr.sourceforge.net/docs/fcl/db/tdataset.eof.html
Quote
EOF is True if the cursor is on the last record in the dataset, and no more records are available
But in the cases after it it says:
Quote
2. The record is on the last record, and the TDataset.Next method is called.
and also:
Quote
It is only when the cursor is on the last record and Next is called, that EOF will become True. This means that the following loop will stop after the last record was visited

Edit: I think normally you would post new code in a new post. Otherwise the discussion (and pointers) you get would mean nothing when reading the code from the first post. If I wanted to look at the old code where you set grEof I wouldn't find it anymore so I couldn't see what you did "wrong".
« Last Edit: July 08, 2015, 09:38:03 am by rvk »

jc99

  • Hero Member
  • *****
  • Posts: 553
    • My private Site
Re: TStringsDataset, help needed
« Reply #4 on: July 08, 2015, 11:10:23 am »
Thanks rvk,
I'll try to implement this, as soon as I have time, I'm on a buissnes/Service tripp abroad. Any code-suggestions are welcome.
OS: Win XP x64, Win 7, Win 7 x64, Win 10, Win 10 x64, Suse Linux 13.2
Laz: 1.4 - 1.8.4, 2.0
https://github.com/joecare99/public
'~|    /''
,_|oe \_,are
If you want to do something for the environment: Twitter: #reduceCO2 or
https://www.betterplace.me/klimawandel-stoppen-co-ueber-preis-reduzieren

rvk

  • Hero Member
  • *****
  • Posts: 4948
Re: TStringsDataset, help needed
« Reply #5 on: July 08, 2015, 12:57:10 pm »
I was just about to try using a TDataset-like descendant for accessing Google calendar and GMail after creating a OAuth2.0 library for the Google apis (for .Next/Prior etc) (see here).

What I did notice about your code is that you have GetFieldData and SetFieldData in the protected section of your descendant. I think you should have gotten an compiler warning saying that the visibility of those method is lower than in TDataSet (which is in public).
Code: [Select]
Note: Virtual method "GetFieldData(TField;Pointer):Boolean;" has a lower visibility (protected) than parent class TDataSet (public)
Note: Virtual method "SetFieldData(TField;Pointer);" has a lower visibility (protected) than parent class TDataSet (public)

That would mean that your GetFieldData and SetFieldData are never called. Move them to public and see if it works better.

taazz

  • Hero Member
  • *****
  • Posts: 5364
Re: TStringsDataset, help needed
« Reply #6 on: July 08, 2015, 01:45:34 pm »
That would mean that your GetFieldData and SetFieldData are never called. Move them to public and see if it works better.
Erm no it doesn't. Lowering visibility is not supported as far as I know even if it was supported how do you think the protected virtual method work in the first place? The warning you see is a simple "FYI the visibility is not what you defined here" message and nothing else.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

rvk

  • Hero Member
  • *****
  • Posts: 4948
Re: TStringsDataset, help needed
« Reply #7 on: July 08, 2015, 01:57:20 pm »
That would mean that your GetFieldData and SetFieldData are never called. Move them to public and see if it works better.
Erm no it doesn't. Lowering visibility is not supported as far as I know even if it was supported how do you think the protected virtual method work in the first place? The warning you see is a simple "FYI the visibility is not what you defined here" message and nothing else.
Woops... my bad ("lack of knowledge"  :-[)

I was under the impression the lower visibility would also mean the original public method of the base-class would still be called (and not the newer protected method). I was wrong.
 
Quote
In practice this is never harmful, but it can be confusing to someone reading documentation and observing the visibility attributes of the document.
(Source)

jc99

  • Hero Member
  • *****
  • Posts: 553
    • My private Site
Re: TStringsDataset, help needed
« Reply #8 on: July 08, 2015, 09:42:17 pm »
I was just about to try using a TDataset-like descendant for accessing Google calendar and GMail after creating a OAuth2.0 library for the Google apis (for .Next/Prior etc) (see here).

What I did notice about your code is that you have GetFieldData and SetFieldData in the protected section of your descendant. I think you should have gotten an compiler warning saying that the visibility of those method is lower than in TDataSet (which is in public).
Code: [Select]
Note: Virtual method "GetFieldData(TField;Pointer):Boolean;" has a lower visibility (protected) than parent class TDataSet (public)
Note: Virtual method "SetFieldData(TField;Pointer);" has a lower visibility (protected) than parent class TDataSet (public)

That would mean that your GetFieldData and SetFieldData are never called. Move them to public and see if it works better.
I used TMemDataset as a Template,
And in memds.pas (line 85 ,91 & 109 ) you see that they also lower the visibility of these Methods.
however in  TCustomBufDataset (BufDataset Line 512, 516 ff ) these methods are public.
OS: Win XP x64, Win 7, Win 7 x64, Win 10, Win 10 x64, Suse Linux 13.2
Laz: 1.4 - 1.8.4, 2.0
https://github.com/joecare99/public
'~|    /''
,_|oe \_,are
If you want to do something for the environment: Twitter: #reduceCO2 or
https://www.betterplace.me/klimawandel-stoppen-co-ueber-preis-reduzieren

rvk

  • Hero Member
  • *****
  • Posts: 4948
Re: TStringsDataset, help needed
« Reply #9 on: July 08, 2015, 09:52:18 pm »
Do you also have some small test-program.

I tried your source with the following and it worked.
Code: [Select]
a=test
b=ok
became
Code: [Select]
a=test
b=ccccc

Code: [Select]
procedure TForm1.Button1Click(Sender: TObject);
begin
    ds.Edit;
    ds.FieldByName('b').asString := 'ccccc';
    ds.Post;
    Showmessage(ds.Source.Text);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  sl: TStringList;
begin
  sl := TStringList.Create;
  sl.Add('a=test');
  sl.Add('b=ok');
  ds := TStringsDataSet.Create(Self);
  ds.Source := sl;
  ds.Open;
  DataSource1.DataSet := ds;
end;

What exactly is going wrong at your end?

jc99

  • Hero Member
  • *****
  • Posts: 553
    • My private Site
Re: TStringsDataset, help needed
« Reply #10 on: July 09, 2015, 07:32:51 am »
First I needed the Ds to update when the SL is updated
So:
Code: [Select]
  Published
    { Published-Deklarationen }
    PROPERTY Source: TStrings Read FStrings Write SetStrings;

procedure TStringsDataSet.SetStrings(AValue: TStrings);
begin
  if FStrings=AValue then Exit;
  FStrings:=AValue;
  if Fstrings.InheritsFrom(TStringList) then
    TStringList(FStrings).OnChange:=StringListOnChange;
end;

Then DBGrid should work properly:
Thanks to your suggestion i updated:
Code: [Select]
  Private
    { Private-Deklarationen }
    FStrings: TStrings;
    Fopened: Boolean;
    FCurrendDS :integer;
    FChanging : boolean;

[...]
function TStringsDataSet.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
  DoCheck: Boolean): TGetResult;
BEGIN
  Result:=grOk;
    case GetMode of
      gmCurrent:
        if (FCurrendDS>=1) or (FCurrendDS<0) then
          Result:=grError;
      gmNext:
        if (FCurrendDS<0) then
          Inc(FCurrendDS)
        else
          Result:=grEOF;
      gmPrior:
        if (FCurrendDS>0) then
          Dec(FCurrendDS)
        else
          result:=grBOF;
    end;
END;

procedure TStringsDataSet.InternalFirst;
begin
  FCurrendDS:=0;
end;
[...]
procedure TStringsDataSet.InternalOpen;

BEGIN
  //
  internalinitFielddefs;
  CreateFields;
  FCurrendDS:=-1;
END;
[...]


procedure TStringsDataSet.SetFieldData(Field: TField; Buffer: Pointer);
begin
//  FStrings.Values[Field.FieldName]:=Field.value;
  FChanging := true;
  try
  inherited SetFieldData(Field, Buffer);
  if assigned(Buffer) then
  case Field.DataType of
    ftBoolean:FStrings.Values[Field.FieldName]:=BoolToStr( wordbool(Buffer^),true);
    ftFloat: FStrings.Values[Field.FieldName]:=FloatToStr( double(Buffer^));
    ftInteger: FStrings.Values[Field.FieldName]:=IntToStr( longint(Buffer^));
    ftstring:FStrings.Values[Field.FieldName]:= PChar( Buffer);
  end;
  finally
    Fchanging:= false;
  end;
end;

procedure TStringsDataSet.StringListOnChange(Sender: Tobject);
begin
  try
  if not FChanging then
    Refresh;
  except

  end;
end;
I Hope i didn't forget something
... maybe more comes later

I know i have to do a saver String to XXX - Conversion.

after the post everything seems to work,
but when editing i have a small update-problem.


OS: Win XP x64, Win 7, Win 7 x64, Win 10, Win 10 x64, Suse Linux 13.2
Laz: 1.4 - 1.8.4, 2.0
https://github.com/joecare99/public
'~|    /''
,_|oe \_,are
If you want to do something for the environment: Twitter: #reduceCO2 or
https://www.betterplace.me/klimawandel-stoppen-co-ueber-preis-reduzieren

jc99

  • Hero Member
  • *****
  • Posts: 553
    • My private Site
Re: TStringsDataset, help needed
« Reply #11 on: July 09, 2015, 10:59:18 pm »
Solved the Update-Problem & did a saver string to anything conversion
Code: [Select]
function TStringsDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
  aStr:string;
  lInteger: integer;
  lBool: Boolean;
  lFloat: Double;
begin
  if assigned(Buffer) then
    begin
  case Field.DataType of
    ftBoolean:
      if TryStrToBool(FStrings.Values[Field.FieldName],lBool) then
        wordbool(Buffer^):= lBool;
    ftinteger:if TryStrToint(FStrings.Values[Field.FieldName],lInteger) then
        LongInt(Buffer^):= lInteger;
    ftFloat:if TryStrToFloat(FStrings.Values[Field.FieldName],lFloat,FFs) then
        double(Buffer^):=lFloat;
    ftString:begin
      aStr :=  FStrings.Values[Field.FieldName];
      if length(astr) > 0 then
      move(aStr[1],Buffer^,length(aStr)+1)
      else
        byte(Buffer^):=0;
    end;
  end;
  result := true;
    end;
end;

procedure TStringsDataSet.SetFieldData(Field: TField; Buffer: Pointer);

begin
//  FStrings.Values[Field.FieldName]:=Field.value;
  FChanging := true;
  try
  inherited SetFieldData(Field, Buffer);
  if assigned(Buffer) then
  case Field.DataType of
    ftBoolean:FStrings.Values[Field.FieldName]:=BoolToStr( wordbool(Buffer^),true);
    ftFloat: FStrings.Values[Field.FieldName]:=FloatToStr( double(Buffer^),Ffs);
    ftInteger: FStrings.Values[Field.FieldName]:=IntToStr( longint(Buffer^));
    ftstring:FStrings.Values[Field.FieldName]:= PChar( Buffer);
  end;
  finally
    Fchanging:= false;
  end;
  if not (State in [dsCalcFields, dsFilter, dsNewValue]) then
   DataEvent(deFieldChange, PtrInt(Field));

end;
OS: Win XP x64, Win 7, Win 7 x64, Win 10, Win 10 x64, Suse Linux 13.2
Laz: 1.4 - 1.8.4, 2.0
https://github.com/joecare99/public
'~|    /''
,_|oe \_,are
If you want to do something for the environment: Twitter: #reduceCO2 or
https://www.betterplace.me/klimawandel-stoppen-co-ueber-preis-reduzieren

 

TinyPortal © 2005-2018