How to use DefineProperties in a custom Class Object for Arrays - Delphi

后端 未结 1 1727
萌比男神i
萌比男神i 2021-01-26 12:30

I\'m trying to create my own class object and use it to store various data types for my application, this all works fine when using Published Properties, I can stream these to d

1条回答
  •  挽巷
    挽巷 (楼主)
    2021-01-26 12:45

    HOW do you actually try to read and write it ? I think you're trying to make complex incompatible things when there instead of using standard methods.

    Why not to use standard VCL streaming procedures?

    procedure TMyDataSet.SaveToStream(const Stream: TStream);
    begin
       Stream.WriteComponent(self);
    end;
    
    procedure TMyDataSet.LoadFromStream(const Stream: TStream);
    begin
       Stream.ReadComponent(self);
    end;
    

    However if instead of using TFiler and standard VCL streamer you make your custom code using RTTI (GetPropList) - then it would not call those virtual properties APi custom to TFiler and would only show real properties.

    So my advice is just to use standard emthods like shown above and to streamline and harden the code.

    And since RegisterClass works by the classname you'd better choose another name, not clashing with a real TDataSet from stock DB unit.

    Fix the name and do register the class, so VCL streamer could find it by name! For example:

    procedure TMyDataSet.ReadArray(Reader: TReader);
    var
      N: Integer; S: String;
    begin
      N := Low(FArrayToSave);
      Reader.ReadListBegin;
      while not Reader.EndOfList do begin
        S := Reader.ReadString; // even if we would not save it - we should remove it from the input
        if N <= High(FArrayToSave) then
           FArrayToSave[N] := S;
        Inc(N);
      end;
      Reader.ReadListEnd;
    end;
    
    procedure TMyDataSet.WriteArray(Writer: TWriter);
    var
      I: Integer;
    begin
      Writer.WriteListBegin;
      for I := Low(FArrayToSave) to High(FArrayToSave) do begin
        Writer.WriteString(FArrayToSave[I]);
      end;
      Writer.WriteListEnd;
    end;
    
    initialization
      DataSet := TMyDataSet.Create(Nil);
      RegisterClasses([TMyDataSet]);
    
    finalization
      DataSet.Free;
    end.
    

    Additionally, i think you'd better - for future extensibility - save the array length in DFM.

    procedure TMyDataSet.WriteArray(Writer: TWriter);
    var
      I: Integer;
    begin
      Writer.WriteInteger(Length(FArrayToSave));
      Writer.WriteListBegin;
      for I := Low(FArrayToSave) to High(FArrayToSave) do begin
    

    ....

    procedure TMyDataSet.ReadArray(Reader: TReader);
    var
      N: Integer;  S: String;
    begin
      for N := Low(FArrayToSave) to High(FArrayToSave) do begin
          FArrayToSave := ''; // in case DFM would have less elements than 50
      N := Reader.ReadInteger;
      if N <> Length(FArrayToSave) then... recovery from unexpected DFM version error
    
      N := Low(FArrayToSave);
      Reader.ReadListBegin;
      while not Reader.EndOfList do begin
    

    PS. you do not need {$M+} there since TComponent already is derived from TPersistent

    PPS. Wanted to comment upon update in the question, but the phone refuses to do (too long?) so putting it here.

    1: since we moved away from using RTTI, the Typinfo unit no more needed in uses. 2: if N = Length(FFullArray) then lacks ELSE path. Okay, now we learned that DFM is broken or incompatible, what then? I think we better raise some error. Or try to remove list of N strings, so next property could be read. Or even remove the list of elements of any type/quantity until list end. Future compatibly is never warranted, but at least some attempt can be done, even just to explicitly halt with error. Skipping reading and silently leaving the reader inside middle of property, so next properties would get crazy, I think is not the way to do it.

    And generally David is correct about ignoring incorrect indices in the setter and getter. Unless you would intentionally come with some unusual pattern of implicit item creation from default template in sparse array by setting or getting with "free" "unbound" index (which is no code for either) the better approach at least in Delphi would be "fail early". That is what users of your class would expect by default. So kinda

      Procedure class.CheckArrayIdx(const i: integer);
      Var mx, mn : integer;
      Begin 
           Mn := low(myarray) ; Mx := high(myarray);
           If (i <= mx) and (I >= mn) then exit;
           Raise ERangeError.CreateFmt('%s.Items index should be %d <= %d <= %d',  [
                 Self.ClassName, mn, I, mx]) ;
       End;
    

    This procedure can be called as 1st line in both setter and getter. Then you can just work with surely correct index value.

    0 讨论(0)
提交回复
热议问题