在一个DBGrid移动列似乎移动连接数据集字段
-
22-09-2019 - |
题
上周我看到的东西,我没想到的,并在下文描述。我很好奇,为什么发生这种情况。它是在数据集组件类的东西内部,在TDBGrid中的神器,还是别的什么?
字段的在开放的ClientDataSet的顺序改变。具体而言,我通过使用FieldDefs定义其结构之后调用CreateDatatSet创建代码中的ClientDataSet。在此的ClientDataSet的结构的第一个字段是一个名为StartOfWeek日期字段。只有片刻之后,代码,我也写了,这假设StartOfWeek场是在第零位置,ClientDataSet.Fields [0],失败了,因为StartOfWeek领域已不再是ClientDataSet的第一个字段。
经过一番调查后,我才知道这是可能的,在ClientDataSet的威力任何一个领域,在给定时刻,出现在从原来的结构在那个ClientDataSet的创建时的一些位置不同。我不知道,这可能发生,而且在谷歌搜索不转了这种效应的任何提任。
实际情况是不是魔术。本场也不会自己改变位置,也不是基于什么,我在我的代码确实没有他们改变。什么原因造成的字段以物理出现在ClientDataSet的变化位置是用户已经改变到ClientDataSet的附着在一个DBGrid的列的顺序(通过数据源组件,当然)。我复制的这种效果在Delphi 7,德尔福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;
要展示移动场效应,运行该应用程序,并单击按钮标记显示ClientDataSet的结构。您应该看到类似的东西如下:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
接着,拖动的DBGrid的列重新安排的场的显示顺序。再次单击显示ClientDataSet的结构按钮。这时候你会看到类似的东西如下:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
Label
StartOfWeek
Active
Count
什么是显着的这个例子是DBGrid中的列被移动,但对字段中ClientDataSet的,的位置的明显效果,使得在所述ClientDataSet.Field领域[0]在一个点的位置不一定有片刻之后。而且,不幸的是,这不是明显的一个ClientDataSet的问题。余进行与基于BDE-TTables和基于ADO的AdoTables相同的试验,得到了同样的效果。
如果你永远需要参考您的ClientDataSet的字段显示在一个DBGrid,那么你就不必对这种影响的担心。对于其余的你,我能想到的几种解决方案。
的最简单的,尽管不是必要的优选方式,以避免这个问题是防止用户在重排序一个DBGrid字段。这可以通过从DBGrid中的选项属性中删除dgResizeColumn标志来完成。虽然这种方法是有效的,它消除了潜在的有价值的显示选项,从用户的角度来看。此外,除去该标志不仅限制重新排序列,防止列大小调整。 (要了解如何限制重新排序列不删除列调整选项,请参见 HTTP:/ /delphi.about.com/od/adptips2005/a/bltip0105_2.htm 。)
在第二解决方法是避免指一个数据集的字段基于它们的字面位置(因为这是问题的本质)。为了也就是说,如果你需要参考凑NT字段,不使用DataSet.Fields [2]。只要你知道字段的名称,你可以使用像DataSet.FieldByName(“计数”)。
有一个相当大的缺点,以使用FieldByName的,但是。具体而言,该方法通过迭代通过DataSet的字段属性标识字段,寻找基于字段名称匹配。由于它这样做每次调用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的克隆,这将有原来的结构。其结果是,无论哪个字段是创建在第零位置仍然会在该位置,不管是什么用户已经完成到一个DBGrid显示的ClientDataSets数据。
这证明在下面的代码,它是用标记显示克隆的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到另一个的数据属性中,目的地的ClientDataSet还示出了真实结构(其类似于效果观察当源ClientDataSet的克隆)。
类似地,改变结合于其他测试数据集,包括的TTable和AdoTable DBGrids,的列顺序实际上并不影响根本的表的结构。例如,TTable的从样品CUSTOMER.DB Paradox表显示数据附带的Delphi实际上并不改变该表的结构(也没有你希望它)。
我们可以从这些观察中得出结论的是,该数据集本身的内部结构保持完整。其结果是,我必须假设有DataSet的结构某处的副代表。而且,它必须是用DataSet(这似乎是矫枉过正,因为没有一个数据集的所有使用需要这个),与DBGrid中(这更有意义,因为DBGrid中使用该功能相关的关联,但不通过观察结果支持了TField重新排序似乎与数据集本身)持续下去,或者是别的东西。
另一种选择是,该效果与TGridDataLink,这是给多行感知控件(如DBGrids)类及其数据意识相关联。然而,我倾向于拒绝这个解释为好,因为这类与电网相关联的,而不是数据集,再次因为效果似乎与数据集的类坚持他们自己。
这让我回到原来的问题。这是影响一些内部的数据集组件类,在TDBGrid中的神器,还是别的什么?
也请允许我强调的东西在这里,我加入的下面的评论之一。最重要的是,我的帖子的目的是使开发人员知道,当他们使用DBGrids其列顺序是可以改变的,他们中tfields的顺序也可以改变。这神器可以引入间歇性和严重的bug其中c一个非常难以识别和修复。而且,不,我不认为这是一个Delphi错误。我怀疑一切工作,因为它的设计工作。这只是我们很多人并不知道,这种行为发生。现在我们知道了。
解决方案
显然的行为是由设计。事实上,它是不相关的DBGrid的。它仅仅是一个列设定的场索引的副作用。例如,这声明,
ClientDataSet1.Fields [0]的.index:= 1;
将导致“查看ClientDataSet的结构”按钮的输出来相应地改变,无论是有一个网格。对于TField.Index文档状态;
“更改数据集中的字段的位置的通过改变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;
[...]
其他提示
Wodzu发布了解决方案,这是特定于ADO DataSet中的排序场问题,但他带领我到一个解决方案是相似的,并适用于所有数据集(无论它是在正确实施的所有的数据集是另一个问题)。请注意,无论是这个答案,也没有Wodzu的,实际上是一个答案,原来的问题。相反,它是指出问题的解决方案,而问题涉及到在那里此工件起源。
在解决方案,Wodzu的溶液导致我是FieldByNumber,并且它是Fields属性的方法。有两个有趣的方面的使用FieldByNumber的。首先,你必须符合它与DataSet中的字段属性引用。其次,字段阵列,这需要一个从零开始的索引不同,FieldByNumber是,需要一个基于一个参数,以指示要参考TField的位置的方法。
以下是我发表在我原来的问题Button1的事件处理程序的更新版本。此版本使用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;
有关示例项目,此代码产生以下输出,而不管相关联的DBGrid列的取向的:
The Structure of ClientDataSet1 using FieldByNumber
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
要重复,通知,参考到底层TField需要FieldByNumber是合格与以场的参考。此外,此方法的参数必须位于1至DataSet.FieldCount范围内。其结果是,指在第一字段中的数据集,可以使用以下代码:
ClientDataSet1.Fields.FieldByNumber(1)
字段阵列一样,FieldByNumber返回TField参考。因此,如果你想指的是针对特定的TField类中的方法,你必须返回的值转换成相应的类。例如,为了节省TBlobField到一个文件中的内容,您可能需要做类似下面的代码:
TBlobField(MyDataSet.Fields.FieldByNumber(6)).SaveToFile('c:\mypic.jpg');
请注意,我不是建议你应该在DataSet中采用整型直接引用中tfields。就个人而言,使用的是被通过一次调用FieldByName初始化的TField变量是更具可读性,并且免疫于表的结构的物理顺序的变化(虽然不能避免在您的字段的名称变化!)。
但是,如果你有DBGrids其列可以被重新排序相关联的数据集,并且您引用使用整数常量作为字段阵列的索引这些数据集的字段,则可能要考虑转换代码以使用所述DataSet.Fields .FieldByName方法。
卡里,我想我已经找到了一个解决这个问题。代替使用VCL包装领域,我们需要使用记录COM对象的内部属性字段
下面是应该如何被引用:
qry.Recordset.Fields.Item[0].Value
这些领域不会受到你所描述的早期行为。因此,我们仍然可以通过他们的指数是指等领域。
测试了这一点,并告诉我究竟是什么结果。它为我工作。
编辑:
当然它将只工作于ADO组件,而不是将TClientDataSet ...
EDIT2:
卡里,我不知道这是你的问题的答案,但我已经对英巴卡迪诺论坛推动乡亲和韦恩Niddery给了我这一切的场运动相当详细的解答。
为了使长话短说:?如果您在TDBGrid中定义的列明确,现场索引不移动现在有一点更有意义,不是吗
阅读全螺纹的位置: https://forums.embarcadero.com/post!reply.jspa?messageID= 197287