クラス配列や、配列を含むクラスなどをまとめたTComponent型のクラスをファイル保存、読出しする方法

解決


kz5  2020-11-12 15:16:43  No: 149332

設定値(入力値)に基づき、配列を確保して各種計算処理後、その設定値と配列を含む計算結果のファイル保存と読出しをしようと思います。
元々設定値や計算結果はクラス宣言し、各変数やクラスを含むクラス(TObject)として作成していましたが、ファイルとのやり取りを考える中で、
Mr.XRAYさんの「」と別の書籍を参考に、すべてのデータを含む最上位(最下位?)のクラスをTComponent型にして、書き換えました。
Mr.XRAYさんのサンプルページ:
http://mrxray.on.coocan.jp/Delphi/plSamples/130_SaveComponent.htm

各変数のpublishedなpropertyを作り、保存と読出しをテストしたところ、下記の問題があります。
1.下位のクラス(TCompornentに含まれるクラス)の配列でないpropertyは成功します。
2.下位のクラスの配列(クラスの配列も含む)は実現できていません。配列のproperty宣言の仕方がわからない。
下記も参考に読みましたが、そもそもファイルとの出し入れ目的なので、的が外れているようです。indexを使った宣言をした上で何らかの手段が必要かもしれませんが・・・?
http://docwiki.embarcadero.com/RADStudio/Sydney/ja/%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3

クラス配列や、配列を含むクラスなどをまとめたTComponent型のクラスをファイル保存、読出しする方法を教えていただけませんか?

下記はそのクラスの宣言部です。(配列部などは宣言方法不明のため記述していません)

unit TestClassUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Controls,
  Dialogs, StdCtrls, ExtCtrls, ExtDlgs;

type
  TA = class(TPersistent)
  private
    FA1: integer;
    FA2: array of double;
  published
    property A1  :integer read FA1 write FA1;

end;

type
  TB = class(TPersistent)
  private
    FB1: integer;
    FB2: array of TA;
  public
    procedure SetArrayA(num: integer);
  published
    property B1  :integer read FB1 write FB1;

end;

type
  TC = class(TPersistent)
  private
    FC1: array of double;
    FC2: array of double;
  public
    procedure SetArrayC(num: integer);

end;

type
  TD = class(TComponent)
  public
    FD1: integer;
    FD2: double;
    FD3: TC;
    FD4: TB;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
end;

implementation

procedure TC.SetArrayC(num: Integer);
begin
  SetLength(FC1, num);
  SetLength(FC2, num);
end;

constructor TD.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FD3 := TC.Create;
  FD4 := TB.Create;
end;

destructor TD.Destroy;
var
  i: Integer;
begin
  FreeAndNil(FD3);
  for i := Low(FD4.FB2) to High(FD4.FB2) do begin
    FreeAndNil(FD4.FB2[i]);
  end;
  FreeAndNil(FD4);
  inherited;
end;

end.


HFUKUSHI  2020-11-12 17:44:45  No: 149333

> クラス配列や、配列を含むクラスなどをまとめたTComponent型のクラスをファイル保存、読出しする方法を教えていただけませんか?
TComponentとして、ということは、フォームやデータモジュール上に配置して、そのデータをdfmに保存/読込したい、ということでしょうか?
もしそうでないなら、TComponentとして作り込む必要はないですね。


kz5  2020-11-12 19:28:54  No: 149334

コントロールのような実体を持たないものです。変数と変数配列のカタマリです。


take  2020-11-13 08:49:16  No: 149335

TComponentから派生されるクラスは保存、読み出す方法が用意されていますが
配列にした場合は独自に用意する必要があるかと思います。

Formを保存、呼び出す仕組みを実装するとこんな感じです。
Formの部分を自分のTComponentに置き換えて使えるかどうか?と言ったところです。

RTIという仕組みです。

【保存】
var
  FSaveObj : TComponent;
  e : TMemoryStream;
begin
  FSaveObj := Form1
  e := TMemoryStream.Create;
  try
    e.WriteComponent(FSaveObj);
    e.SaveToFile(保存ファイル名);
  finally
    e.Free;
  end;
  
【読込】
var
  FSaveObj : TComponent;
  e : TMemoryStream;
begin
  FSaveObj := Form1
  e := TMemoryStream.Create;
  try
    e.LoadFromFile(読込ファイル名);
    e.ReadComponent(FSaveObj);
  finally
    e.Free;
  end;
end;


kz5  2020-11-13 11:07:35  No: 149336

回答ありがとうございます。
これは一度メモリーストリームへ入れてファイル保存/読み出しをするやり方ですね。
Mr.XRAYさんのサンプルページにあったので、テストしました。その結果が最初の質問の通りです。
「配列のやり方がわからない。」「配列以外の変数(publishedなproperty)はうまくいく。」
delphi IDEのフォームのファイル保存のような、テキスト形式でも同じ結果です。

