我上周观察到一些我没有预料到的事情,下面将进行描述。我很好奇为什么会这样。它是TDataset类的内部内容、TDbgrid的工件还是其他内容?
打开的客户端数据集中字段的顺序已更改。具体来说,我在代码中创建了一个客户端数据集,方法是在使用fielddefs定义其结构后调用createdatatset。此ClientDataset结构中的第一个字段是名为StartOfWeek的日期字段。不久之后,我还编写了代码,它假定startofweek字段位于零的位置,clientdataset.fields[0]失败,因为startofweek字段不再是clientdataset中的第一个字段。
经过一些调查,我了解到在给定的时刻,clientdataset中的每个字段都可能出现在与创建clientdataset时的原始结构不同的位置。我不知道这会发生,谷歌的搜索也没有提到这种效果。
发生的不是魔法。字段本身并没有更改位置,也没有根据我在代码中所做的任何操作进行更改。导致字段在物理上看起来在clientdataset中改变位置的原因是,用户改变了clientdataset附加到的dbgrid中列的顺序(当然是通过datasource组件)。我在Delphi7、Delphi2007和Delphi2010中复制了这个效果。
我创建了一个非常简单的Delphi应用程序来演示这种效果。它由一个具有一个dbgrid、一个datasource、两个clientdataset和两个按钮的表单组成。此窗体的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结构,包含以下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的列以重新排列字段的显示顺序。再次单击“显示客户端数据集结构”按钮。这次,您将看到与此处所示类似的内容:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
Label
StartOfWeek
Active
Count
这个例子值得注意的是,dbgrid的列正在被移动,但是对clientDataSet中字段的位置有明显的影响,因此在某一点上clientDataSet.field[0]位置的字段不一定是在稍后移动的。不幸的是,这并不是一个明显的客户数据集问题。我使用基于bde的ttables和基于ado的adotables执行了相同的测试,并获得了相同的效果。
如果您不需要引用在DBGrid中显示的clientDataSet中的字段,那么您不必担心这种影响。对于你们中的其他人,我可以想出一些解决办法。
最简单的方法是防止用户对dbgrid中的字段重新排序,尽管这不是必需的。这可以通过从dbgrid的选项属性中删除dgResizeColumn标志来完成。虽然这种方法是有效的,但从用户的角度来看,它消除了一个潜在的有价值的显示选项。此外,删除此标志不仅限制了列的重新排序,还阻止了列的大小调整。(要了解如何在不删除列大小调整选项的情况下限制列重新排序,请参见
http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm
。)
第二种解决方法是避免根据数据集的文本位置引用其字段(因为这是问题的本质)。在order words中,如果需要引用count字段,请不要使用dataset.fields[2]。只要知道字段的名称,就可以使用类似于dataset.fieldbyname(“count”)的内容。
然而,使用fieldbyname有一个相当大的缺点。具体来说,此方法通过迭代数据集的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;
还有第三种解决方案,但是只有当您的数据集是一个客户端数据集时,这才可用,就像在我的原始示例中那样。在这些情况下,您可以创建原始客户端数据集的克隆,它将具有原始结构。因此,无论用户对显示客户端数据集数据的dbgrid做了什么,在zeroeth位置创建的任何字段都将仍然处于该位置。
下面的代码演示了这一点,该代码与标记为show cloned 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;
如果运行此项目并单击标记为“显示克隆的客户端数据集结构”的按钮,将始终获得客户端数据集的真正结构,如图所示
客户端数据集的结构1
——————————————————
每周开始时间
标签
伯爵
主动的
附录:
需要注意的是,基础数据的实际结构不会受到影响。具体来说,如果在更改了dbgrid中列的顺序后,调用了clientDataSet的saveToFile方法,则保存的结构是原始(真正的内部)结构。此外,如果将一个客户端数据集的数据属性复制到另一个客户端数据集,则目标客户端数据集也会显示真正的结构(这类似于克隆源客户端数据集时观察到的效果)。
同样,绑定到其他测试数据集(包括ttable和adotable)的dbgrid的列顺序的更改实际上不会影响基础表的结构。例如,一个显示来自与delphi一起提供的customer.db sample paradox表的数据的ttable实际上不会改变该表的结构(您也不会期望它改变)。
我们从这些观察中得出的结论是,数据集本身的内部结构保持不变。因此,我必须假设在某个地方存在数据集结构的二级表示。而且,它必须要么与数据集相关联(因为并非所有数据集的使用都需要它),要么与dbgrid相关联(因为dbgrid正在使用此功能,这更为合理,但观察到tfield重新排序似乎与数据集本身保持一致,则不支持它),要么是一些其他。
另一种选择是效果与tgridatalink相关,tgridatalink是一个类,它为多行感知控件(如dbgrids)提供数据感知。但是,我也倾向于拒绝这种解释,因为这个类与网格相关联,而不是与数据集相关联,因为这种效果似乎与数据集类本身相关联。
这让我回到了最初的问题。这是TDataset类的内部效果、TDbgrid的工件还是其他效果?
请允许我在这里强调一些我在下面的评论中添加的内容。更重要的是,我的文章旨在让开发人员意识到,当他们使用的dbgrids的列顺序可以更改时,他们的tfield的顺序也可能更改。这个工件可能会引入间歇性和严重的错误,这些错误很难识别和修复。而且,不,我不认为这是一个德尔福错误。我怀疑一切都按设计的那样工作。只是我们中的许多人不知道这种行为正在发生。现在我们知道了。