如何使用DefineProperties在一个自定义类对象数组 - Delphi [英] How to use DefineProperties in a custom Class Object for Arrays - Delphi

查看:639
本文介绍了如何使用DefineProperties在一个自定义类对象数组 - Delphi的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建我自己的类对象,并使用它存储各种数据类型为我的应用程序,这一切工作正常,当使用发布的属性,我可以流这些到磁盘和回来没有问题。但我需要流一些整数和字符串数据类型的数组。

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 disk and back with no problems. But I need to stream some Arrays of both integer and strings data types as well.

我理解,数组,其他数据类型不能被发布的属性,因为Delphi不知道如何流式传输它们,我被引导相信你需要使用DefineProperties来完成这个,我创建了一个测试String数组作为公共属性,我可以读写它很好,但我需要流它到磁盘,所以我可以保存以备将来使用。

I understand that Arrays, amongst other data types can't be published properties because Delphi doesn't know how to stream them, I was led to believe you need to use DefineProperties to accomplish this, I've created a test Array of String as a Public property, I can read and write to it just fine, however I need to stream it to disk so i can save it for future use.

我可以找到的关于这个主题的唯一的东西在这里:

The only thing i can find that touches on this subject is here:

作为属性的自定义类的数组

我试图复制此代码和操作它来存档我需要的,但我不能得到它保存,我似乎缺少一些明显的,我使用的测试代码下面,我没有得到这个代码的错误,已发布的属性流到磁盘确定,但我的私人数组不。任何帮助将非常感激。

I've attempted to copy this code and manipulate it to archive what I need but I cannot get it to save, I'm seemingly missing something obvious, my test code I'm using is below, I get no errors with this code, published properties stream to disk ok but my private array does not. Any help would be greatly appreciated.

谢谢。

unit UnitDataSet;

//------------------------------------------------------------------------------

interface

uses System.Classes;
 {$M+}

//------------------------------------------------------------------------------

type
  TDataStrings = Array [1..50] of String;

  TDataSet = class(TComponent)
  protected
    procedure DefineProperties(Filer: TFiler); override;
    procedure ReadArray(Reader: TReader);
    procedure WriteArray(Writer: TWriter);

  private
    FArrayToSave : TDataStrings;
    FPStr        : String;

    function  GetItem(I: Integer): String;
    procedure SetItem(I: Integer; Value: string);

  public
    constructor Create(aOwner: TComponent); override;
    destructor  Destroy; override;

    procedure LoadFromStream(const Stream: TStream);
    procedure LoadFromFile(const FileName: string);
    procedure SaveToStream(const Stream: TStream);
    procedure SaveToFile(const FileName: string);

    property Items[I: Integer]: String read GetItem write SetItem;

  published

    property StringItem : String read FPStr write FPStr;

  end;

//------------------------------------------------------------------------------

var
  DataSet: TDataSet;

implementation

uses TypInfo, Sysutils;

{ TDataSet }

//------------------------------------------------------------------------------

procedure TDataSet.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('DataArray', ReadArray, WriteArray, True);
end;

//------------------------------------------------------------------------------

destructor TDataSet.Destroy;
begin
  inherited;
end;

//------------------------------------------------------------------------------

function TDataSet.GetItem(I: Integer): string;
begin
  Result := '';
  if (I > 0) and (I < Length(FArrayToSave)) then
    Result := FArrayToSave[I];
end;

//------------------------------------------------------------------------------

procedure TDataSet.SetItem(I: Integer; Value: string);
begin
  if (I > 0) and (I < Length(FArrayToSave)) then
    FArrayToSave[I] := Value;
end;

//------------------------------------------------------------------------------