iniやCSV、バイナリーなど、一つ一つを処理していく方法はあると思いますが、
TComponent標準の機能のように、プロパティー宣言しておけば、バサッと処理してくれるものがないかと・・・。
HELP等も探しましたが、できそうでできない・・・。


take  2020-11-13 11:52:34  No: 149337

簡単に説明するとRTTI(実行時型情報)には配列に相当する物がありません。

FormにButtonが10個ある場合の保存処理としては
それぞれのButtonのParentとしてFormが設定されている状態ですが
これをFormから見るとCollectionとして10個のButtonが登録されているので10個分が処理されます。
これを配列として設計しつつ、上記のように見せかけると保存、読込が出来るのかも知れません。

なぜこんな便利な方法があるのに採用しないのか?と問われると
・配列となったクラスの構造が変わると保存しておいたデータが読み込めない
・配列の要素数が変わった場合も上記と同じく読み込めない
以上が面倒ななのですよね。

Delphiからは直接使えませんが外部ライブラリかなにかで「JSON形式」が使えるもの
TJsonSerializer などがもしかすると使えるのかも知れません。


kz5  2020-11-13 13:16:46  No: 149338

回答ありがとうございます。
「なんかできそう。」と思ったのも、takeさんの言われる通り、
IDEからのForm保存で配列らしきものもあるし、
せっかくクラスに情報(入力と出力)を集約したのに、ファイル出力でまた中身をばらして保存するのもなんだかなぁ・・・
という。実際入力値が5種類各30ぐらい、出力(計算結果)配列が10種類各300づつ、けっこうめんどくさい。
検索で「*クラス*保存*」にTComponent型がヒットしたので、これはいいと。
もう少しあがいてみます。


take  URL  2020-11-13 13:33:10  No: 149339

TComponent系列の保存は、自分の頭の中ではDelphi2007で停止しているので現在のDelphiでは出来る方法があるかも知れません。
と思ったらエンデバカデロの方からテクニカルセッションにて拡張されたRTTIの説明がありました。

https://www.embarcadero.com/images/jp/event/devcamp/session_docs/21/T8_Part2.pdf

読んでいくと「配列に対するサポートが弱い」 と手厳しいみたいです。


kz5  2020-11-13 15:11:44  No: 149340

お付き合いいただきありがとうございます。
さっそくpdf読んでみました。
今使っているのはXE2(10年近く使ってませんでした)、拡張された直後のバージョンみたいですね。
ただ、指摘の通り「配列に対するサポートが弱い。」
「動的配列のフィールドは通常のプロパティ並に扱えるので・・・」との記述。
プロパティからの配列アクセスに関する方法論として云々ですが、
私本来の目的「クラスの一括保存/読み出し」とはどんどんかけ離れていくような・・・。
「クラス丸ごとバサッ」と行きたいのは都合がよすぎるみたいですね。


au  2020-11-13 16:45:10  No: 149341

ここのやり方だと上手く行くかもですね。
色々コード書く必要がありますが
https://stackoverflow.com/questions/20662975/how-to-use-defineproperties-in-a-custom-class-object-for-arrays-delphi


kz5  2020-11-13 21:31:05  No: 149342

auさん、ありがとうございます。
読んでみました。(自動翻訳でですが)
ここでの質問者は、変数と配列を含むクラスの保存を考えていたようです。
途中からTComponentが出てきて、配列だけは別にアクセスみたいな話になり、最後はDFM形式で云々。
結論は??。しっぽ切れ終わってるみたいです。似ているようで外れているような。
RTTI、WriteComponent、ReadComponrnt、property・・・近いところまではいくんですがねぇ・・・。
やはり、配列が絡むと途端にややこしくなり、クラス保存は「コレ」といったきめ技はないみたいですね。
元に戻って、クラスメンバを地道に扱うのが妥当ですかね?


AAA  2020-11-14 07:20:48  No: 149343

type
   TArrayString = array[2..4] of String;
   TArrayInteger = array[2..4] of Integer;

public
    property ArrayString: TArrayString read FArrayString write FArrayString;
    property ArrayInteger: TArrayInteger read FArrayInteger write FArrayInteger;

の保存なら

procedure SaveArray(AOBJECT: TOBJECT);
var
    I: Integer;
    FProp   : TRttiProperty;
    FContext: TRttiContext;
    FType   : TRttiType ;
    VALUE1  : TValue ;
    VALUE2  : TValue ;
begin
    FContext := TRttiContext.Create;
    FType    := FContext.GetType ( AObject.ClassType );
    for FProp in FType.GetProperties do
    begin
      case FProp.PropertyType.TypeKind of
        tkArray:
        begin
          VALUE1 := FProp.GetValue(AOBject);
          Form1.Memo1.Lines.Add(FProp.Name);            /// PropertyName
          for I:=0 to VALUE1.GetArrayLength -1 do
          begin
            VALUE2 := VALUE1.GetArrayElement(I);
            Form1.Memo1.Lines.Add(VALUE2.ToString);     /// 値
          end;
        end;
      end;
    end;

