我上周观察了一些我没有预料到的情况,下面将对此进行描述。我很好奇为什么会发生这种情况。 TDataSet类是TDBGrid的一个工件,还是其他东西?移动DBGrid中的列似乎移动了附加的数据集字段
打开的ClientDataSet中的字段顺序已更改。具体来说,我使用FieldDefs定义了它的结构后,通过调用CreateDatatSet在代码中创建了一个ClientDataSet。此ClientDataSet结构中的第一个字段是一个名为StartOfWeek的Date字段。不久之后,由于StartOfWeek字段不再是ClientDataSet中的第一个字段,因此我写过的代码假定StartOfWeek字段处于零位置ClientDataSet.Fields [0]失败。
经过一番调查后,我发现ClientDataSet中的每一个字段都有可能出现在与创建ClientDataSet时的原始结构不同的某个位置。我并不知道这可能会发生,Google上的搜索也没有提到这种效果。
发生了什么并不神奇。这些字段本身并没有改变位置,也没有根据我在代码中做的任何改变。是什么导致字段在物理上显示为在ClientDataSet中改变位置的原因是用户已经更改了ClientDataSet所连接到的DbGrid中的列的顺序(当然,通过DataSource组件)。我在Delphi 7,Delphi 2007和Delphi 2010中复制了这个效果。
我创建了一个非常简单的Delphi应用程序来演示这种效果。它由带有一个DBGrid的单一窗体,一个数据源,两个ClientDataSets和两个按钮组成。这种形式的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;
要演示运动场效应,请运行此应用程序并单击标记为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,使得位于一个点上的ClientDataSet.Field [0]位置的字段不一定会在那个时刻出现。不幸的是,这并不是一个明显的ClientDataSet问题。我使用基于BDE的TTables和基于ADO的AdoTables执行了相同的测试,并获得了相同的效果。
如果您永远不需要引用您的ClientDataSet中显示在DBGrid中的字段,那么您不必担心这种影响。对于其他人,我可以想到几种解决方案。
最简单但并非必要的避免此问题的最佳方法是防止用户重新排序DBGrid中的字段。这可以通过从DBGrid的Options属性中删除dgResizeColumn标志来完成。尽管这种方法很有效,但从用户的角度来看,它消除了潜在的宝贵显示选项。此外,删除此标志不仅会限制列重新排序,还会阻止列大小调整。 (要了解如何限制列重新排序而不删除列大小调整选项,请参阅http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm。)
第二种解决方法是避免基于字面位置引用DataSet的字段(因为这是问题的实质)。换句话说,如果您需要引用Count字段,请不要使用DataSet.Fields [2]。只要您知道该字段的名称,就可以使用类似DataSet.FieldByName('Count')的内容。
但是,使用FieldByName有一个相当大的缺点。具体而言,此方法通过迭代DataSet的Fields属性来标识字段,并根据字段名称查找匹配项。由于每次调用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;
还有第三种解决方案,但是这仅仅是可用当你的DataSet是一个ClientDataSet时,就像我原来的例子。在这些情况下,您可以创建原始ClientDataSet的克隆,并且它将具有原始结构。因此,无论用户对显示ClientDataSets数据的DBGrid执行了什么操作,无论哪个字段都处于零位状态,都将处于该位置。
这在以下代码中演示,该代码与标记为显示克隆的ClientDataSet结构的按钮的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的列顺序可以改变时,他们的TFields的顺序也可能发生变化。这个工件可能会引入间歇性和严重的错误,这些错误很难识别和修复。不,我不认为这是一个Delphi错误。我怀疑所有事情都是按照设计的方式工作的。只是我们很多人不知道这种行为正在发生。现在我们知道了。
非常丰富,但是在这里有一个问题吗? – 2009-12-31 06:21:23
感谢@Cary,我对此毫不知情,而且我经常使用DataSet.Field [x]构造。我想你应该在Embarcadero网站上报告它是一个错误。 – Wodzu 2009-12-31 09:33:26
有一个问题出现在第二句话中:“TDataSet类是内部的东西,TDBGrid的人工产物还是其他东西?“我花了一段时间(一个小时左右)搜索TCustomGrid和TDataSet源代码,但没有看到发生了什么。 更重要的是,这就是为什么我的帖子太长了,我至少想Delphi开发人员意识到这种有趣的行为,对于任何使用DBGrid或其他类似的网格,在TFields命令中产生这些更改的人来说,它可能是间歇性的,并且很难发现错误的来源 – 2009-12-31 16:24:58