procedure TDataSet.LoadFromFile(const FileName: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

//------------------------------------------------------------------------------

procedure TDataSet.LoadFromStream(const Stream: TStream);
var
  Reader: TReader;
  PropName, PropValue: string;
begin
  Reader := TReader.Create(Stream, $FFF);
  Stream.Position := 0;
  Reader.ReadListBegin;

  while not Reader.EndOfList do
  begin
    PropName := Reader.ReadString;
    PropValue := Reader.ReadString;
    SetPropValue(Self, PropName, PropValue);
  end;
   FreeAndNil(Reader);
end;

//------------------------------------------------------------------------------

procedure TDataSet.SaveToFile(const FileName: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmCreate);
  try
    SaveToStream(Stream);
  finally
    Stream.Free;
  end;
end;

//------------------------------------------------------------------------------

procedure TDataSet.SaveToStream(const Stream: TStream);
var
  PropName, PropValue: string;
  cnt: Integer;
  lPropInfo: PPropInfo;
  lPropCount: Integer;
  lPropList: PPropList;
  lPropType: PPTypeInfo;
  Writer: TWriter;
begin
  lPropCount := GetPropList(PTypeInfo(ClassInfo), lPropList);
  Writer := TWriter.Create(Stream, $FFF);
  Stream.Size := 0;
  Writer.WriteListBegin;
  for cnt := 0 to lPropCount - 1 do
  begin
    lPropInfo := lPropList^[cnt];
    lPropType := lPropInfo^.PropType;
    if lPropType^.Kind = tkMethod then Continue;
     PropName := lPropInfo.Name;
    PropValue := GetPropValue(Self, lPropInfo);
    Writer.WriteString(PropName);
    Writer.WriteString(PropValue);
  end;
  Writer.WriteListEnd;
  FreeAndNil(Writer);
end;

//------------------------------------------------------------------------------

constructor TDataSet.Create(aOwner: TComponent);
begin
  inherited;

end;

//------------------------------------------------------------------------------

procedure TDataSet.ReadArray(Reader: TReader);
var
  N: Integer;
begin
  N := 0;
  Reader.ReadListBegin;
  while not Reader.EndOfList do begin
    Reader.ReadListBegin;
    FArrayToSave[N] := Reader.ReadString;
    Reader.ReadListEnd;
    Inc(N);
  end;
  Reader.ReadListEnd;

end;

//------------------------------------------------------------------------------

procedure TDataSet.WriteArray(Writer: TWriter);
var
  I: Integer;
begin
  Writer.WriteListBegin;
  for I := 1 to High(FArrayToSave) do begin
    Writer.WriteListBegin;
    Writer.WriteString(FArrayToSave[I]);
    Writer.WriteListEnd;
  end;
  Writer.WriteListEnd;
end;


//------------------------------------------------------------------------------

initialization
  DataSet := TDataSet.Create(Nil);
finalization
  FreeAndNil(DataSet);
end.

//------------------------------------------------------------------------------

这是我的类代码重写了从Arioch的建议代码修改从下面:

Here is my Class code re-written with Arioch's suggested code modifications from below:

unit UnitCharSett;

interface

//------------------------------------------------------------------------------

uses System.Classes;

//------------------------------------------------------------------------------

type

  TCustomDatSetA = Array [0..99] of String;

  TCustomCharSet = class(TComponent)
  public
    procedure LoadFromStream(const Stream: TStream);
    procedure LoadFromFile(const FileName: string);
    procedure SaveToStream(const Stream: TStream);
    procedure SaveToFile(const FileName: string);
  end;

  TZCharSet = class(TCustomCharSet)

  private

    FFullArray : TCustomDatSetA;
    function  GetItem(I: Integer): String;
    procedure SetItem(I: Integer; Value: string);

  protected

    procedure DefineProperties(Filer: TFiler); override;
    procedure ReadArray(Reader:TReader);
    procedure WriteArray(Writer:TWriter);

  public

    property Items[Index: Integer]: string read GetItem write SetItem;

  published

  end;

//------------------------------------------------------------------------------

implementation

uses

  System.TypInfo, System.SysUtils;

//------------------------------------------------------------------------------

procedure TCustomCharSet.LoadFromFile(const FileName: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

//------------------------------------------------------------------------------

procedure TCustomCharSet.LoadFromStream(const Stream: TStream);
begin
  Stream.ReadComponent(Self);
end;

//------------------------------------------------------------------------------

procedure TCustomCharSet.SaveToFile(const FileName: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmCreate);
  try
    SaveToStream(Stream);
  finally
    Stream.Free;
  end;
end;

//------------------------------------------------------------------------------

procedure TCustomCharSet.SaveToStream(const Stream: TStream);
begin
 Stream.WriteComponent(Self);
end;

//------------------------------------------------------------------------------

{ TZCharSett }

//------------------------------------------------------------------------------

procedure TZCharSet.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('DataArray', ReadArray, WriteArray, True);
end;

//------------------------------------------------------------------------------

function TZCharSet.GetItem(I: Integer): string;
begin
  Result := '';
  if (I > -1) and (I < Length(FFullArray)) then
    Result := FFullArray[I];
end;

//------------------------------------------------------------------------------

procedure TZCharSet.ReadArray(Reader: TReader);
var
  N: Integer;
  S: String;
begin
  for N := Low(FFullArray) to High(FFullArray) do begin
    FFullArray[N] := '';
  end;
  Reader.ReadListBegin;
  N := Reader.ReadInteger;
  if N = Length(FFullArray) then
   begin
     N := Low(FFullArray);
     while not Reader.EndOfList do
      begin
       S := Reader.ReadString;
       if N <= High(FFullArray) then
         FFullArray[N] := S;
       Inc(N);
      end;
  end;
  Reader.ReadListEnd;
end;

//------------------------------------------------------------------------------

procedure TZCharSet.SetItem(I: Integer; Value: string);
begin
  if (I > -1) and (I < Length(FFullArray)) then
    FFullArray[I] := Value;
end;

//------------------------------------------------------------------------------

procedure TZCharSet.WriteArray(Writer: TWriter);
var
  I: Integer;
begin
  Writer.WriteListBegin;
  Writer.WriteInteger(Length(FFullArray));
  for I := Low(FFullArray) to High(FFullArray) do begin
    Writer.WriteString(FFullArray[I]);
  end;
  Writer.WriteListEnd;
end;

//------------------------------------------------------------------------------

initialization

  RegisterClasses([TZCharSet]);

//------------------------------------------------------------------------------

end.


推荐答案

你实际上如何读写?

为什么不使用标准的VCL流程?

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;






但是如果不使用TFiler和标准VCL流你使用RTTI(GetPropList)创建你的自定义代码 - 然后它不会调用那些虚拟属性APi自定义 TFiler ,只会显示真实的属性。


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.

所以我的建议只是使用如上所示的标准emthods,并简化和强化代码。

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

而且因为 RegisterClass 按类名工作,最好选择其他名称,而不是与库存DB单元中的真实 TDataSet 冲突。

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

修复名称并注册类,因此VCL streamer可以按名称找到它!例如:

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.






此外,我认为你会更好未来的可扩展性 - 在DFM中保存数组长度。


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。你不需要{$ M +},因为TComponent已经从TPersistent派生。

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

PPS。想在评论更新的问题,但手机拒绝做(太长?)所以把它放在这里。

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

1:因为我们不再使用RTTI,Typinfo单元不再需要使用。 2:如果N = Length(FFullArray)then 缺少ELSE路径。好了,现在我们了解到DFM是破碎的或不兼容的,那么呢?我想我们最好提出一些错误。或者尝试删除N个字符串的列表,所以下一个属性可以被读取。或者甚至删除任何类型/数量的元素的列表,直到列表结束。未来兼容是从不保证,但至少有一些尝试可以做,即使只是显式停止与错误。跳过阅读,默默地将读者留在财产的中间,所以下一个属性会疯狂,我认为不是这样做的方式。

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.

通常,David是正确的,忽略了setter和getter中的不正确的索引。除非你通过设置或获取freeunbound索引(这是没有任何代码),故意从稀疏数组中的默认模板创建一些不寻常的模式的隐式项目,至少在Delphi中更好的方法是fail早。这是你的类的用户默认期望的。所以kinda

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;

此过程可以在setter和getter中调用第一行。然后你可以只工作与肯定正确的索引值。

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

这篇关于如何使用DefineProperties在一个自定义类对象数组 - Delphi的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