Pergunta

Observei algo na semana passada que não esperava e descreveria abaixo. Estou curioso para saber por que isso acontece. É algo interno para a classe Tdataset, um artefato do TDBGrid ou outra coisa?

A ordem dos campos em um clientDataSet aberto alterou. Especificamente, criei um clientDataSet no código ligando para o CreatedAtatSet após definir sua estrutura usando o FieldDefs. O primeiro campo da estrutura deste clientDataSet foi um campo de data chamado StartOfWeek. Apenas momentos depois, o código que eu também havia escrito, que assumiu que o campo StartOfWeek estava na posição Zeroeth, ClientDataSet.Fields [0], falhou, já que o campo StartOfWeek não era mais o primeiro campo no ClientDataSet.

Após alguma investigação, aprendi que era possível que todos os campos do ClientDataset pudessem, em um determinado momento, aparecer em alguma posição diferente da estrutura original no momento em que o ClientDataSet foi criado. Eu não sabia que isso poderia acontecer, e uma pesquisa no Google também não fez nenhuma menção a esse efeito.

O que aconteceu não foi mágica. Os campos não mudaram de posição sozinhos, nem mudaram com base em qualquer coisa que eu fiz no meu código. O que fez com que os campos pareçam fisicamente mudar de posição no clientDataSet foi que o usuário havia alterado a ordem das colunas em um DBGRID ao qual o clientDataSet foi anexado (através de um componente de fonte de dados, é claro). Repliquei esse efeito em Delphi 7, Delphi 2007 e Delphi 2010.

Criei um aplicativo Delphi muito simples que demonstra esse efeito. Consiste em um único formulário com um DBGRID, uma fonte de dados, dois dados do cliente e dois botões. O manipulador de eventos OnCreate deste formulário se parece com o seguinte

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, que é rotulado como a estrutura do clientDataSet, contém o seguinte manipulador de eventos 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;

Para demonstrar o efeito do campo em movimento, execute este aplicativo e clique no botão Mostrar estrutura do clientDataSet. Você deve ver algo assim mostrado aqui:

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

Em seguida, arraste as colunas do DBGrid para reorganizar a ordem de exibição dos campos. Clique no botão Show ClientDataSet mais uma vez. Desta vez, você verá algo semelhante ao mostrado aqui:

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

O que é notável sobre este exemplo é que as colunas do DBGRID estão sendo movidas, mas há um efeito aparente na posição dos campos no clientDataSet, de modo que o campo que estava na posição clientDataSet.field [0] em um em um Point não está necessariamente lá momentos depois. E, infelizmente, esse não é distintamente um problema do DireDataSet. Realizei o mesmo teste com o TTABLES baseado em BDE e o ADO baseado em ADO e tive o mesmo efeito.

Se você nunca precisar se referir aos campos no seu DireDataSet sendo exibido em um DBGrid, não precisa se preocupar com esse efeito. Para o resto de vocês, posso pensar em várias soluções.

A mais simples, embora não necessária, a maneira preferível de evitar esse problema é impedir que o usuário reordenasse campos em um DBGrid. Isso pode ser feito removendo o sinalizador DGRESIZECOLumn da propriedade Opções do DBGRID. Embora essa abordagem seja eficaz, elimina uma opção de exibição potencialmente valiosa, da perspectiva do usuário. Além disso, a remoção desse sinalizador não apenas restringe a reordenação da coluna, como evita o redimensionamento da coluna. (Para aprender a limitar a reordenação da coluna sem remover a opção de redimensionamento da coluna, consulte http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm.)

A segunda solução alternativa é evitar se referir aos campos de um conjunto de dados com base em sua posição literal (já que essa é a essência do problema). Para palavras, se você precisar consultar o campo de contagem, não use o DataSet.fields [2]. Desde que você saiba o nome do campo, você pode usar algo como DataSet.FieldbyName ('contagem').

No entanto, há uma grande desvantagem no uso do FieldByName. Especificamente, esse método identifica o campo iterando a propriedade Fields do conjunto de dados, procurando uma correspondência com base no nome do campo. Como faz isso toda vez que você chama FieldByName, este é um método que deve ser evitado em situações em que o campo precisa ser referenciado muitas vezes, como em um loop que navega em um conjunto de dados grande.