end;

public
    property XXX[Index: Integer]: String read GetXXX write SetXXX; 
これは 無理 ぽい


KZ5  2020-11-14 12:13:02  No: 149344

AAAさん、ありがとうございます。
これは配列の保存と考えていいのでしょうか?
type節の型は、実現部では使われていないようですが・・・。
私にとっては難解で、解読するのに時間がかかりそうです。


Mr.XRAY  2020-11-14 12:34:01  No: 149345

参考になれば.
難解でしたらゴメンなさい.

[130] [ 05_ポインタ型と配列型のプロパティ ]
http://mrxray.on.coocan.jp/Delphi/plSamples/130_SaveComponent.htm#05


kz5  2020-11-14 12:49:32  No: 149346

Mr.XRAYさんこんにちは。
指摘のページを最初から見たはずなのに・・・。05から下を読んでいませんでした。
研究してみます。


Mr.XRAY  2020-11-14 13:42:15  No: 149348

> 05から下を読んでいませんでした。

いえ,あの,
ページの先頭の履歴に書きましたが,昨日追加した項目てす.


AAA  2020-11-14 15:01:51  No: 149349

type 
   TArrayString = array[2..4] of String; 
   TArrayInteger = array[2..4] of Integer;
これ?

public 
    property ArrayString: TArrayString read FArrayString write FArrayString; 
    property ArrayInteger: TArrayInteger read FArrayInteger write FArrayInteger;
この型って意味


kz5  2020-11-14 20:12:17  No: 149351

AAAさん 書き込みありがとうございます。
内容がちょっと理解できません。
スミマセン。


kz5  2020-11-14 22:56:15  No: 149352

Mr.XRAYさん 配列プロパティの読み書き うまくいきました。ありがとうございます。
全く記事の通りで、「配列型は published なプロパティにできない」で、コンパイルエラーが発生していました。
今まで何気なく配列を使っていましたが、TDoubleDynArrayなんてかすりもしませんでした。
調べると他にもTIntegerDynArrayとかありますね。仕組みはまだ理解不足ですが・・・。
その後、自分のテストプログラムに組み込んで変更し、Dに含まれるCクラスの配列プロパティ、そのCに含まれるBクラスの配列プロパティと階層を付けてテストしましたが、気持ちいいくらいどれもうまくいきました。

そこで、一番最初の質問の Dに含まれるCクラスですが、このCクラスそのものがDクラスで配列としたい場合、どのようにしたら実現できるでしょうか?

配列をTxxxxxDynArrayとすることと、DefineProperties メソッド というのがこのテクニックのミソだと思いますが、「クラスの配列」を扱う TxxxxxDynArray がありません。試行錯誤中です。


Mr.XRAY  2020-11-15 11:26:29  No: 149353

あるクラスの published でないプロパティを書き込み,読み込み可能にするには,
そのクラスのメンバとして DefineProperties メソッドをオーバライドします.
オーバライドなメソッドを作成するには,
前に提示した記事に以下のリンクがあります.

[03_継承元クラスのメソッドのオーバライド] 

その記事に以下のリンクがあります.

[メッセージ処理メソッドの作成]

 ↑ の記事に書いてある操作で作成します.そうすればタイプミスを防止できます.
Defin ぐらいまでタイプすれば一覧に出てくるでしょう.
書き込み用と読み出し用のメソッドは自分で作成します.
基本的に private 部でいいと思います.
 
DefineProperties は,上の記事の方法で入力した場合,
どこで入力しても自動的に protected 部に挿入されます.
継承元のクラスの protected 部にあるからです.
DefineProperties は TPersistent の派生クラスで使用可能です.

派生元はオンラインヘルプで調べることができます (クラスの系統図があります).
この系統図の左側に TPersistent があれば,
TPersistent の protected, public 部のメソッドやプロパティが使用できます. 

例 : TComponent の直接の継承元は TPersistent
   したがって,DefineProperties が使用できる
http://docwiki.embarcadero.com/Libraries/Sydney/ja/System.Classes.TComponent
 
※ 記事中のリンクはダテではありません (笑)


Mr.XRAY  2020-11-15 11:32:02  No: 149354

>  TxxxxxDynArray がありません。

uses に System.Types (または単に Types) が必要です.
オンラインヘルプで検索すると System.TxxxxxDynArray がヒットしますが,
最近の Delphi では使用できないようです.
そんな時は臨機応変に,検索でヒットする

http://docwiki.embarcadero.com/Libraries/Sydney/ja/System.Types.TDoubleDynArray

