I observed something last week that I did not expect, and will describe below. I am curious as to why this happens. Is it something internal to the TDataSet class, an artifact o
Apparently the behaviour is by design. In fact it is not related to the dbgrid. It is merely a side effect of a column setting a field index. For instance this statement,
ClientDataSet1.Fields[0].Index := 1;
will cause the output of the "Show ClientDataSet Structure" button to change accordingly, either there is a grid or not. The documentation for TField.Index states;
"Change the order of a field's position in the dataset by changing the value of Index. Changing the Index value affects the order in which fields are displayed in data grids, but not the position of the fields in physical database tables."
One should conclude the reverse should also be true and changing the order of fields in a grid should cause field indexes to be changed.
The code causing this is in TColumn.SetIndex. TCustomDBGrid.ColumnMoved sets a new index for the moved column and TColumn.SetIndex sets the new index for that column's field.
procedure TColumn.SetIndex(Value: Integer);
[...]
if (Col <> nil) then
begin
Fld := Col.Field;
if Assigned(Fld) then
Field.Index := Fld.Index;
end;
[...]
Cary I think I've found a solution for this problem. Instead of using VCL wrapper Fields we need to use an internal Fields property of the Recordset COM object.
Here is how it should be referenced:
qry.Recordset.Fields.Item[0].Value
Those fields are NOT affected by the behaviour you have described earlier. So we can still refer to the fields by their index.
Test this out and tell me what was the result. It worked for me.
Edit:
Of course it will work only for ADO components, not for the TClientDataSet...
Edit2:
Cary I do not know if this is answer for your question, however I've been pushing folks on the embarcadero forums and Wayne Niddery gave me quite detailed answer about all this Fields movement.
To make a long story short: If you define your columns in TDBGrid explicitly, field indexes are not moving! Have a bit more sense now, hasn't it?
Read full thread here: https://forums.embarcadero.com/post!reply.jspa?messageID=197287
Wodzu posted a solution to the reordered Field problem that was specific to ADO DataSet, but he led me to a solution that is similar, and available for all DataSets (whether it is implemented properly in all DataSets is another issue). Note that neither this answer, nor Wodzu's, is actually an answer to the original question. Instead, it is a solution to the problem noted, whereas the question relates to where this artifact originates.
The solution that Wodzu's solution lead me to was FieldByNumber, and it is a method of the Fields property. There are two interesting aspects to the use of FieldByNumber. First, you must qualify its reference with the Fields property of your DataSet. Second, unlike the Fields array, which takes a zero-based indexer, FieldByNumber is a method that takes a one-based parameter to indicate the position of the TField you want to reference.
The following is an updated version of the Button1 event handler that I posted in my original question. This version uses 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;
For the sample project, this code produces the following output, regardless of the orientation of the Columns in the associated DBGrid:
The Structure of ClientDataSet1 using FieldByNumber
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
To repeat, notice that the reference to the underlying TField required FieldByNumber to be qualified with a reference to Fields. Furthermore, the parameter for this method must lie within the 1 to DataSet.FieldCount range. As a result, to refer to the first field in the DataSet, you use the following code:
ClientDataSet1.Fields.FieldByNumber(1)
Like the Fields array, FieldByNumber returns a TField reference. As a result, if you want to refer to a method that is specific to a particular TField class, you have to cast the returned value to the appropriate class. For example, to save the contents of a TBlobField to a file, you may have to do something like the following code:
TBlobField(MyDataSet.Fields.FieldByNumber(6)).SaveToFile('c:\mypic.jpg');
Note that I am not suggesting that you should reference TFields in a DataSet using integer literals. Personally, the use of a TField variable that gets initialized through a one time call to FieldByName is more readable, and is immune to changes in the physical order of a table's structure (though not immune to changes in the names of your fields!).
However, if you have DataSets associated with DBGrids whose Columns can be reordered, and you reference the fields of these DataSets using integer literals as indexers of the Fields array, you may want to consider converting your code to use the DataSet.Fields.FieldByName method.