在DBGrid中移动列似乎可以移动附加的DataSet字段 [英] Moving Columns in a DBGrid seems to move the attached DataSet fields

查看:57
本文介绍了在DBGrid中移动列似乎可以移动附加的DataSet字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我上周观察到了一些我没想到的事情,下面将对此进行描述.我很好奇为什么会这样.是TDataSet类内部的东西,TDBGrid的工件还是其他东西?

I observed something last week that I did not expect, and will describe below. I am curious as to why this happens. Is it something internal to the TDataSet class, an artifact of the TDBGrid, or something else?

打开的ClientDataSet中的字段顺序已更改.具体来说,我在使用FieldDefs定义了ClientDataSet的结构后,通过调用CreateDatatSet在代码中创建了ClientDataSet.此ClientDataSet结构的第一个字段是一个名为StartOfWeek的Date字段.只是片刻之后,我还编写了假定StartOfWeek字段位于第零位的代码ClientDataSet.Fields [0],但失败了,因为StartOfWeek字段不再是ClientDataSet中的第一个字段.

The order of the fields in an open ClientDataSet changed. Specifically, I created a ClientDataSet in code by calling CreateDatatSet after defining its structure using FieldDefs. The first field in this ClientDataSet's structure was a Date field named StartOfWeek. Only moments later, code that I had also written, which assumed that the StartOfWeek field was in the zeroeth position, ClientDataSet.Fields[0], failed, since the StartOfWeek field was no longer the first field in the ClientDataSet.

经过一番调查,我了解到ClientDataSet中的每个单个字段在给定时刻可能会出现在不同于创建ClientDataSet时原始结构的某个位置.我不知道会发生这种情况,而且在Google上进行的搜索也没有提及这种影响.

After some investigation, I learned that it was possible that every single field in the ClientDataSet might, at a given moment, appear in some position different from the original structure at the time that the ClientDataSet was created. I was unaware that this could happen, and a search on Google didn't turn up any mention of this effect either.

发生的事情不是魔术.这些字段不会自行更改位置,也不会基于我在代码中所做的任何更改.导致字段实际出现变化的原因是用户更改了ClientDataSet附加到的DbGrid中的列的顺序(当然是通过DataSource组件).我在Delphi 7,Delphi 2007和Delphi 2010中复制了这种效果.

What happened wasn't magic. The fields didn't change position by themselves, nor did they change based on anything I did in my code. What caused the fields to physically appear to change position in the ClientDataSet was that the user had changed the order of the Columns in a DbGrid to which the ClientDataSet was attached (through a DataSource component, of course). I replicated this effect in Delphi 7, Delphi 2007, and Delphi 2010.

我创建了一个非常简单的Delphi应用程序,演示了这种效果.它由具有一个DBGrid,一个数据源,两个ClientDataSet和两个Button的单个表单组成.这种形式的OnCreate事件处理程序如下所示:

I created a very simple Delphi application that demonstrates this effect. It consists of a single form with one DBGrid, a DataSource, two ClientDataSets, and two Buttons. The OnCreate event handler of this form looks like the following

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;

标记为显示ClientDataSet结构"的Button1包含以下OnClick事件处理程序.

Button1, which is labeled Show ClientDataSet Structure, contains the following OnClick event handler.

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的按钮.您应该看到如下所示的内容:

To demonstrate the moving field effect, run this application and click the button labeled Show ClientDataSet Structure. You should see something like that shown here:

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

接下来,拖动DBGrid的列以重新排列字段的显示顺序.再次单击显示ClientDataSet结构"按钮.这次您将看到类似于此处显示的内容:

Next, drag the columns of the DBGrid to re-arrange the display order of the fields. Click the Show ClientDataSet Structure button once again. This time you will see something similar to that shown here:

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

此示例的显着之处在于,正在移动DBGrid的列,但是对ClientDataSet中的字段的位置有明显影响,例如ClientDataSet.Field [0]中的字段某一时刻的位置不一定会在稍后出现.而且,不幸的是,这显然不是ClientDataSet问题.我对基于BDE的TTables和基于ADO的AdoTables进行了相同的测试,并获得了相同的效果.

What is remarkable about this example is that the Columns of the DBGrid are being moved, but there is an apparent effect on the position of the Fields in the ClientDataSet, such that the field that was in the ClientDataSet.Field[0] position at one point is not necessarily there moments later. And, unfortunately, this is not distinctly a ClientDataSet issue. I performed the same test with BDE-based TTables and ADO-based AdoTables and got the same effect.

