代码之家  ›  专栏  ›  技术社区  ›  Cary Jensen

在dbgrid中移动列似乎会移动附加的数据集字段

  •  12
  • Cary Jensen  · 技术社区  · 16 年前

    我上周观察到一些我没有预料到的事情,下面将进行描述。我很好奇为什么会这样。它是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的顺序也可能更改。这个工件可能会引入间歇性和严重的错误,这些错误很难识别和修复。而且,不,我不认为这是一个德尔福错误。我怀疑一切都按设计的那样工作。只是我们中的许多人不知道这种行为正在发生。现在我们知道了。

    3 回复  |  直到 16 年前
        1
  •  3
  •   Sertac Akyuz    16 年前

    显然,这种行为是故意的。实际上,它与dbgrid无关。这只是设置字段索引的列的副作用。例如,这个语句,

    clientdataset1.fields[0].索引:=1;

    将导致“显示客户端数据集结构”按钮的输出相应更改,无论是否存在网格。tfield.index的文档说明;

    “通过更改索引的值来更改字段在数据集中的位置顺序。更改索引值会影响字段在数据网格中的显示顺序,但不会影响字段在物理数据库表中的位置。”

    我们应该得出结论,反过来也应该是正确的,改变网格中字段的顺序会导致字段索引发生变化。


    导致这种情况的代码在tcolumn.setindex中。tcustombgrid.column moved为移动的列设置新索引,tcolumn.setindex为该列的字段设置新索引。

    procedure TColumn.SetIndex(Value: Integer);
    [...]
            if (Col <> nil) then
            begin
              Fld := Col.Field;
              if Assigned(Fld) then
                Field.Index := Fld.Index;
            end;
    [...]
    
        2
  •  1
  •   Cary Jensen    16 年前

    Wodzu发布了一个针对ADO数据集的重新排序字段问题的解决方案,但他引导我找到了一个类似的解决方案,可用于所有数据集(无论是否在 全部的 数据集是另一个问题)。注意,无论是这个答案,还是Wodzu的答案,实际上都不是原始问题的答案。相反,它是对所指出的问题的解决方案,而这个问题与这个工件的起源有关。

    Wodzu的解把我引向的解是FieldByNumber,它是Fields属性的一种方法。FieldByNumber的使用有两个有趣的方面。首先,必须使用数据集的Fields属性限定其引用。第二,与采用基于零的索引器的字段数组不同,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');
    

    注意,我不建议您使用整型文本引用数据集中的tfield。就个人而言,通过一次性调用fieldbyname初始化的tfield变量的使用更具可读性,并且不受表结构物理顺序变化的影响(尽管不受字段名称变化的影响!).

    但是,如果有数据集与可以重新排序其列的dbgrids关联,并且使用整型文本作为字段数组的索引器引用这些数据集的字段,则可能需要考虑将代码转换为使用dataset.fields.fieldbyname方法。

        3
  •  1
  •   Wodzu    16 年前

    卡里,我想我找到了解决这个问题的办法。我们需要使用记录集COM对象的内部字段属性,而不是使用VCL包装字段。

    以下是如何引用它:

    qry.Recordset.Fields.Item[0].Value
    

    这些字段不受您前面描述的行为的影响。所以我们仍然可以通过字段的索引来引用它们。

    测试一下,告诉我结果如何。这对我很有用。

    编辑:

    当然,它只适用于ADO组件,而不适用于TClientDataSet…

    编辑2:

    卡里,我不知道这是否是你的问题的答案,但是我一直在推动人们在embarcadero论坛和韦恩·奈德瑞给了我非常详细的答案,关于所有这些领域的运动。

    长话短说: 如果在tdbgrid中显式定义列,则字段索引不会移动! 现在有点理智了,不是吗?

    阅读全文: https://forums.embarcadero.com/post!reply.jspa?messageID=197287

    推荐文章