Question

J'ai observé quelque chose la semaine dernière que je ne m'y attendais pas, et je décrirai ci-dessous. Je suis curieux de savoir pourquoi cela se produit. Est-ce quelque chose d'interne à la classe TDataSet, un artefact de la TDBGrid, ou autre chose?

L'ordre des champs dans un ClientDataSet ouvert a changé. Plus précisément, je créé un ClientDataSet dans le code en appelant CreateDatatSet après avoir défini sa structure à l'aide FieldDefs. Le premier champ dans la structure de cette ClientDataSet était un champ Date nommé StartOfWeek. Quelques instants plus tard, le code que j'avais aussi écrit, ce qui suppose que le champ StartOfWeek était en position zeroeth, ClientDataSet.Fields [0], a échoué, puisque le champ StartOfWeek ne fut plus le premier champ ClientDataSet.

Après enquête, j'ai appris qu'il était possible que chaque champ unique dans le ClientDataSet pourrait, à un moment donné, apparaissent dans une position différente de la structure d'origine au moment où ClientDataSet a été créé. Je ne savais pas que cela pourrait se produire, et une recherche sur Google ne se sont pas fait mention de cet effet non plus.

Ce qui est arrivé n'a pas été magique. Les champs ne changent de position par eux-mêmes, ni ont-ils changé en fonction de tout ce que je l'ai fait dans mon code. Ce qui a causé les champs apparaissent physiquement changer de position dans le ClientDataSet était que l'utilisateur avait changé l'ordre des colonnes dans un DBGRID auquel ClientDataSet a été fixé (par l'intermédiaire d'un composant DataSource, bien sûr). Je répliqués cet effet dans Delphi 7, Delphi 2007 et Delphi 2010.

J'ai créé une application Delphi très simple qui démontre cet effet. Il se compose d'une seule forme avec un DBGrid, une source de données, deux ClientDataSets, et deux boutons. Le gestionnaire d'événements OnCreate de cette forme se présente comme suit

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;

Onglet1, qui est marqué Afficher la structure du ClientDataSet, contient le gestionnaire d'événement OnClick suivant.

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;

Pour démontrer l'effet de champ mobile, exécutez cette application et cliquez sur le bouton intitulé Structure Show ClientDataSet. Vous devriez voir quelque chose comme ça montré ici:

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

Ensuite, faites glisser les colonnes du DBGrid réarranger l'ordre d'affichage des champs. Cliquez sur le bouton Afficher la structure ClientDataSet une nouvelle fois. Cette fois-ci, vous verrez quelque chose de similaire à celui montré ici:

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

Ce qui est remarquable cet exemple est que les colonnes du DBGrid sont déplacés, mais il y a un effet apparent sur la position des champs dans ClientDataSet, tel que le champ qui était dans le ClientDataSet.Field [0] position à un moment donné est pas nécessairement là quelques instants plus tard. Et, malheureusement, ce n'est pas distinctement une question ClientDataSet. Je fis le même test avec TTables BDE et AdoTables basés sur ADO et a obtenu le même effet.

Si vous ne devez consulter les champs de votre ClientDataSet affiché dans un DBGrid, alors vous n'avez pas à vous soucier de cet effet. Pour le reste d'entre vous, je peux penser à plusieurs solutions.

Le plus simple, mais pas nécessaire la façon préférable d'éviter ce problème est d'empêcher l'utilisateur de réordonnancement champs dans un DBGrid. Cela peut être fait en retirant le drapeau dgResizeColumn de la propriété Options du DBGrid. Bien que cette approche soit efficace, il élimine une option d'affichage potentiellement utile, du point de vue de l'utilisateur. En outre, la suppression de cet indicateur de colonne réordonnancement restreint non seulement, il empêche le redimensionnement de la colonne. (Pour savoir comment limiter la colonne sans réordonnancement supprimer l'option de redimensionnement de la colonne, consultez http: / /delphi.about.com/od/adptips2005/a/bltip0105_2.htm .)

La deuxième solution consiste à éviter de se référer à un champs de DataSet en fonction de leur position littérale (puisque c'est l'essence du problème). En autres termes, si vous avez besoin de se référer à COUdomaine nt, ne pas utiliser DataSet.Fields [2]. Tant que vous connaissez le nom du champ, vous pouvez utiliser quelque chose comme DataSet.FieldByName ( « Count »).