如果您永远不需要引用显示在DBGrid中的ClientDataSet中的字段,那么您就不必担心这种影响.对于其余的人,我可以想到几种解决方案.

If you never need to refer to the fields in your ClientDataSet being displayed in a DBGrid, then you don't have to worry about this effect. For the rest of you, I can think of several solutions.

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

The simplest, though not necessary the preferable way to avoid this problem is to prevent the user from reordering fields in a DBGrid. This can be done by removing the dgResizeColumn flag from the Options property of the DBGrid. While this approach is effective, it eliminates a potentially valuable display option, from the user's perspective. Furthermore, removing this flag not only restricts column reordering, it prevents column resizing. (To learn how to limit column reordering without removing the column resizing option, see http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm.)

第二种解决方法是避免基于其字面位置引用数据集的字段(因为这是问题的本质).换句话说,如果您需要引用Count字段,请不要使用DataSet.Fields [2].只要您知道字段的名称,就可以使用DataSet.FieldByName('Count')之类的东西.

The second workaround is to avoid referring to a DataSet's fields based on their literal position (since this is the essence of the problem). In order words, if you need to refer to the Count field, don't use DataSet.Fields[2]. So long as you know the name of the field, you can use something like DataSet.FieldByName('Count').

但是,使用FieldByName有一个相当大的缺点.具体地说,此方法通过遍历DataSet的Fields属性并基于字段名称查找匹配项来标识该字段.由于每次调用FieldByName都会执行此操作,因此在需要多次引用该字段的情况下(例如在导航大型DataSet的循环中),应该避免使用此方法.

There is one rather big drawback to the use of FieldByName, however. Specifically, this method identifies the field by iterating through the Fields property of the DataSet, looking for a match based on the field name. Since it does this every time you call FieldByName, this is a method that should be avoided in situations where the field needs to be referenced many times, such as in a loop that navigates a large DataSet.

如果您确实需要重复(且多次)引用该字段,请考虑使用类似以下代码段的内容:

If you do need to refer to the field repeatedly (and a large number of times), consider using something like the following code snippet:

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;

还有第三种解决方案,但是仅当您的数据集是ClientDataSet时才可用,例如我的原始示例中的解决方案.在这种情况下,您可以创建原始ClientDataSet的副本,并且它将具有原始结构.结果,无论用户对显示ClientDataSets数据的DBGrid进行了什么操作,在零位位置创建的任何字段都仍将位于该位置.

There is a third solution, but this is only available when your DataSet is a ClientDataSet, like the one in my original example. In those situations, you can create a clone of the original ClientDataSet, and it will have the original structure. As a result, whichever field was create in the zeroeth position will still be in that position, regardless of what a user has done to a DBGrid that displays the ClientDataSets data.

下面的代码对此进行了演示,该代码与标记为显示克隆的ClientDataSet结构"的按钮的OnClick事件处理程序相关.

This is demonstrated in the following code, which is associated with the OnClick event handler of the button labeled Show Cloned ClientDataSet Structure.

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的真实结构,如此处所示

If you run this project and click the button labeled Show Cloned ClientDataSet Structure, you will always get the true structure of the ClientDataSet, as shown here

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

附录:

重要的是要注意,基础数据的实际结构不受影响.具体地说,如果在更改DBGrid中的列顺序之后,调用ClientDataSet的SaveToFile方法,则保存的结构是原始(真正的内部)结构.另外,如果将一个ClientDataSet的Data属性复制到另一个ClientDataSet,则目标ClientDataSet也将显示真实结构(类似于克隆源ClientDataSet时观察到的效果).

It is important to note that that the actual structure of the underlying data is not affected. Specifically, if, after changing the order of the columns in a DBGrid, you call the SaveToFile method of the ClientDataSet, the saved structure is the original (true internal) structure. Also, if you copy the Data property of one ClientDataSet to another, the destination ClientDataSet also shows the true structure (which is similar to the effect observed when a source ClientDataSet is cloned).

类似地,绑定到其他测试数据集(包括TTable和AdoTable)的DBGrid列顺序的更改实际上不会影响基础表的结构.例如,一个TTable可以显示Delphi附带的customer.db示例Paradox表中的数据,但实际上并不会改变该表的结构(您也不希望这样做).

