上周我观察到了一些我没有预料到的事情,将在下面描述。我很好奇为什么会发生这种情况。它是 TDataSet 类内部的东西、TDBGrid 的工件还是其他东西?
打开的 ClientDataSet 中的字段顺序发生了变化。具体来说,我在使用 FieldDefs 定义其结构后,通过调用 CreateDatatSet 在代码中创建了一个 ClientDataSet。此 ClientDataSet 结构中的第一个字段是名为 StartOfWeek 的日期字段。不久之后,我也编写的代码(假设 StartOfWeek 字段位于第零位 ClientDataSet.Fields[0])失败了,因为 StartOfWeek 字段不再是 ClientDataSet 中的第一个字段。
经过一番调查,我了解到 ClientDataSet 中的每个字段都可能在特定时刻出现在与创建 ClientDataSet 时的原始结构不同的某个位置。我不知道这可能会发生,在谷歌上搜索也没有提到这种效果。
发生的事情不是魔术。这些字段本身并没有改变位置,也没有根据我在代码中所做的任何事情而改变。导致字段在 ClientDataSet 中物理出现位置变化的原因是用户更改了 ClientDataSet 附加到的 DbGrid 中列的顺序(当然是通过 DataSource 组件)。我在 Delphi 7、Delphi 2007 和 Delphi 2010 中复制了这种效果。
我创建了一个非常简单的 Delphi 应用程序来演示这种效果。它由一个带有一个 DBGrid、一个 DataSource、两个 ClientDataSet 和两个 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;
Button1(标记为 Show ClientDataSet Structure)包含以下 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;
要演示移动场效果,请运行此应用程序并单击标有 Show ClientDataSet Structure 的按钮。您应该会看到类似此处所示的内容:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
接下来,拖动 DBGrid 的列来重新排列字段的显示顺序。再次单击 Show ClientDataSet Structure 按钮。这次您将看到与此处显示的内容类似的内容:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
Label
StartOfWeek
Active
Count
此示例的显着之处在于 DBGrid 的列正在移动,但对 ClientDataSet 中 Fields 的位置有明显影响,因此 ClientDataSet.Field[0] 中的字段位置为 1点不一定在片刻之后。而且,不幸的是,这显然不是 ClientDataSet 问题。我对基于 BDE 的 TTables 和基于 ADO 的 AdoTables 进行了相同的测试,得到了相同的效果。
如果您从不需要引用在 DBGrid 中显示的 ClientDataSet 中的字段,那么您不必担心这种影响。对于你们其他人,我可以想到几个解决方案。
避免此问题的最简单但不必要的优选方法是防止用户重新排序 DBGrid 中的字段。这可以通过从 DBGrid 的 Options 属性中删除 dgResizeColumn 标志来完成。虽然这种方法是有效的,但从用户的角度来看,它消除了潜在的有价值的显示选项。此外,删除此标志不仅会限制列重新排序,还会阻止调整列大小。(要了解如何在不删除列调整大小选项的情况下限制列重新排序,请参阅http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm。)
第二种解决方法是避免根据字面位置引用数据集的字段(因为这是问题的本质)。换句话说,如果您需要引用 Count 字段,请不要使用 DataSet.Fields[2]。只要您知道字段的名称,就可以使用 DataSet.FieldByName('Count') 之类的名称。
然而,使用 FieldByName 有一个相当大的缺点。具体来说,此方法通过遍历 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;
还有第三种解决方案,但这仅在您的 DataSet 是 ClientDataSet 时可用,就像我原来的示例中的那个一样。在这些情况下,您可以创建原始 ClientDataSet 的克隆,它将具有原始结构。因此,无论用户对显示 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 时观察到的效果)。
同样,对绑定到其他测试数据集(包括 TTable 和 AdoTable)的 DBGrid 的列顺序的更改实际上不会影响基础表的结构。例如,显示来自 Delphi 附带的 customer.db 示例 Paradox 表的数据的 TTable 实际上并没有改变该表的结构(您也不希望它改变)。
我们可以从这些观察中得出结论,DataSet 本身的内部结构保持不变。因此,我必须假设在某处存在 DataSet 结构的二级表示。而且,它必须与 DataSet 相关联(这似乎有点过头了,因为并非所有使用 DataSet 都需要这个),与 DBGrid 相关联(这更有意义,因为 DBGrid 正在使用此功能,但不是由 TField 重新排序似乎与 DataSet 本身持续存在的观察结果支持),或者是其他东西。
另一种选择是效果与 TGridDataLink 相关联,TGridDataLink 是为多行感知控件(如 DBGrids)提供数据感知的类。但是,我也倾向于拒绝这种解释,因为这个类与网格相关联,而不是与 DataSet 相关联,因为效果似乎与 DataSet 类本身有关。
这让我回到了最初的问题。这种效果是 TDataSet 类内部的东西、TDBGrid 的工件还是其他东西?
请允许我在这里强调一些我添加到以下评论之一的内容。最重要的是,我的帖子旨在让开发人员意识到,当他们使用可以更改列顺序的 DBGrid 时,他们的 TField 的顺序也可能会发生变化。该工件可能会引入难以识别和修复的间歇性和严重错误。而且,不,我不认为这是一个 Delphi 错误。我怀疑一切都按照设计的方式工作。只是我们中的许多人都不知道这种行为正在发生。现在我们知道了。