Car*_*sen 12 delphi tclientdataset tdbgrid tdataset
我上周观察到了一些我没想到的事情,并将在下面描述.我很好奇为什么会这样.它是TDataSet类内部的东西,TDBGrid的工件还是别的东西?
打开的ClientDataSet中的字段顺序已更改.具体来说,我在使用FieldDefs定义其结构后通过调用CreateDatatSet在代码中创建了一个ClientDataSet.此ClientDataSet结构中的第一个字段是名为StartOfWeek的Date字段.不久之后,我编写的代码(假设StartOfWeek字段位于零位置,ClientDataSet.Fields [0])失败,因为StartOfWeek字段不再是ClientDataSet中的第一个字段.
经过一番调查后,我了解到ClientDataSet中的每个字段都可能在给定时刻出现在创建ClientDataSet时与原始结构不同的某个位置.我不知道这可能会发生,并且对谷歌的搜索也没有提到这种效果.
发生了什么不是魔术.这些字段本身并没有改变位置,也没有根据我在代码中所做的任何事情而改变.导致字段在物理上看起来改变ClientDataSet中位置的原因是用户已经更改了ClientDataSet附加到的DbGrid中的列的顺序(当然是通过DataSource组件).我在Delphi 7,Delphi 2007和Delphi 2010中复制了这个效果.
我创建了一个非常简单的Delphi应用程序来演示这种效果.它由一个包含一个DBGrid,一个DataSource,两个ClientDataSets和两个Buttons的表单组成.此表单的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;
Run Code Online (Sandbox Code Playgroud)
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;
Run Code Online (Sandbox Code Playgroud)
要演示移动场效果,请运行此应用程序并单击标记为Show ClientDataSet Structure的按钮.您应该看到如下所示的内容:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
Run Code Online (Sandbox Code Playgroud)
接下来,拖动DBGrid的列以重新排列字段的显示顺序.再次单击"显示ClientDataSet结构"按钮.这次你会看到类似于这里显示的东西:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
Label
StartOfWeek
Active
Count
Run Code Online (Sandbox Code Playgroud)
这个例子的显着之处在于DBGrid的Columns正在被移动,但是ClientDataSet中的Fields的位置有明显的影响,因此ClientDataSet.Field [0]中的Field位于一个位置点不一定会在那之后.而且,遗憾的是,这并不是ClientDataSet问题.我使用基于BDE的TTables和基于ADO的AdoTables进行了相同的测试并获得了相同的效果.
如果您永远不需要引用在DBGrid中显示的ClientDataSet中的字段,那么您不必担心这种影响.对于其他人,我可以想到几个解决方案.
避免此问题的最简单但不是必需的优选方法是防止用户重新排序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时都会执行此操作,因此在需要多次引用字段的情况下(例如在导航大型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;
Run Code Online (Sandbox Code Playgroud)
还有第三种解决方案,但这仅在您的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;
Run Code Online (Sandbox Code Playgroud)
如果您运行此项目并单击标记为显示克隆ClientDataSet结构的按钮,您将始终获得ClientDataSet的真实结构,如此处所示
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
Run Code Online (Sandbox Code Playgroud)
附录:
重要的是要注意底层数据的实际结构不受影响.具体来说,如果在更改DBGrid中列的顺序后,调用ClientDataSet的SaveToFile方法,则保存的结构是原始(真正的内部)结构.此外,如果将一个ClientDataSet的Data属性复制到另一个ClientDataSet,则目标ClientDataSet也会显示真实结构(类似于克隆源ClientDataSet时观察到的效果).
同样,绑定到其他测试数据集(包括TTable和AdoTable)的DBGrids列顺序的更改实际上不会影响基础表的结构.例如,显示来自Delphi附带的customer.db示例Paradox表的数据的TTable实际上并不会更改该表的结构(您也不会期望它).
我们从这些观察中得出的结论是,DataSet本身的内部结构保持不变.因此,我必须假设某个地方存在DataSet结构的辅助表示.并且,它必须与DataSet相关联(这似乎是过度杀伤,因为并非所有DataSet的使用都需要这个),与DBGrid相关联(这更有意义,因为DBGrid使用此功能,但不是支持TField重新排序似乎与DataSet本身持续存在的观点,或者是其他东西.
另一种选择是效果与TGridDataLink相关联,TGridDataLink是为多行感知控件(如DBGrids)提供数据感知的类.但是,我倾向于拒绝这种解释,因为这个类与网格相关联,而不是DataSet,因为效果似乎与DataSet类本身一致.
这让我回到原来的问题.这个效果是TDataSet类内部的东西,TDBGrid的一个工件还是别的东西?
请允许我在这里强调一些我在下面的评论中添加的内容.最重要的是,我的帖子旨在让开发人员意识到,当他们使用DBGrids时,其列顺序可以更改,其TField的顺序也可能会发生变化.此工件可能会引入间歇性和严重的错误,这些错误很难识别和修复.而且,不,我不认为这是一个Delphi错误.我怀疑一切都在工作,因为它的设计工作.只是我们很多人都没有意识到这种行为正在发生.现在我们知道了.
显然这种行为是设计使然的。事实上它与dbgrid无关。它只是列设置字段索引的副作用。比如这个声明,
ClientDataSet1.Fields[0].Index := 1;
将导致“显示 ClientDataSet 结构”按钮的输出相应改变,无论是否有网格。TField.Index 的文档指出;
“通过更改索引值来更改字段在数据集中的位置顺序。更改索引值会影响字段在数据网格中显示的顺序,但不会影响字段在物理数据库表中的位置。”
人们应该得出相反的结论也应该成立,并且更改网格中字段的顺序应该会导致字段索引发生更改。
导致此问题的代码位于 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;
[...]
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
5418 次 |
最近记录: |