Se você precisar se referir ao campo repetidamente (e um grande número de vezes), considere usar algo como o seguinte snippet de código:

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;

Há uma terceira solução, mas isso só está disponível quando seu conjunto de dados é um DireDataSet, como o do meu exemplo original. Nessas situações, você pode criar um clone do clientDataSet original e ele terá a estrutura original. Como resultado, qualquer campo criado na posição Zeroeth ainda estará nessa posição, independentemente do que um usuário fez com um DBGRID que exibe os dados do ClientDataSets.

Isso é demonstrado no código a seguir, que está associado ao manipulador de eventos OnClick do botão rotulado Mostrar estrutura Cloned DataSet.

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;

Se você executar este projeto e clicar no botão rotulado Mostrar estrutura clonada do DataSet, você sempre obterá a verdadeira estrutura do clientDataSet, como mostrado aqui

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

Termo aditivo:

É importante observar que a estrutura real dos dados subjacentes não é afetada. Especificamente, se, depois de alterar a ordem das colunas em um DBGRID, você chama o método Savetofile do ClientDataSet, a estrutura salva é a estrutura original (verdadeira interna). Além disso, se você copiar a propriedade de dados de um clientDataSet para outro, o destino ClientDataSet também mostrará a estrutura verdadeira (que é semelhante ao efeito observado quando um clientDataSet de origem é clonado).

Da mesma forma, as alterações nas ordens de coluna dos DBGrids vinculadas a outros conjuntos de dados testados, incluindo TTable e ADotable, não afetam a estrutura das tabelas subjacentes. Por exemplo, um TTABLE que exibe dados da tabela de paradox do cliente.db que envia com Delphi na verdade não altera a estrutura dessa tabela (nem você esperaria).

O que podemos concluir com essas observações é que a estrutura interna do próprio conjunto de dados permanece intacta. Como resultado, devo assumir que existe uma representação secundária da estrutura do conjunto de dados em algum lugar. E, deve estar associado ao conjunto de dados (que parece ser um exagero, uma vez que nem todos os usos de um conjunto de dados precisam disso), associados ao DBGrid (o que faz mais sentido, pois o DBGrid está usando esse recurso, mas que não é Apoiado pela observação de que a reordenação de TField parece persistir com o próprio conjunto de dados) ou é outra coisa.

Outra alternativa é que o efeito está associado ao TGridDatalink, que é a classe que fornece controles com consciência multirrow (como DBGrids) sua conscientização sobre dados. No entanto, também estou inclinado a rejeitar essa explicação, uma vez que essa classe está associada à grade, e não ao conjunto de dados, novamente, pois o efeito parece persistir com as próprias classes do conjunto de dados.

O que me leva de volta à pergunta original. Esse efeito é algo interno para a classe Tdataset, um artefato do TDBGrid ou outra coisa?

Permita -me também enfatizar algo aqui que adicionei a um dos comentários abaixo. Mais do que tudo, minha postagem foi projetada para conscientizar os desenvolvedores de que, quando estão usando DBGRIDs cujas pedidos de coluna podem ser alteradas para que a ordem de seus campos também possa estar mudando. Esse artefato pode introduzir bugs intermitentes e sérios que podem ser muito difíceis de identificar e corrigir. E, não, não acho que este seja um bug do Delphi. Suspeito que tudo esteja funcionando como foi projetado para funcionar. É que muitos de nós não sabíamos que esse comportamento estava ocorrendo. Agora sabemos.

Foi útil?

Solução

Aparentemente, o comportamento é por design. De fato, não está relacionado ao DBGrid. É apenas um efeito colateral de uma coluna configurando um índice de campo. Por exemplo, esta afirmação,

ClientDataset1.fields [0] .Index: = 1;

fará com que a saída do botão "Show ClientDataset Structure" mude de acordo, ou não há uma grade ou não. A documentação para os estados tfield.index;