Similarly, changes to the column orders of DBGrids bound to other tested Datasets, including TTable and AdoTable, do not actually affect the structure of the underlying tables. For example, a TTable that displays data from the customer.db sample Paradox table that ships with Delphi does not actually change that table's structure (nor would you expect it to).

从这些观察结果中可以得出的结论是,数据集本身的内部结构保持不变.结果,我必须假设某个地方存在DataSet结构的辅助表示.并且,它必须与数据集关联(这似乎是过大的,因为并非所有对数据集的使用都需要此关联),又必须与DBGrid关联(由于DBGrid使用了此功能,因此更有意义,但并非如此)TField重新排序似乎与DataSet本身保持一致的观察结果支持了这一点,或者是其他原因.

What we can conclude from these observations is that the internal structure of the DataSet itself remains intact. As a result, I must assume that there is a secondary representation of the DataSet's structure somewhere. And, it must be either associated with the DataSet (which would seem to be overkill, since not all uses of a DataSet need this), associated with the DBGrid (which makes more sense since the DBGrid is using this feature, but which is not supported by the observation that the TField reordering seems to persist with the DataSet itself), or is something else.

另一种替代方法是将效果与TGridDataLink关联,TGridDataLink是为多行感知控件(如DBGrids)提供其数据感知的类.但是,我也倾向于拒绝这种解释,因为该类与网格关联,而不是与DataSet关联,这再次是因为效果似乎与DataSet类本身有关.

Another alternative is that the effect is associated with the TGridDataLink, which is the class that gives multirow-aware controls (like DBGrids) their data awareness. However, I am inclined to reject this explanation as well, since this class is associated with the grid, and not the DataSet, again since the effect seems persist with the DataSet classes themselves.

这使我回到了最初的问题.这是TDataSet类内部的东西,TDBGrid的工件还是其他东西?

Which brings me back to the original question. Is this effect something internal to the TDataSet class, an artifact of the TDBGrid, or something else?

请允许我在此处强调一些我添加到以下评论之一的内容.最重要的是,我的文章旨在使开发人员意识到,当他们使用可以更改列顺序的DBGrid时,其TField的顺序也可能会更改.该工件可能会引入间歇性和严重的错误,这些错误很难识别和修复.而且,不,我不认为这是Delphi的错误.我怀疑一切都按设计的目的进行了工作.只是我们许多人不知道这种行为正在发生.现在我们知道了.

Permit me also to stress something here that I added to one of the below comments. More than anything, my post is designed to make developers aware that when they are using DBGrids whose column orders can be changed that the order of their TFields may also be changing. This artifact can introduce intermittent and serious bugs which can be very difficult to identify and fix. And, no, I don't think this is a Delphi bug. I suspect that everything is working as it was designed to work. It's just that many of us were unaware that this behavior was occurring. Now we know.

推荐答案

显然,该行为是设计使然.实际上,它与dbgrid无关.这仅仅是设置字段索引的列的副作用.例如,该语句

Apparently the behaviour is by design. In fact it is not related to the dbgrid. It is merely a side effect of a column setting a field index. For instance this statement,

ClientDataSet1.Fields [0] .Index:= 1;

ClientDataSet1.Fields[0].Index := 1;

将导致显示ClientDataSet结构"按钮的输出相应地更改,无论是否存在网格.TField.Index的文档指出;

will cause the output of the "Show ClientDataSet Structure" button to change accordingly, either there is a grid or not. The documentation for TField.Index states;

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

"Change the order of a field's position in the dataset by changing the value of Index. Changing the Index value affects the order in which fields are displayed in data grids, but not the position of the fields in physical database tables."

一个人应该得出结论,反之亦然,并且更改网格中字段的顺序应该导致字段索引发生更改.

One should conclude the reverse should also be true and changing the order of fields in a grid should cause field indexes to be changed.


导致此问题的代码在TColumn.SetIndex中.TCustomDBGrid.ColumnMoved为移动的列设置新索引,而TColumn.SetIndex为该列的字段设置新索引.


The code causing this is in TColumn.SetIndex. TCustomDBGrid.ColumnMoved sets a new index for the moved column and TColumn.SetIndex sets the new index for that column's field.

procedure TColumn.SetIndex(Value: Integer);
[...]
        if (Col <> nil) then
        begin
          Fld := Col.Field;
          if Assigned(Fld) then
            Field.Index := Fld.Index;
        end;
[...]

这篇关于在DBGrid中移动列似乎可以移动附加的DataSet字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