delphi – 在DBGrid中移动列似乎移动附加的DataSet字段

前端之家收集整理的这篇文章主要介绍了delphi – 在DBGrid中移动列似乎移动附加的DataSet字段前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我上周观察到我没有想到的,下面将会描述.我很好奇为什么会发生这种情况.它是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,两个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;

标记为Show ClientDataSet Structure的Button1包含以下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的列以重新排列字段的显示顺序.再次单击显示客户端数据集结构按钮.这次你会看到类似的东西:

The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - - 
Label
StartOfWeek
Active
Count

这个例子令人惊奇的是DBGrid的列正被移动,但是对ClientDataSet中的Fields的位置有明显的影响,使得ClientDataSet.Field [0]中的字段位于一个点不一定在那个瞬间.而且,不幸的是,这并不是一个ClientDataSet问题.我使用基于BDE的TTables和基于ADO的AdoTables进行了同样的测试,并得到了相同的效果.

如果您不需要引用显示在DBGrid中的ClientDataSet中的字段,则不必担心此效果.对于其余的人,我可以想到几个解决方案.

最简单的,虽然不是必需的,但避免此问题的最佳方法是防止用户重新排序DBGrid中的字段.这可以通过从DBGrid的Options属性删除dgResizeColumn标志来完成.虽然这种方法是有效的,但从用户的角度来看,它消除了潜在的有价值的显示选项.此外,删除此标志不仅限制列重新排序,还可以防止列大小调整. (要了解如何限制列重新排序而不删除列调整大小选项,请参阅http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm.)

第二个解决方法是避免基于它们的字面位置引用DataSet的字段(因为这是问题的本质).按顺序,如果您需要参考“计数”字段,请不要使用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做了什么,无论在零位置创建哪个字段,仍将处于该位置.

这在以下代码中演示,该代码标记显示克隆的客户端数据结构的按钮的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方法,则保存的结构是原始(true internal)结构.此外,如果将一个ClientDataSet的Data属性复制到另一个ClientDataSet,则目标ClientDataSet还将显示真实的结构(与克隆源ClientDataSet时观察到的结果相似).

类似地,绑定到其他测试数据集(包括TTable和AdoTable)的DBGrids的列顺序的更改实际上不会影响底层表的结构.例如,显示来自client.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;

将导致“Show ClientDataSet Structure”按钮的输出相应地改变,或者是否有一个网格. 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;
[...]

猜你在找的Delphi相关文章