"Altere a ordem da posição de um campo no conjunto de dados alterando o valor do índice. Alteração do valor do índice afeta a ordem em que os campos são exibidos nas grades de dados, mas não na posição dos campos nas tabelas de banco de dados físico".

Deve -se concluir que o reverso também deve ser verdadeiro e alterar a ordem dos campos em uma grade deve fazer com que os índices de campo sejam alterados.


O código que causa isso está em tcolumn.setIndex. Tcustomdbgrid.columnmoved Define um novo índice para a coluna movida e o tcolumn.setIndex define o novo índice para o campo dessa coluna.

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

Outras dicas

Wodzu postou uma solução para o problema de campo reordenado que era específico do conjunto de dados ADO, mas ele me levou a uma solução que é semelhante e disponível para todos os conjuntos de dados (seja implementado corretamente em tudo DataSets é outro problema). Observe que nem essa resposta, nem a de Wodzu, são realmente uma resposta para a pergunta original. Em vez disso, é uma solução para o problema mencionado, enquanto a pergunta se relaciona com o origem do arte.

A solução à qual a solução de Wodzu me levou foi a FieldByNumber, e é um método da propriedade Fields. Existem dois aspectos interessantes no uso do FieldByNumber. Primeiro, você deve qualificar sua referência com a propriedade Fields do seu conjunto de dados. Segundo, diferentemente da matriz Fields, que leva um indexador baseado em zero, o FieldByNumber é um método que pega um parâmetro baseado em um para indicar a posição do campo T que você deseja fazer referência.

A seguir, é apresentada uma versão atualizada do manipulador de eventos Button1 que eu publiquei na minha pergunta original. Esta versão usa o 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;

Para o projeto de amostra, este código produz a seguinte saída, independentemente da orientação das colunas no DBGrid associado:

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

Para repetir, observe que a referência ao campo t subjacente exigia que o FieldByNumber fosse qualificado com uma referência aos campos. Além disso, o parâmetro para esse método deve estar dentro do intervalo 1 do DataSet.FieldCount. Como resultado, para se referir ao primeiro campo no conjunto de dados, você usa o seguinte código:

ClientDataSet1.Fields.FieldByNumber(1)

Como a matriz Fields, o FieldByNumber retorna uma referência do TIFFEL. Como resultado, se você deseja se referir a um método específico para uma classe TField específica, precisará lançar o valor retornado à classe apropriada. Por exemplo, para salvar o conteúdo de um tblobfield em um arquivo, você pode ter que fazer algo como o seguinte código:

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

Observe que não estou sugerindo que você faça referência a TFIELDS em um conjunto de dados usando literais inteiros. Pessoalmente, o uso de uma variável de campo T que é inicializado através de uma chamada única para o FieldByName é mais legível e é imune a mudanças na ordem física da estrutura de uma tabela (embora não seja imune a alterações nos nomes de seus campos!).

No entanto, se você possui conjuntos de dados associados a DBGrids cujas colunas podem ser reordenadas e você faz referência aos campos desses conjuntos de dados usando literais inteiros como indexadores da matriz dos campos, você pode considerar converter seu código para usar o método do DataSet.fields.fieldByName .

Cary, acho que encontrei uma solução para esse problema. Em vez de usar os campos de wrapper VCL, precisamos usar uma propriedade interna dos campos do objeto com compartamento de registro.

Aqui está como deve ser referenciado:

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

Esses campos não são afetados pelo comportamento que você descreveu anteriormente. Portanto, ainda podemos nos referir aos campos pelo índice.

Teste isso e me diga qual foi o resultado. Funcionou para mim.

Editar:

Claro que funcionará apenas para componentes ADO, não para o TclientDataSet ...

Edit2:

Cary Eu não sei se isso é resposta para sua pergunta, no entanto, tenho empurrado as pessoas nos fóruns de Embarcadero e Wayne Niddery me deu uma resposta detalhada sobre todo esse movimento de campos.

Para encurtar uma longa história: Se você definir suas colunas no TDBGrid explicitamente, os índices de campo não estarão em movimento! Tem um pouco mais de sentido agora, não é?

Leia o tópico completo aqui:https://forums.embarcadero.com/post!reply.jspa?messageId=197287

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top