Domanda

ho osservato qualcosa la settimana scorsa che non mi aspettavo, e descriverò di seguito. Sono curioso di sapere perché questo accade. E 'qualcosa di interno alla classe TDataSet, un artefatto della TDBGrid, o qualcos'altro?

L'ordine dei campi in un ClientDataSet aperta cambiato. In particolare, ho creato un ClientDataSet nel codice chiamando CreateDatatSet dopo aver definito la struttura utilizzando FieldDefs. Il primo campo nella struttura di questo ClientDataSet era un campo Data denominato StartOfWeek. Solo pochi istanti dopo, il codice che avevo anche scritto, che ha assunto che il campo StartOfWeek era nella posizione dello zeroeth, ClientDataSet.Fields [0], non è riuscito, poiché il campo StartOfWeek non era più il primo campo nel ClientDataSet.

Dopo alcune indagini, ho imparato che era possibile che ogni singolo campo nel ClientDataSet potrebbe, in un dato momento, appaiono in qualche posizione diversa dalla struttura originale nel momento in cui il ClientDataSet è stato creato. Non sapevo che questo possa accadere, e una ricerca su Google non si presentò alcuna menzione di questo effetto sia.

Quello che è successo non era magia. I campi non hanno cambiato posizione da soli, né hanno cambiano in base a tutto ciò che ho fatto nel mio codice. Nei causato i campi appaiano fisicamente cambiare posizione nella ClientDataSet era che l'utente aveva cambiato l'ordine delle colonne in una DBGrid cui ClientDataSet era attaccato (attraverso un componente DataSource, ovviamente). Ho replicato questo effetto in Delphi 7, Delphi 2007 e Delphi 2010.

Ho creato un'applicazione molto semplice Delphi che dimostra questo effetto. Si compone di una singola forma con una DBGrid, un DataSource, due ClientDataSets, e due pulsanti. Il gestore di eventi OnCreate di questa forma è simile al seguente

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, che viene etichettato Mostra ClientDataSet Struttura, contiene il seguente gestore di evento 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;

Per dimostrare l'effetto di campo in movimento, eseguire l'applicazione e fare clic sul pulsante etichettato Mostra ClientDataSet Struttura. Si dovrebbe vedere qualcosa simile a quello mostrato qui:

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

Avanti, trascinare le colonne del DBGrid di ri-organizzare l'ordine di visualizzazione dei campi. Fare clic di nuovo sul pulsante Visualizza struttura ClientDataSet. Questa volta si vedrà qualcosa di simile a quello mostrato qui:

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

Ciò che è notevole di questo esempio è che le colonne del DBGrid vengono spostati, ma v'è un effetto apparente sulla posizione dei campi ClientDataSet, tali che il campo che era in ClientDataSet.Field [0] posizione ad un certo punto non è necessariamente lì qualche istante dopo. E, purtroppo, questo non è chiaramente un problema di ClientDataSet. Ho eseguito lo stesso test con TTables BDE-based e AdoTables ADO-based ed ho ottenuto lo stesso effetto.

Se non è necessario fare riferimento ai campi nella vostra ClientDataSet essere visualizzati in una DBGrid, allora non dovete preoccuparvi di questo effetto. Per il resto di voi, mi viene in mente diverse soluzioni.