Il y a un assez grand inconvénient de l'utilisation de FieldByName, cependant. Plus précisément, cette méthode identifie le champ en parcourant la propriété Fields du DataSet, à la recherche d'un match basé sur le nom du champ. Comme il le fait chaque fois que vous appelez FieldByName, ceci est une méthode qui doit être évité dans des situations où le champ doit être référencé plusieurs fois, comme dans une boucle qui navigue un grand DataSet.

Si vous avez besoin de faire référence au champ à plusieurs reprises (et un grand nombre de fois), envisagez d'utiliser quelque chose comme l'extrait de code suivant:

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;

Il y a une troisième solution, mais cela est disponible uniquement si votre DataSet est un ClientDataSet, comme celui dans mon exemple original. Dans ces situations, vous pouvez créer un clone de ClientDataSet original, et il aura la structure d'origine. En conséquence, quel que soit le terrain a été crée en position zeroeth sera encore dans cette position, quel que soit ce que l'utilisateur a fait à un DBGrid qui affiche les données ClientDataSets.

Ceci est démontré dans le code suivant, qui est associé avec le gestionnaire d'événement OnClick du bouton intitulé VOIR 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;

Si vous exécutez ce projet et cliquez sur le bouton intitulée Afficher clonées ClientDataSet Structure, vous obtiendrez toujours la vraie structure du ClientDataSet, comme indiqué ici

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

Addendum:

Il est important de noter que que la structure réelle des données sous-jacente est pas affectée. Plus précisément, si, après avoir changé l'ordre des colonnes dans un DBGrid, vous appelez la méthode SaveToFile de ClientDataSet, la structure enregistrée est la structure d'origine (vrai interne). En outre, si vous copiez la propriété des données d'un ClientDataSet à l'autre, ClientDataSet de destination montre aussi la vraie structure (qui est similaire à l'effet observé quand est cloné une ClientDataSet source).

De même, les modifications apportées aux commandes de colonne de DBGrids liés à d'autres jeux de données testés, y compris TTable et AdoTable, ne touchent pas vraiment la structure des tables sous-jacentes. Par exemple, un TTable qui affiche les données de la table exemple CLIENTS.DB Paradox livré avec Delphi ne modifie pas la structure de cette table (ne vous attendez-vous à).

Ce que nous pouvons conclure de ces observations est que la structure interne du DataSet lui-même reste intacte. Par conséquent, je dois supposer qu'il ya une représentation secondaire de la structure du DataSet quelque part. Et, il doit être soit associé au DataSet (qui semble être exagéré, puisque toutes les utilisations d'un DataSet ont besoin de ce), associé à la DBGrid (ce qui est plus logique puisque le DBGrid utilise cette fonctionnalité, mais qui ne sont pas soutenu par l'observation que le réordonnancement TField semble persister avec le DataSet lui-même), ou autre chose.

Une autre alternative est que l'effet est associé à la TGridDataLink, qui est la classe qui donne des contrôles de multilignes conscients (comme DBGrids) leur connaissance des données. Cependant, je suis enclin à rejeter cette explication aussi bien, étant donné que cette classe est associée à la grille, et non le DataSet, encore une fois puisque l'effet semble persister avec les classes DataSet eux-mêmes.

Ce qui me ramène à la question initiale. Est-ce quelque chose d'effet interne à la classe TDataSet, un artefact de la TDBGrid, ou autre chose?

Permettez-moi aussi de souligner quelque chose que j'ai ajouté à l'un des commentaires ci-dessous. Plus que tout, mon poste est conçu pour les développeurs faire savoir que, lorsqu'ils utilisent DBGrids dont les ordres colonne peut être modifiée que l'ordre de leur TFields peut également changer. Cet artefact peut introduire des bugs intermittents et graves qui cun très difficile d'identifier et de corriger. Et, non, je ne pense pas que ce soit un bug Delphi. Je soupçonne que tout fonctionne comme il a été conçu pour fonctionner. Il est juste que beaucoup d'entre nous ne savaient pas que ce comportement se produisait. Maintenant, nous savons.

Était-ce utile?

La solution

Il semble que le comportement est. En fait, il est pas lié à la DBGrid. Il est simplement un effet secondaire d'une colonne d'un index réglage de champ. Par exemple cette déclaration,

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

provoquera la sortie du bouton « Afficher ClientDataSet structure » pour changer en conséquence, soit il y a une grille ou non. La documentation pour les États TField.Index;

« Modifier l'ordre dans l'ensemble de données en changeant la valeur de l'indice de la position d'un champ. Modification de la valeur de l'indice affecte l'ordre dans lequel les champs sont affichés dans les réseaux de données, mais pas la position des champs dans les tables de base de données physiques. »

Il faut conclure l'inverse devrait être vrai et changer l'ordre des champs dans une grille devrait provoquer des indices de champ à modifier.


Le code est en cause ce TColumn.SetIndex. TCustomDBGrid.ColumnMoved fixe un nouvel indice pour la colonne déplacé et TColumn.SetIndex fixe le nouvel indice pour le champ de cette colonne.

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

Autres conseils

Wodzu a posté une solution au problème le terrain réorganisées qui était spécifique à DataSet ADO, mais il m'a conduit à une solution similaire, et disponible pour tous les jeux de données (si elle est mise en œuvre correctement dans tous DataSets est une autre question). Notez que est en fait une réponse à la question initiale ni cette réponse, ni Wodzu de,. Au lieu de cela, il est une solution au problème noté, alors que la question se rapporte à l'endroit où cet artefact est originaire.

La solution que la solution de Wodzu me conduire à FieldByNumber était, et il est une méthode de la propriété Fields. Il y a deux aspects intéressants à l'utilisation de FieldByNumber. Tout d'abord, vous devez qualifier la référence avec la propriété Fields de votre DataSet. En second lieu, à la différence du tableau Fields, qui prend un indexeur de base zéro, FieldByNumber est une méthode qui prend un paramètre à base d'un pour indiquer la position du TField que vous souhaitez référencer.

Ce qui suit est une version mise à jour du gestionnaire d'événements Button1 que j'ai posté dans ma question initiale. Cette version utilise 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;

Pour l'exemple de projet, ce code produit la sortie suivante, quelle que soit l'orientation des colonnes du DBGrid associé:

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

Pour répéter, notez que la référence à la TField sous-jacente nécessaire FieldByNumber être qualifié avec une référence aux champs. En outre, le paramètre pour ce procédé doit se situer dans la gamme de 1 à DataSet.FieldCount. En conséquence, de se référer au premier champ dans le DataSet, utilisez le code suivant:

ClientDataSet1.Fields.FieldByNumber(1)

Comme le tableau Fields, FieldByNumber renvoie une référence TField. Par conséquent, si vous voulez faire référence à une méthode qui est spécifique à une classe TField particulière, vous devez jeter la valeur retournée à la classe appropriée. Par exemple, pour enregistrer le contenu d'un TBlobField dans un fichier, vous devrez peut-être faire quelque chose comme le code suivant:

TBlobField(MyDataSet.Fields.FieldByNumber(6)).SaveToFile('c:\mypic.jpg');

Notez que je ne dis pas que vous devez faire référence à TFields dans un DataSet en utilisant les littéraux entiers. Personnellement, l'utilisation d'une variable TField qui obtient initialisé par un appel d'une fois à FieldByName est plus lisible, et est immunisé contre les changements dans l'ordre physique de la structure d'une table (mais pas à l'abri des changements dans les noms de vos champs!).

