いつもお世話になります。macoです。
レコードのポインタ型を格納したTListを、
更にTListに格納するクラスを作成しています。
イメージとして、
「値 := CellList.Items[x].Items[y].メンバ;」で値を取り出したいのです。
(補足)
Itemsをdefaultプロパティに設定して、
「値 := CellList[x][y].メンバ;」と値を管理したい。
しかし、レコードのポインタ型を格納する時点で、
無限ループが発生してしまい、上手く動作しません。
「Self」や「default」の使い方がまずいのかと思い、
色々試してはみたのですが、解決できませんでした。
どなたかお知恵を貸して下さい。宜しくお願いします。
次発言にコードをつけます。
type
PRec = ^TRecord;
TRec = record
メンバ
end;
type
TPList = class(TList) //PRecを格納するリスト
private
function GetP(Index: Integer): PRec;
procedure PutP(Index: Integer; const Value: PRec);
public
property Items[Index : Integer] : PRec Read GetP Write PutP; default;
// ↑ ItemsでPRecにアクセスする
type
TCellList = Class(TList) //TPListを格納するリスト ■クラスの本体
private
XList : TPList;
public
constructor Create;
property Items[Index : Integer] : TPList Read Get Write Put; default;
// ↑ ItemsでPRecを格納したTPListにアクセスする
end;
implementation
//-------------{ TCellList }-----------------
constructor TCellList.Create;
var
Rec : PRec;
X,Y : Integer;
begin
for X := 0 to 9 do begin
XList := TPList.Create;
XList.Count := 10;
for Y := 0 to 9 do begin
new(Rec);
Rec^.メンバ := 値;
XList.Items[j] := Rec; // (1).ここで(2)のPutPメソッドが実行される
end;
end;
end;
procedure TCellList.Put(Index: Integer; const Value: TPList);
begin
TLA(Self).Items[Index] := Value;
end;
function TCellList.Get(Index: Integer): TPList;
begin
Result := inherited Get(Index);
end;
//-------------{ TPList }-----------------
function TPList.GetP(Index: Integer): PRec;
begin
Result := PLARec(inherited Get(Index));
end;
procedure TPList.PutP(Index: Integer; const Value: PRec);
begin
TPList(Self).Items[Index] := Value; // (2).この文が無限ループになる
end;
申し訳ありません。
編集チェックが甘く元コードの変数名の個所がありました。
以下に編集後のものを書きます。
//-------------{ TCellList }-----------------
procedure TCellList.Put(Index: Integer; const Value: TPList);
begin
> TLA(Self).Items[Index] := Value;
> ↓
TCellList(Self).Items[Index] := Value;
end;
//-----------{ TPList }---------------
function TPList.GetP(Index: Integer): PRec;
begin
> Result := PLARec(inherited Get(Index));
> ↓
Result := PRec(inherited Get(Index));
end;
回答ではありません。
もし、100行100列のポインタの二次元配列をつくるとして、その実装では
101個の TList 派生クラスをつくることになりますね。わたしだったら、
内部にポインタの二次元動的配列を保持して、Cells[iCol,iRow] のプロパティー
を作ります。そして、Cols と Rows のプロパティーにアクセスするときだけ
動的に TList のインスタンスを作ります。TStringGrid と TStrings の関係を
見習ってみてください。
一応、この書き込みに関しては自分で解決出来ました。
(1)でTPListにRecを格納するときにItemsメソッドが呼ばれ(2)に
(2)でItemsメソッドが呼ばれ再度(2)に……
と言った感じにループになっていました。
なので、TCellList、TPListで定義したItemsメソッド名をどちらも変更し、
定義名を変更したメソッド内ではTList標準のItemsメソッドを利用することで正常に動作しました。
-----------------------------------------------------
jokさん
アドバイスありがとうございます。
jokさんがおっしゃる通り、100×100のポインタ二次元配列には
101個のTList派生クラスが必要になります。
もしよろしければ、jokさんの方法をもう少し具体的に教えていただけませんか?
jokさんの方法だと、私の方法でのポインタ管理用の100個のTListが不要になり、
代わり必要なときだけ、TListのインスタンスを作成するわけですか?
恥ずかしい話ですが、TList派生クラスでの二次元配列も結構苦労して考えたくらいですので、
正直実現方法に見当がつきません。
ヒントでも良いので教えて頂けないでしょうか?
(充分ヒントはもらっているのでしょうけど…)
>もしよろしければ、jokさんの方法をもう少し具体的に教えていただけませんか?
こんな感じです。ポインタの二次元配列を操作するクラスを TwoDArrayUnit.pas
につくりました。
--------------------------------------------
unit TwoDArrayUnit;
interface
uses
SysUtils, Classes, Dialogs;
type
TPointerGrid = class(TObject)
private
FPArray: array of array of pointer;
FColCount: integer;
FRowCount: integer;
procedure SetColCount(const Value: integer);
procedure SetRowCount(const Value: integer);
function GetItems(iCol,iRow:integer): pointer;
procedure SetItems(iCol,iRow:integer; const Value: pointer);
protected
public
constructor Create(InitColCount,InitRowCount:integer);
destructor Destroy;override;
property ColCount:integer read FColCount write SetColCount;
property RowCount:integer read FRowCount write SetRowCount;
property Items[iCol,iRow:integer]:pointer read GetItems write SetItems;
end;
implementation
{ TPointerGrid }
constructor TPointerGrid.Create(InitColCount, InitRowCount: integer);
begin
FColCount := InitColCount;
FRowCount := InitRowCount;
SetLength(FPArray,FColCount,FRowCount);
end;
destructor TPointerGrid.Destroy;
begin
inherited;
end;
procedure TPointerGrid.SetColCount(const Value: integer);
begin
FColCount := Value;
SetLength(FPArray,FColCount,FRowCount);
end;
procedure TPointerGrid.SetRowCount(const Value: integer);
begin
FRowCount := Value;
SetLength(FPArray,FColCount,FRowCount);
end;
function TPointerGrid.GetItems(iCol,iRow:integer): pointer;
begin
result := nil;
if (iCol>=FColCount) or (iRow>=FRowCount) then
ShowMessage('範囲外のアクセスです')
else
result := FPArray[iCol,iRow];
end;
procedure TPointerGrid.SetItems(iCol,iRow:integer; const Value: pointer);
begin
if (iCol>=FColCount) or (iRow>=FRowCount) then
ShowMessage('範囲外のアクセスです')
else
FPArray[iCol,iRow] := Value;
end;
end.
--------------------------------------------
まだ基本的な部分だけです。行・列の途中に行・列を挿入したり、削除したりの
メソッドは未実装です。property Cols:TList なども未実装ですが、感じがつかめ
るでしょう。これをテストするコードは、Button1 とStringGrid1をつかって
--------------------------------------------
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, TwoDArrayUnit, StdCtrls, Grids;
type
TForm1 = class(TForm)
StringGrid1: TStringGrid;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
PointerGrid:TPointerGrid;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
iCol,iRow:integer;
begin
PointerGrid := TPointerGrid.Create(5,5);
for iRow := 0 to PointerGrid.RowCount-1 do
for iCol := 0 to PointerGrid.ColCount-1 do
PointerGrid.Items[iCol,iRow] := pointer(iCol*iRow);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
PointerGrid.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
iCol,iRow:integer;
begin
for iRow := 0 to PointerGrid.RowCount-1 do
for iCol := 0 to PointerGrid.ColCount-1 do
StringGrid1.Cells[iCol,iRow] := IntToStr(integer(PointerGrid.Items[iCol,iRow]));
end;
end.
--------------------------------------------
うまくいっているようです。特定のレコードのポインタの配列の場合は、入出力の型を
pointer から PRec などに変えてください。
よく考えてみると、上の TPointerGrid クラスは二次元の動的配列へのアクセスを
簡単にラップしているだけですね。
> TStringGrid と TStrings の関係を見習ってみてください。
自分でこう書いておいて、ちっとも見習ってないですね。まとまった時間をかけて
実装コードを調べて改良してみます。
TBitmapを使って二次元配列を実装してみるのはどうでしょう
var
ArrayBmp:TBitmap;
X,Y:integer;//座標
I:integer;//格納したい値
begin
with ArraryBmp do
begin
ArraryBmp.Width := 100 //縦=Y
ArraryBmp.Height := 100 //横=X
Canvas.Pixels[X,Y] := I //格納
I := Canvas.Pixels[X,Y];//取り出し
end;
end;
これをプロパティにして高速にアクセスできるようにしてはどうでしょう
副産物としてbitmap形式でファイルに落とせます
さらにそのファイルは正式なファイルなので適当なツールで読み込めます
> TBitmapを使って二次元配列を実装してみるのはどうでしょう
おもしろいですね。いや、まったくおもしろい。発想が素晴らしい。
行・列を拡張・縮小するとき消えてしまわないようにバックアップするとか、
pf24bit 以上でないと Pixels[] の値がセットとゲットと一致しない
とかいろいろいろありますけど、発想の自由さに驚嘆しました。
jokさん
サンプルプログラムありがとうございます。
この方法であれば、
PRecを格納するTList(=TPList)も、TPListを格納するTListもどちらも不要になりますね。
プロパティの実装方法なども参考になりました。ありがとうございます。
> FPArray: array of array of pointer;
↓
FPArray: array of array of PRec; というように変更
new(FPArray[i,j]);
FPArray[i,j]^.メンバ := 値;
とすることでレコード型のポインタでも動作OKでした。
ところで、質問なのですが、
クラスの内部で、PRec(レコードのポインタ型)の二次元配列を保持するのと、
私のTListでの方法のどちらがメモリ消費上効率的なのでしょうか?
行・列数とも最大でも20程度ですので、メモリ消費を気にしなくても良いとは思いますが、
疑問に思ったので質問しました。
--------------------------------------------------------------------------
FODKさん
ご助言ありがとうございます。
今回の質問はStringGridの各セルの行列番号から補足情報を取り出す用途を
考えていました。もしかしたら、FODKさんの方法でも出来るのかも知れませんが、
TBitmapを扱ったことがないので、すぐには試せそうもありません。
また機会を作って試してみたいと思います。その時もし分からないことがありましたら、
質問させてください。
本当にありがとうございます。
> どちらがメモリ消費上効率的なのでしょうか?
クラスを動的に生成するのが少ない動的配列の方が効率的だと思います
> 今回の質問はStringGridの各セルの行列番号から補足情報を取り出す用途
Objects[i,j] は TObject 型ですからポインタと同じですけど
> クラスを動的に生成するのが少ない動的配列の方が効率的だと思います
やはりTListの動的生成が不要な分、動的配列での管理の方がスマートなのですね。
ありがとうございます。
今回は、レコードのポインタ型の二次配列を利用しクラスを作成してみます。
> Objects[i,j] は TObject 型ですからポインタと同じ
TBitmapもPixelsもTObjectを継承しているからでしょうか?
今回は数値しか扱わないので、他の人がプログラムを見たときに
TBitmapやPixelsを使っているとイメージが掴みにくくなるかなと思いましたので…。
(とうよりも私が良く理解できていません。申し訳ありません)
>> Objects[i,j] は TObject 型ですからポインタと同じ
> TBitmapもPixelsもTObjectを継承しているからでしょうか?
違います。StringGrid では行・列を指定して たとえば
StringGrid1.Cells[iCol,iRow] := 'Data';
というふうに文字列を設定したり、また取り出したりできますね。同様に
StringGrid1.Objects[iCol,iRow] := TObject(SomePointer);
というふうに、また、
SomePointer := Pointer(StringGrid1.Objects[iCol,iRow]);
というふうにポインタの値を設定取得可能です。
TBitmap ではたとえば pf32bit の PixelFormat では、1ピクセルあたりの
色データが32ビットの整数と等価です。また、ポインタも TObject などの
オブジェクト参照も 32bit の整数と等価のデータです。ですから互いにキャスト
して設定・取得ができます、という意味です。ビットマップは本質的に色の
二次元配列のデータ構造ですから、色が32bitのときは、キャストすると
ポインタの二次元配列として利用できます。この場合、ファイルとのアクセスは
標準のメソッドを使うことができて便利ですね。
TStringGrid の Objectsプロパティのことだったのですね。
> pf24bit 以上でないと Pixels[] の値がセットとゲットと一致しない
> TObject などのオブジェクト参照も 32bit の整数と等価のデータです。
pf32bitの件もようやく理解出来ました。
少し話しが違うかもしれませんが、
コンポーネントのTagプロパティに、Integer型にキャストしたポインタを格納するテクニックに雰囲気が似てますね。
(Tagのヘルプにも記述がありますが)
FODKさんのアドバイスは
TBitmapをベースにクラスを作成すれば、二次元配列を簡単に管理出来るし、
PixelFormatプロパティでpf32bitを指定すれば、Pixelsにポインタ型を格納出来るということだったのですね?
> Pixelsにポインタ型を格納出来るということだったのですね?
そうです。おもしろいでしょ?
> 今回の質問はStringGridの各セルの行列番号から補足情報を取り出す用途
このためには Objects[] を使用すると特にほかの配列は必要ないですね。
> そうです。おもしろいでしょ?
そうですね。本当に関心しました。
TBitmapという名前がついてる時点で、わたしにはそのような柔軟な考えは思いつかないと思います。
jokさん、FODKさん
この度はありがとうございました。とても勉強になりました。
ツイート | ![]() |