先週、私が予期していなかったことがわかりました。以下に説明します。なぜこれが起こるのか、私は興味があります。TDataSet クラスの内部的なものですか、TDBGrid のアーティファクトですか、それとも何か他のものですか?
開いている ClientDataSet のフィールドの順序が変更されました。具体的には、FieldDefs を使用してその構造を定義した後、CreateDatatSet を呼び出して、コードで ClientDataSet を作成しました。この ClientDataSet の構造の最初のフィールドは、StartOfWeek という名前の Date フィールドでした。ほんの少し後、StartOfWeek フィールドが ClientDataSet.Fields[0] の 0 番目の位置にあると仮定して私が書いたコードは、StartOfWeek フィールドが ClientDataSet の最初のフィールドではなくなったため、失敗しました。
調査の結果、ClientDataSet のすべてのフィールドが、特定の時点で、ClientDataSet が作成された時点の元の構造とは異なる位置に表示される可能性があることがわかりました。私はこれが起こる可能性があることを知りませんでした.Googleで検索しても、この影響についての言及はありませんでした.
起こったことは魔法ではありませんでした。フィールドはそれ自体で位置を変更したり、コードで行ったことに基づいて変更したりしませんでした。フィールドが ClientDataSet 内の位置を物理的に変更したように見える原因は、ユーザーが ClientDataSet がアタッチされている DbGrid 内の列の順序を (もちろん、DataSource コンポーネントを介して) 変更したためです。Delphi 7、Delphi 2007、および Delphi 2010 でこの効果を再現しました。
この効果を実証する非常に単純な Delphi アプリケーションを作成しました。これは、1 つの DBGrid、DataSource、2 つの ClientDataSet、および 2 つの Button を持つ単一のフォームで構成されています。このフォームの OnCreate イベント ハンドラは次のようになります。
procedure TForm1.FormCreate(Sender: TObject);
begin
with ClientDataSet1.FieldDefs do
begin
Clear;
Add('StartOfWeek', ftDate);
Add('Label', ftString, 30);
Add('Count', ftInteger);
Add('Active', ftBoolean);
end;
ClientDataSet1.CreateDataSet;
end;
[ClientDataSet 構造を表示] というラベルの付いた Button1 には、次の OnClick イベント ハンドラーが含まれています。
procedure TForm1.Button1Click(Sender: TObject);
var
sl: TStringList;
i: Integer;
begin
sl := TStringList.Create;
try
sl.Add('The Structure of ' + ClientDataSet1.Name);
sl.Add('- - - - - - - - - - - - - - - - - ');
for i := 0 to ClientDataSet1.FieldCount - 1 do
sl.Add(ClientDataSet1.Fields[i].FieldName);
ShowMessage(sl.Text);
finally
sl.Free;
end;
end;
移動フィールド効果を示すには、このアプリケーションを実行し、[ClientDataSet 構造を表示] というラベルの付いたボタンをクリックします。次のようなものが表示されるはずです。
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
次に、DBGrid の列をドラッグして、フィールドの表示順序を並べ替えます。[ClientDataSet 構造を表示] ボタンをもう一度クリックします。今回は、次のようなものが表示されます。
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
Label
StartOfWeek
Active
Count
この例で注目に値するのは、DBGrid の列が移動されていることですが、ClientDataSet.Field[0] にあったフィールドが 1 つの位置にあるように、ClientDataSet のフィールドの位置に明らかな影響があることです。ポイントは、すぐにそこにあるとは限りません。残念ながら、これは明確に ClientDataSet の問題ではありません。BDE ベースの TTable と ADO ベースの AdoTable で同じテストを実行したところ、同じ結果が得られました。
DBGrid に表示されている ClientDataSet のフィールドを参照する必要がない場合は、この影響について心配する必要はありません。残りの皆さんのために、いくつかの解決策を考えることができます。
必須ではありませんが、この問題を回避するための最も簡単な方法は、ユーザーが DBGrid 内のフィールドを並べ替えられないようにすることです。これは、DBGrid の Options プロパティから dgResizeColumn フラグを削除することで実行できます。このアプローチは効果的ですが、ユーザーの観点からすると、潜在的に価値のある表示オプションが排除されます。さらに、このフラグを削除すると、列の並べ替えが制限されるだけでなく、列のサイズ変更が防止されます。(列のサイズ変更オプションを削除せずに列の並べ替えを制限する方法については、http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm を参照してください。 )
2 番目の回避策は、DataSet のフィールドをリテラル位置に基づいて参照しないようにすることです (これが問題の本質であるため)。つまり、Count フィールドを参照する必要がある場合は、DataSet.Fields[2] を使用しないでください。フィールドの名前がわかっている限り、DataSet.FieldByName('Count') のようなものを使用できます。
ただし、FieldByName の使用にはかなり大きな欠点が 1 つあります。具体的には、このメソッドは、DataSet の Fields プロパティを反復処理してフィールドを識別し、フィールド名に基づいて一致を探します。これは FieldByName を呼び出すたびに行われるため、大きな DataSet をナビゲートするループなど、フィールドを何度も参照する必要がある状況では避けるべきメソッドです。
フィールドを繰り返し (そして何度も) 参照する必要がある場合は、次のコード スニペットのようなものを使用することを検討してください。
var
CountField: TIntegerField;
Sum: Integer;
begin
Sum := 0;
CountField := TIntegerField(ClientDataSet1.FieldByName('Count'));
ClientDataSet1.DisableControls; //assuming we're attached to a DBGrid
try
ClientDataSet1.First;
while not ClientDataSet1.EOF do
begin
Sum := Sum + CountField.AsInteger;
ClientDataSet1.Next;
end;
finally
ClientDataSet1.EnableControls;
end;
3 番目の解決策がありますが、これは、元の例のように、DataSet が ClientDataSet である場合にのみ使用できます。このような状況では、元の ClientDataSet のクローンを作成すると、元の構造を持つことができます。その結果、0 番目の位置に作成されたフィールドは、ClientDataSets データを表示する DBGrid に対してユーザーが行った操作に関係なく、その位置に残ります。
これは、Show Cloned ClientDataSet Structure というラベルの付いたボタンの OnClick イベント ハンドラーに関連付けられている次のコードで示されています。
procedure TForm1.Button2Click(Sender: TObject);
var
sl: TStringList;
i: Integer;
CloneClientDataSet: TClientDataSet;
begin
CloneClientDataSet := TClientDataSet.Create(nil);
try
CloneClientDataSet.CloneCursor(ClientDataSet1, True);
sl := TStringList.Create;
try
sl.Add('The Structure of ' + CloneClientDataSet.Name);
sl.Add('- - - - - - - - - - - - - - - - - ');
for i := 0 to CloneClientDataSet.FieldCount - 1 do
sl.Add(CloneClientDataSet.Fields[i].FieldName);
ShowMessage(sl.Text);
finally
sl.Free;
end;
finally
CloneClientDataSet.Free;
end;
end;
このプロジェクトを実行し、[複製された ClientDataSet 構造を表示] というラベルの付いたボタンをクリックすると、ここに示すように、ClientDataSet の真の構造が常に取得されます。
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
補遺:
基になるデータの実際の構造は影響を受けないことに注意することが重要です。具体的には、DBGrid 内の列の順序を変更した後、ClientDataSet の SaveToFile メソッドを呼び出すと、保存された構造は元の (真の内部) 構造になります。また、ある ClientDataSet の Data プロパティを別の ClientDataSet にコピーすると、コピー先の ClientDataSet にも真の構造が表示されます (これは、コピー元の ClientDataSet を複製したときに見られる効果と同様です)。
同様に、TTable や AdoTable など、他のテスト済みのデータセットにバインドされた DBGrid の列の順序を変更しても、実際には基になるテーブルの構造には影響しません。たとえば、Delphi に同梱されている customer.db サンプル Paradox テーブルからのデータを表示する TTable は、実際にはそのテーブルの構造を変更しません (また、期待することもありません)。
これらの観察結果から結論できることは、DataSet 自体の内部構造は損なわれていないということです。その結果、DataSet の構造の二次表現がどこかにあると想定する必要があります。また、DataSet に関連付ける必要があります (DataSet のすべての使用でこれが必要なわけではないため、やり過ぎのように思われます)、DBGrid に関連付ける必要があります (DBGrid がこの機能を使用しているため、これはより理にかなっていますが、そうではありません)。 TField の並べ替えが DataSet 自体で持続するように見えるという観測によってサポートされています)、または別のものです。
もう 1 つの方法は、効果を TGridDataLink に関連付けることです。これは、複数行対応コントロール (DBGrid など) にデータ認識を提供するクラスです。ただし、このクラスは DataSet ではなくグリッドに関連付けられているため、この説明も拒否する傾向があります。これもまた、効果が DataSet クラス自体で持続するように見えるためです。
元の質問に戻ります。この効果は TDataSet クラスの内部にあるものですか、TDBGrid のアーティファクトですか、それとも何か他のものですか?
以下のコメントの1つに追加したことをここで強調することも許可してください. 何よりも、私の投稿は、列の順序を変更できる DBGrid を使用している場合、TField の順序も変更される可能性があることを開発者に認識させるように設計されています。このアーティファクトは、断続的で重大なバグを引き起こす可能性があり、特定と修正が非常に困難になる可能性があります。いいえ、これは Delphi のバグではないと思います。機能するように設計されているため、すべてが機能していると思います。私たちの多くが、この動作が発生していることに気づいていなかっただけです。今、私たちは知っています。