を使用します.この場合,System.Types.TDoubleDynArray となっていますから,
uses に System.Types を追加します.

一般的には,uses に何が必要かはオンラインヘルプで調べることができます.
オンラインヘルプでクラス型等を検索する時は,プリフィックスの T が必要です.
TDoubleDynArray は,DoubleDynArray ではなく TDoubleDynArray で検索します.
同じ様に,
TOpenDialog を調べる時は OpenDialog ではなく TOpenDialog で検索します.

Protected 部のメンバを調べるには,Protected をチェックします.

参考 : [ クラス名のプリフィックスの T ( 入門者用 ) ]
http://mrxray.on.coocan.jp/Delphi/Others/A_UltraIntro03.htm


Mr.XRAY  2020-11-15 11:51:31  No: 149355

Google で検索すると英語のオンラインヘルプのページがヒットすることがあります.
例えば 「delphi TDoubleDynArray」で検索すると以下の記事がヒットします.
                                  
http://docwiki.embarcadero.com/Libraries/Sydney/en/System.Types.TDoubleDynArray

この場合,en のところを ja にすれば日本語の記事のページになります.

http://docwiki.embarcadero.com/Libraries/Sydney/ja/System.Types.TDoubleDynArray


kz5  2020-11-15 12:39:19  No: 149356

Mr.XRAYさん たぶたび回答ありがとうございます。
私の記事の書き方が悪かったのか、あり得もしないクラスを想定していたのか・・・。
TxxxxxDynArrayで「xxxxx」の部分、integerやDoubleなどのクラスはHELPで見つけてテストしました。
「「クラスの配列」を扱う TxxxxxDynArray がありません。」の意味は、
「配列を持つクラスの配列」を同じ方法で扱うことのできる(T-CLASS-DynArrayのような)クラスはないものかと。

今の「配列を持つクラス」にもう一段階層をつけて、配列部分を「TxxxxxDynArray」クラス化すれば、できそうな気もしていますが、コーディングしてるとだんだんと頭がこんがらがってきてます。
保存と読み出しのためのアクセスモデルの系統は、Mr.XRAYさんの解説と資料で理解が進みましたが、寄る年波で・・・、階層の宣言部と実現部を行ったり来たりしているうちにこんがらがってしまい。


Mr.XRAY  2020-11-15 14:14:11  No: 149357

> 「配列を持つクラスの配列」

具体的にどのようなものなのか理解できませんが,「クラスの配列」は可能です. 
そのクラスに配列型の変数やプロパティを実装するのは,開発者の自由です.

 array of TMyClass
 
のように定義すれば可能ですが,ジェネリックスを使用した方が便利です.
以下のサンプルに

 FClassList : TList<TMyClass>;
  FClassList : TObjectList<TMyClass>;
  
というのがあります. 
これはクラス型のリストてすが,

 FClassList : TArray<TMyClass>;

とすれば配列になりますが,リストの方が扱い易いと思います.

ジェネリックスの使用には uses に Generics.Collections が必要です.
ジェネリックスでは Integer 等も使用できます.
つまり,動的配列に使用できます.

[ 140_オブジェクトリスト ( クラス型 ) の保存 - TLabel ]
[ 07_TList<T> を使用したクラス型のリストの保存の基本例 ]
http://mrxray.on.coocan.jp/Delphi/plSamples/140_SaveComponentList1.htm#07

[ 08_TObjectList<T> を使用したクラス型のリストの保存の基本例 ]
http://mrxray.on.coocan.jp/Delphi/plSamples/140_SaveComponentList1.htm#08

※ ↑ のページは,前に提示した記事のページに参考リンクとしてリンクしています.
※ 記事内のリンクはダテではありません (笑)

以下も同じようものです.

[ 12_ジェネリックスの TList を使用したコンポーネントのリスト  ]
http://mrxray.on.coocan.jp/Delphi/plSamples/161_CreateComponent_List.htm#12

[ 13_TObjectList<T> を使用したコンポーネントのリスト ]
http://mrxray.on.coocan.jp/Delphi/plSamples/161_CreateComponent_List.htm#13


kz5  2020-11-16 00:21:44  No: 149358

やっと頭の整理がつきました。(利用するクラスの真の理解は別として)
最初の難関:配列を持つTComponent型のproperty表現と、その入出力:をクリアでき、
テストプログラムで期待する結果が出ました。
最後は、自分で宣言しようとしたクラスの階層ではまってしまいましたが、何とか実現できました。

クラスで使う手続きや関数で、型が違うだけで同じ処理部分が相当あります。
言われる通り、ジェネリックスを使ってみたいのですが、ここで欲を出すとまたまたはまりそうです。
とりあえずは今のテストプログラムを基に、実際のプログラムの改変をしなければなりません。

たくさんの指摘、示唆をありがとうございました。


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加