12

先週、私が予期していなかったことがわかりました。以下に説明します。なぜこれが起こるのか、私は興味があります。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 のバグではないと思います。機能するように設計されているため、すべてが機能していると思います。私たちの多くが、この動作が発生していることに気づいていなかっただけです。今、私たちは知っています。

4

3 に答える 3

1

Cary この問題の解決策を見つけたと思います。VCL ラッパー フィールドを使用する代わりに、Recordset COM オブジェクトの内部フィールド プロパティを使用する必要があります。

参照方法は次のとおりです。

qry.Recordset.Fields.Item[0].Value

これらのフィールドは、以前に説明した動作の影響を受けません。そのため、インデックスでフィールドを参照できます。

これをテストして、結果を教えてください。それは私のために働いた。

編集:

もちろん、TClientDataSet ではなく、ADO コンポーネントに対してのみ機能します...

編集2:

Cary これがあなたの質問に対する答えかどうかはわかりませんが、私は embarcadero フォーラムで人々に働きかけてきました.Wayne Niddery は、この Fields の動きすべてについて非常に詳細な回答をくれました.

簡単に言うと、TDBGrid で列を明示的に定義すると、フィールド インデックスは移動しません。もう少し理にかなっていますよね?

ここでスレッド全体を読む: https://forums.embarcadero.com/post!reply.jspa?messageID=197287

于 2009-12-31T09:52:16.330 に答える
1

Wodzu は、ADO DataSet に固有の並べ替えフィールドの問題に対する解決策を投稿しましたが、同様の解決策をすべての DataSet で利用できるように導きました (すべての DataSet で適切に実装されているかどうか別の問題です)。この回答もWodzuの回答も、実際には元の質問に対する回答ではないことに注意してください。代わりに、これは指摘された問題の解決策ですが、問題はこのアーティファクトの発生源に関連しています。

Wodzu のソリューションが導いてくれたソリューションは FieldByNumber で、これは Fields プロパティのメソッドです。FieldByNumber の使用には、興味深い点が 2 つあります。まず、その参照を DataSet の Fields プロパティで修飾する必要があります。第 2 に、0 から始まるインデクサーを取る Fields 配列とは異なり、FieldByNumber は、参照する TField の位置を示すために 1 から始まるパラメーターを取るメソッドです。

以下は、元の質問に投稿した Button1 イベント ハンドラーの更新版です。このバージョンは FieldByNumber を使用します。

procedure TForm1.Button1Click(Sender: TObject);
var
  sl: TStringList;
  i: Integer;
begin
  sl := TStringList.Create;
  try
    sl.Add('The Structure of ' + ClientDataSet1.Name +
      ' using FieldByNumber');
    sl.Add('- - - - - - - - - - - - - - - - - ');
    for i := 0 to ClientDataSet1.FieldCount - 1 do
      sl.Add(ClientDataSet1.Fields.FieldByNumber(i + 1).FieldName);
    ShowMessage(sl.Text);
  finally
    sl.Free;
  end;
end;

サンプル プロジェクトの場合、このコードは、関連付けられた DBGrid 内の列の方向に関係なく、次の出力を生成します。

The Structure of ClientDataSet1 using FieldByNumber
- - - - - - - - - - - - - - - - - 
StartOfWeek
Label
Count
Active

繰り返しますが、基になる TField への参照には、Fields への参照で FieldByNumber を修飾する必要があることに注意してください。さらに、このメソッドのパラメーターは、1 から DataSet.FieldCount の範囲内にある必要があります。その結果、DataSet の最初のフィールドを参照するには、次のコードを使用します。

ClientDataSet1.Fields.FieldByNumber(1)

Fields 配列と同様に、FieldByNumber は TField 参照を返します。そのため、特定の TField クラスに固有のメソッドを参照する場合は、返された値を適切なクラスにキャストする必要があります。たとえば、TBlobField の内容をファイルに保存するには、次のようなコードを実行する必要がある場合があります。

TBlobField(MyDataSet.Fields.FieldByNumber(6)).SaveToFile('c:\mypic.jpg');

整数リテラルを使用して DataSet 内の TField を参照するように提案しているわけではないことに注意してください。個人的には、FieldByName への 1 回の呼び出しで初期化される TField 変数を使用する方が読みやすく、テーブル構造の物理的な順序の変更の影響を受けません (ただし、フィールドの名前の変更の影響を受けません!)。

ただし、列を並べ替えることができる DBGrid に関連付けられた DataSet があり、Fields 配列のインデクサーとして整数リテラルを使用してこれらの DataSet のフィールドを参照する場合は、DataSet.Fields.FieldByName メソッドを使用するようにコードを変換することを検討してください。 .

于 2009-12-31T17:49:17.450 に答える