Cependant, si vous avez des jeux de données associés à DBGrids dont les colonnes peuvent être réorganisés, et vous faites référence à des champs de ces jeux de données en utilisant les littéraux entiers comme indexeurs du tableau Fields, vous pouvez envisager de convertir votre code pour utiliser les DataSet.Fields procédé .FieldByName.

Cary Je pense avoir trouvé une solution à ce problème. Au lieu d'utiliser les champs wrapper VCL nous avons besoin d'utiliser une propriété Les champs internes de l'objet COM Recordset.

Voici comment il doit être référencé:

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

Ces champs ne sont pas affectés par le comportement que vous avez décrit précédemment. On peut donc toujours se référer aux champs par leur indice.

Faites le test et me dire quel était le résultat. Il a travaillé pour moi.

Edit:

Bien sûr, il ne fonctionnera que pour les composants ADO, pas TClientDataSet ...

Edit2:

Cary Je ne sais pas si cela est réponse à votre question, mais j'ai poussais les gens sur les forums de Embarcadero et Wayne Niddery m'a donné réponse très détaillée au sujet de tout ce mouvement champs.

Pour faire une histoire courte:? Si vous définissez vos colonnes TDBGrid explicitement, les index sur le terrain ne se déplacent pas Avoir un peu plus de sens maintenant, n'a pas

Lire fil complet ici: https://forums.embarcadero.com/post!reply.jspa?messageID= 197287

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top