Il più semplice, anche se non necessario il modo preferibile evitare questo problema è quello di impedire all'utente di campi in una DBGrid riordino. Questo può essere fatto rimuovendo la bandiera dgResizeColumn dalla proprietà Opzioni del DBGrid. Anche se questo approccio è efficace, elimina un potenziale prezioso opzione di visualizzazione, dal punto di vista dell'utente. Inoltre, la rimozione di questo flag non solo limita colonna riordino, impedisce ridimensionamento delle colonne. (Per informazioni su come limitare colonna di riordino senza rimuovere l'opzione di ridimensionamento della colonna, vedi http: / /delphi.about.com/od/adptips2005/a/bltip0105_2.htm .)

La seconda soluzione è evitare di riferirsi ai campi di un dataset in base alla loro posizione letterale (dal momento che questa è l'essenza del problema). In altri termini, se è necessario fare riferimento alla Coucampo nt, non utilizzare DataSet.Fields [2]. Fino a quando si conosce il nome del campo, si può usare qualcosa di simile DataSet.FieldByName ( 'Count').

Non è uno piuttosto grande svantaggio per l'utilizzo del FieldByName, però. In particolare, questo metodo identifica il campo scorrendo la proprietà campi del DataSet, alla ricerca di una corrispondenza in base al nome del campo. Dal momento che lo fa ogni volta che si chiama FieldByName, questo è un metodo che dovrebbe essere evitato in situazioni in cui il settore ha bisogno di essere fatto riferimento più volte, come ad esempio in un ciclo che naviga un grande DataSet.

Se si ha bisogno di fare riferimento al campo più volte (e un gran numero di volte), considerare l'utilizzo di qualcosa come il seguente frammento di codice:

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;

C'è una terza soluzione, ma questo è disponibile solo quando il DataSet è un ClientDataSet, come quello nel mio esempio originale. In tali situazioni, è possibile creare un clone del ClientDataSet originale, e avrà la struttura originaria. Di conseguenza, a seconda di quale campo è stato creare nella posizione dello zeroeth sarà ancora in quella posizione, indipendentemente da ciò che un utente ha fatto per un DBGrid che visualizza i dati ClientDataSets.

Questo è dimostrato nel seguente codice, che è associato con il gestore di eventi OnClick del pulsante etichettato Mostra Cloned ClientDataSet struttura.

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 si esegue questo progetto e fare clic sul pulsante etichettato Mostra Cloned ClientDataSet Struttura, avrai sempre la vera struttura del ClientDataSet, come mostrato qui

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

Addendum:

E 'importante notare che la struttura del effettiva dei dati sottostante non è interessato. In particolare, se, dopo aver cambiato l'ordine delle colonne in un DBGrid, si chiama il metodo SaveToFile del ClientDataSet, la struttura risparmiato è il (true interna) struttura originale. Inoltre, se si copia la proprietà dei dati di una ClientDataSet ad un altro, la destinazione di ClientDataSet mostra anche la vera struttura (che è simile all'effetto osservato quando un ClientDataSet sorgente viene clonato).

Analogamente, modifiche agli ordini di colonna di DBGrids legati ad altri insiemi di dati testati, compresi TTable e AdoTable, in realtà non influenzano la struttura delle tabelle sottostanti. Ad esempio, un TTable che visualizza i dati dalla Customer.db campione di tabella di Paradox fornito con Delphi non modifica la struttura di quel tavolo (e non ci si può aspettare a).

Cosa possiamo concludere da queste osservazioni è che la struttura interna del DataSet stesso rimane intatto. Di conseguenza, devo presumere che v'è una rappresentazione secondaria della struttura del DataSet da qualche parte. E, deve essere sia associato con il DataSet (che sembrerebbe essere eccessivo, dal momento che non tutti gli usi di un DataSet bisogno di questo), associato alla DBGrid (che rende più senso dal momento che la DBGrid utilizza questa funzione, ma che non è sostenuta dall'osservazione che il riordino TField sembra persistere con il DataSet stesso), o è un'altra cosa.

Un'altra alternativa è che l'effetto è associato con il TGridDataLink, che è la classe che dà controlli Multirow-aware (come DBGrids) la loro consapevolezza dei dati. Tuttavia, sono propenso a rifiutare questa spiegazione pure, poiché questa classe è associato alla griglia, e non DataSet nuovamente poiché l'effetto sembra persistono con le classi DataSet stessi.

Il che mi riporta alla domanda iniziale. Questo è l'effetto di qualcosa di interno alla classe TDataSet, un artefatto della TDBGrid, o qualcos'altro?

Mi permetta anche di sottolineare qualcosa qui che ho aggiunto a uno dei commenti qui sotto. Più di ogni altra cosa, il mio post è stato progettato per rendere sviluppatori consapevoli che quando stanno usando DBGrids cui ordini colonna può essere cambiato che l'ordine dei loro tfields può anche cambiare. Questo artefatto può introdurre errori intermittenti e gravi il cui cun essere molto difficili da identificare e correggere. E, no, non credo che questo sia un bug Delphi. Ho il sospetto che tutto funziona come è stato progettato per funzionare. E 'solo che molti di noi non erano a conoscenza che questo comportamento stava accadendo. Ora sappiamo.

È stato utile?

Soluzione

A quanto pare il comportamento legato alla progettazione. In effetti non è legato alla DBGrid. È soltanto un effetto collaterale di una colonna impostazione di un campo indice. Per esempio questa istruzione,

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

causerà l'uscita del pulsante "Mostra ClientDataSet Struttura" per cambiare di conseguenza, o c'è una griglia o meno. La documentazione per TField.Index afferma;

"Modificare l'ordine di posizione di un campo nel set di dati modificando il valore di indice. La modifica del valore indice influenza l'ordine in cui i campi vengono visualizzati in griglie di dati, ma non la posizione dei campi in tabelle di database fisici."

Si dovrebbe concludere il contrario dovrebbe anche essere vero e cambiare l'ordine dei campi in una griglia dovrebbe causare indici dei campi da modificare.


Il codice che causa questo è in TColumn.SetIndex. TCustomDBGrid.ColumnMoved stabilisce un nuovo indice per la colonna spostata e TColumn.SetIndex imposta il nuovo indice per il campo di quella colonna.

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

Altri suggerimenti

Wodzu ha postato una soluzione al problema campo riordinato che era specifico per ADO DataSet, ma lui mi ha portato a una soluzione che è simile, ed è disponibile per tutti i set di dati (sia esso attuato correttamente in tutti DataSet è un altro problema). Si noti che né questa risposta, né Wodzu di, è in realtà una risposta alla domanda iniziale. Invece, si tratta di una soluzione al problema rilevato, mentre la domanda riguarda dove questo artefatto origine.

La soluzione che la soluzione di Wodzu mi portano a era FieldByNumber, ed è un metodo della proprietà Fields. Ci sono due aspetti interessanti all'utilizzo di FieldByNumber. In primo luogo, è necessario qualificare il suo riferimento con la proprietà Campi dell'insieme di dati. In secondo luogo, a differenza della matrice Fields, che prende un indicizzatore a base zero, FieldByNumber è un metodo che accetta un parametro base uno per indicare la posizione del TField si desidera fare riferimento.

Quello che segue è una versione aggiornata del gestore di eventi Button1 che ho postato nella mia domanda iniziale. Questa versione utilizza 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;

Per il progetto di esempio, questo codice produce il seguente output, indipendentemente dall'orientamento delle colonne nella DBGrid associato:

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

Per ripetere, notare che il riferimento al TField sottostante richiesto FieldByNumber essere qualificato con un riferimento ai campi. Inoltre, il parametro per questo metodo deve essere situato sul 1 a DataSet.FieldCount gamma. Di conseguenza, per fare riferimento al primo campo nel DataSet, è possibile utilizzare il seguente codice:

ClientDataSet1.Fields.FieldByNumber(1)

Come la matrice Fields, FieldByNumber restituisce un riferimento TField. Di conseguenza, se si vuole fare riferimento a un metodo che è specifico per una particolare classe TField, si deve lanciare il valore restituito alla classe appropriata. Ad esempio, per salvare il contenuto di un TBlobField a un file, potrebbe essere necessario fare qualcosa come il seguente codice:

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

Si noti che non sto suggerendo che si dovrebbe fare riferimento tfields in un DataSet utilizzando letterali interi. Personalmente, l'uso di una variabile TField che viene inizializzato tramite una chiamata una volta per FieldByName è più leggibile, ed è immune ai cambiamenti di ordine fisico della struttura di una tabella (anche se non immune da cambiamenti nei nomi dei campi!).

Tuttavia, se si dispone di dataset relativi al DBGrids cui colonne eventuale ricambio, e si fa riferimento i campi di questi dataset mediante letterali interi come indicizzatori dell'array campi, si può prendere in considerazione la conversione del codice per utilizzare i DataSet.Fields metodo .FieldByName.

Cary Credo di aver trovato una soluzione per questo problema. Invece di usare VCL campi involucro abbiamo bisogno di usare una proprietà interna campi dell'oggetto Recordset COM.

Ecco come dovrebbe fare riferimento:

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

Questi campi non sono influenzati dal comportamento che avete descritto in precedenza. Così possiamo ancora fare riferimento ai campi da loro indice.

Prova questo fuori e dimmi cosa è stato il risultato. Ha funzionato per me.

Modifica:

Naturalmente funziona solo per i componenti ADO, non per il TClientDataSet ...

Edit2:

Cary Non so se questa è la risposta alla tua domanda, ma ho spinto la gente sui forum Embarcadero e Wayne Niddery mi ha dato risposta abbastanza dettagliate su tutto questo movimento campi.

Per fare una lunga storia breve:!? Se si definiscono le colonne in TDBGrid esplicitamente, gli indici dei campi non si muovono avere un po 'più senso ora, non lo ha

Leggi filo completo qui: https://forums.embarcadero.com/post!reply.jspa?messageID= 197287

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top