dbgridで列を移動すると、添付のデータセットフィールドを移動するようです
-
22-09-2019 - |
質問
私は先週、私が期待していなかった何かを観察し、以下で説明します。なぜこれが起こるのか興味があります。 Tdatasetクラスの内部のもの、Tdbgridのアーティファクト、または何か他のものですか?
オープンクライアントダタセットのフィールドの順序が変更されました。具体的には、fielddefsを使用してその構造を定義した後、CreatedAtatsetを呼び出すことにより、CodeClientDataSetをコードで作成しました。このClientDataSetの構造の最初のフィールドは、StartofWeekという名前の日付フィールドでした。ほんの少し後に、私が書いたコードもあります。これは、StartWeekフィールドがclientDatasetの最初のフィールドではなくなったため、StartWeekフィールドがゼロエス位置であるClientDataset.Fields [0]が失敗したと仮定しました。
いくつかの調査の後、私は、クライアントダタセットのすべてのフィールドが、クライアントダタセットが作成された時点で、元の構造とは異なる位置に表示される可能性があることを学びました。私はこれが起こる可能性があることを知りませんでした、そして、Googleでの検索もこの効果について言及しませんでした。
起こったことは魔法ではありませんでした。フィールドは自分でポジションを変更しませんでしたし、コードで行ったことに基づいて変化しませんでした。フィールドがクライアントダタセットの位置を物理的に変更したように見えるのは、ユーザーがクライアントダタセットが接続されているdbgridの列の順序を変更したことです(もちろん、データソースコンポーネントを介して)。この効果は、Delphi 7、Delphi 2007、およびDelphi 2010で再現しました。
この効果を示す非常にシンプルなDelphiアプリケーションを作成しました。 1つのdbgrid、データソース、2つのクライアントダタセット、2つのボタンを備えた単一のフォームで構成されています。このフォームの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;
Button1は、clientDataSetの構造を表示しているラベルに付属しており、次の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;
移動フィールド効果を示すには、このアプリケーションを実行し、[クライアントダタセットの構造を表示]というラベルの付いたボタンをクリックします。あなたはここに示されているようなものを見るはずです:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
次に、dbgridの列をドラッグして、フィールドの表示順序を再配置します。 [ClientDataSetのShow Client Structure]ボタンをもう一度クリックします。今回は、ここに示されているものに似たものが表示されます。
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
Label
StartOfWeek
Active
Count
この例について注目に値するのは、dbgridの列が移動されていることですが、clientDatasetのフィールドの位置に明らかな影響があり、クライアントダタセット[0]の位置にあるフィールドが1つであることです。ポイントは必ずしもしばらくしてからではありません。そして、残念ながら、これは明確にクライアントダタセットの問題ではありません。私はBDEベースのTTABLESとADOベースの添付物質で同じテストを実行し、同じ効果を得ました。
dbgridに表示されているクライアントダタセットのフィールドを参照する必要がない場合は、この効果を心配する必要はありません。残りの部分では、いくつかの解決策を考えることができます。
この問題を回避するための望ましい方法は最も単純ですが、ユーザーがdbgridでフィールドを並べ替えるのを防ぐことです。これは、dbgridのオプションプロパティからdgresizecolumnフラグを削除することで実行できます。このアプローチは効果的ですが、ユーザーの観点からは、潜在的に価値のあるディスプレイオプションを排除します。さらに、このフラグを削除すると、列の並べ替えが制限されるだけでなく、列のサイズ変更を防ぎます。 (列のサイズ変更オプションを削除せずに列の並べ替えを制限する方法を学ぶには、参照してください http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm.)
2番目の回避策は、文字通りの位置に基づいてデータセットのフィールドを参照することを避けることです(これが問題の本質であるため)。言葉で言えば、カウントフィールドを参照する必要がある場合は、dataset.fields [2]を使用しないでください。フィールドの名前を知っている限り、dataset.fieldbyname( 'count')のようなものを使用できます。
ただし、FieldByNameの使用にはかなり大きな欠点があります。具体的には、このメソッドは、データセットのフィールドプロパティを反復し、フィールド名に基づいた一致を探してフィールドを識別します。 FieldByNameを呼び出すたびにこれを行うため、これは、大きなデータセットをナビゲートするループなど、フィールドを何度も参照する必要がある状況で避けるべき方法です。
フィールドを繰り返し参照する必要がある場合(および多数の回数)、次のコードスニペットのようなものを使用することを検討してください。
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番目のソリューションがありますが、これは、私の元の例のように、データセットがclientDatasetである場合にのみ使用できます。これらの状況では、元のClientDataSetのクローンを作成でき、元の構造があります。その結果、ClientDatasetsデータを表示するDBGridに対してユーザーが何をしたかに関係なく、ゼロエス位置に作成されたフィールドは、その位置にまだその位置にあります。
これは、次のコードで実証されています。これは、クライアントダタセット構造を表示したボタンのラベルの付いたボタンのオンクリックイベントハンドラーに関連付けられています。
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;
このプロジェクトを実行して、クローン式クライアントダタセット構造を表示したボタンラベルのあるボタンをクリックすると、ここに示すように、常にクライアントダタセットの真の構造が得られます。
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
補遺:
基礎となるデータの実際の構造は影響を受けないことに注意することが重要です。具体的には、dbgridで列の順序を変更した後、clientDatasetのsavetofileメソッドと呼ばれる場合、保存された構造は元の(真の内部)構造です。また、あるClientDatasetのデータプロパティを別のClientDatasetにコピーすると、宛先ClientDataSetは真の構造も表示されます(これは、Source ClientDataSetがクローン化されたときに観察される効果に似ています)。
同様に、ttableやAdotableを含む他のテスト済みデータセットにバインドされたdbgridの列順序の変更は、実際には基礎となるテーブルの構造に影響しません。たとえば、customer.dbサンプルのパラドックステーブルからデータを表示するttableは、実際にそのテーブルの構造を変更しません(また予想しません)。
これらの観察から結論付けることができるのは、データセット自体の内部構造がそのままのままであるということです。その結果、データセットの構造の二次表現がどこかにあると仮定する必要があります。そして、それはデータセットに関連付けられている必要があります(データセットのすべての用途がこれを必要とするわけではないので、これは過剰に思われるでしょう)。 Tfieldの並べ替えがデータセット自体に持続しているように見えるという観察によってサポートされています)、または他のものです。
もう1つの選択肢は、効果がTgridDatalinkに関連付けられていることです。これは、データ認識(DBGridsなど)をマルチロウアウェアコントロールに提供するクラスです。ただし、このクラスはデータセットではなくグリッドに関連付けられているため、この説明も拒否する傾向があります。
それは私を元の質問に戻します。この効果は、Tdatasetクラスの内部の何か、Tdbgridのアーティファクト、または何か他のものですか?
また、以下のコメントのいずれかに追加した何かをここで強調することを許可してください。何よりも、私の投稿は、開発者が、列の順序を変更できるDBGridを使用している場合、Tfieldの順序も変更されていることを認識させるように設計されています。このアーティファクトは、断続的で深刻なバグを導入できます。これは、特定して修正するのが非常に難しい場合があります。そして、いや、これはデルファイのバグだとは思わない。私は、それが機能するように設計されているので、すべてが機能しているのではないかと思います。私たちの多くが、この行動が起こっていることを知らなかったということです。今、私たちは知っています。
解決
どうやら動作は設計によるものです。実際、それはdbgridとは関係ありません。これは、フィールドインデックスを設定する列の副作用にすぎません。たとえば、この声明、
clientDataset1.fields [0] .index:= 1;
グリッドがあるかどうかにかかわらず、「clientDataSet構造を表示」ボタンの出力をそれに応じて変更します。 tfield.index Statesのドキュメント。
「インデックスの値を変更することにより、データセット内のフィールドの位置の順序を変更します。インデックス値を変更すると、フィールドがデータグリッドに表示される順序に影響しますが、物理データベーステーブルのフィールドの位置には影響しません。」
逆も真実であると結論付ける必要があり、グリッド内のフィールドの順序を変更すると、フィールドインデックスが変更されます。
これを引き起こすコードは、tcolumn.setIndexにあります。 tcustomdbgrid.columnmovedは、移動列とtcolumn.setindexの新しいインデックスを設定します。その列のフィールドの新しいインデックスを設定します。
procedure TColumn.SetIndex(Value: Integer);
[...]
if (Col <> nil) then
begin
Fld := Col.Field;
if Assigned(Fld) then
Field.Index := Fld.Index;
end;
[...]
他のヒント
Wodzuは、ADOデータセットに固有の並べ替えられたフィールドの問題に対するソリューションを投稿しましたが、彼は私を類似したソリューションに導きました。 全て データセットは別の問題です)。この答えもWodzuも、実際には元の質問に対する答えではないことに注意してください。代わりに、それは指摘された問題の解決策であり、質問はこのアーティファクトがどこから来るかに関連しています。
Wodzuの解決策が私に導くソリューションはFieldByNumberであり、それはFieldsプロパティの方法です。 FieldByNumberの使用には、2つの興味深い側面があります。まず、データセットの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への参照がフィールドへの参照でフィールドバイナンバーが資格を取得する必要があることに注意してください。さらに、このメソッドのパラメーターは、1からDataSet.FieldCount範囲内にある必要があります。その結果、データセットの最初のフィールドを参照するには、次のコードを使用します。
ClientDataSet1.Fields.FieldByNumber(1)
Fieldsアレイと同様に、FieldByNumberはTfieldリファレンスを返します。その結果、特定のTfieldクラスに固有のメソッドを参照する場合は、適切なクラスに返された値をキャストする必要があります。たとえば、tblobfieldの内容をファイルに保存するには、次のコードのようなことを行う必要がある場合があります。
TBlobField(MyDataSet.Fields.FieldByNumber(6)).SaveToFile('c:\mypic.jpg');
整数リテラルを使用してデータセット内のTfieldsを参照することを提案していないことに注意してください。個人的には、FieldByNameへの1回の呼び出しで初期化されるTfield変数の使用は、より読みやすく、テーブルの構造の物理的順序の変化に免疫があります(ただし、フィールドの名前の変化には免疫がありません!)。
ただし、列を並べ替えることができるdbgridsに関連付けられたデータセットがあり、整数リテラルを使用してこれらのデータセットのフィールドをフィールドアレイのインデクサーとして参照する場合、データセットを使用するようにコードを変換することを検討することができます。 。
ケアリーこの問題の解決策を見つけたと思います。 VCLラッパーフィールドを使用する代わりに、RecordSet COMオブジェクトの内部フィールドプロパティを使用する必要があります。
参照する方法は次のとおりです。
qry.Recordset.Fields.Item[0].Value
これらのフィールドは、前述の動作の影響を受けません。そのため、インデックスでフィールドを参照できます。
これをテストして、結果がどうなったのか教えてください。それは私のために働いた。
編集:
もちろん、TclientDatasetではなく、ADOコンポーネントでのみ機能します...
編集2:
Cary私はこれがあなたの質問に対する答えであるかどうかはわかりませんが、私はEmbarcaderoフォーラムで人々をプッシュしてきました、そして、Wayne Nidderyは私にこのすべての分野の動きについて非常に詳細な答えをくれました。
長い話を短くします: tdbgridで列を明示的に定義すると、フィールドインデックスが動いていません! もう少し感覚がありますよね?
ここで完全なスレッドを読む:https://forums.embarcadero.com/post!reply.jspa?messageid=197287