Ho osservato qualcosa la scorsa settimana che non mi aspettavo e descriverò di seguito. Sono curioso di sapere perché questo accade. È qualcosa di interno alla classe TDataSet, un artefatto di TDBGrid o qualcos'altro?Spostare le colonne in un DBGrid sembra spostare i campi DataSet collegati
L'ordine dei campi in un ClientDataSet aperto è stato modificato. Nello specifico, ho creato un ClientDataSet nel codice chiamando CreateDatatSet dopo aver definito la sua struttura usando FieldDefs. Il primo campo nella struttura di ClientDataSet era un campo Data denominato StartOfWeek. Solo pochi istanti dopo, il codice che avevo anche scritto, che presupponeva che il campo StartOfWeek fosse nella posizione zero, ClientDataSet.Fields [0], non era riuscito, poiché il campo StartOfWeek non era più il primo campo in ClientDataSet.
Dopo alcune indagini, ho appreso che era possibile che ogni singolo campo nel ClientDataSet potesse, in un dato momento, apparire in una posizione diversa dalla struttura originale al momento della creazione del ClientDataSet. Non ero a conoscenza che ciò potesse accadere e una ricerca su Google non ha nemmeno accennato a questo effetto.
Quello che è successo non era magia. I campi non cambiano posizione da soli, né cambiano in base a tutto ciò che ho fatto nel mio codice. Ciò che faceva apparire fisicamente i campi per cambiare posizione in ClientDataSet era che l'utente aveva cambiato l'ordine delle Colonne in un DbGrid a cui era collegato il ClientDataSet (tramite un componente DataSource, ovviamente). Ho replicato questo effetto in Delphi 7, Delphi 2007 e Delphi 2010.
Ho creato un'applicazione Delphi molto semplice che dimostra questo effetto. Consiste in un unico modulo con un DBGrid, un DataSource, due ClientDataSet 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 del campo mobile, eseguire questa applicazione e fare clic sul pulsante denominato Mostra struttura ClientDataSet. 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 di nuovo clic sul pulsante Mostra struttura ClientDataSet. Questa volta si vedrà qualcosa di simile a quello mostrato qui:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
Label
StartOfWeek
Active
Count
Ciò che è notevole su questo esempio è che le colonne della DBGrid vengono spostati, ma v'è un effetto apparente sulla posizione dei campi nella ClientDataSet, tale che il campo che era nella posizione ClientDataSet.Field [0] in un punto non è necessariamente lì qualche istante dopo. E, sfortunatamente, questo non è chiaramente un problema di ClientDataSet. Ho eseguito lo stesso test con TTables basati su BDE e AdoTable basati su ADO e ho ottenuto lo stesso effetto.
Se non è necessario fare riferimento ai campi in ClientDataSet visualizzati in un controllo DBGrid, non è necessario preoccuparsi di questo effetto. Per il resto di voi, posso pensare a diverse soluzioni.
Il modo più semplice, ma non necessario, per evitare questo problema è impedire all'utente di riordinare i campi in un oggetto DBGrid. Questo può essere fatto rimuovendo il flag dgResizeColumn dalla proprietà Options di DBGrid. Sebbene questo approccio sia efficace, elimina un'opzione di visualizzazione potenzialmente preziosa, dal punto di vista dell'utente.Inoltre, la rimozione di questo flag non limita solo il riordino della colonna, ma impedisce il ridimensionamento della colonna. (Per informazioni su come limitare il riordino della colonna senza rimuovere l'opzione di ridimensionamento della colonna, vedere http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm.)
La seconda soluzione è evitare di fare riferimento ai campi di un DataSet in base alla loro posizione letterale (poiché questa è l'essenza del problema). Nelle parole ordine, se è necessario fare riferimento al campo Conteggio, non utilizzare DataSet.Fields [2]. Se conosci il nome del campo, puoi utilizzare qualcosa come DataSet.FieldByName ('Count').
C'è tuttavia un grosso svantaggio nell'uso di FieldByName. In particolare, questo metodo identifica il campo iterando attraverso la proprietà Fields del DataSet, cercando una corrispondenza in base al nome del campo. Poiché esegue questa operazione ogni volta che si chiama FieldByName, questo è un metodo da evitare nelle situazioni in cui è necessario fare riferimento al campo più volte, ad esempio in un ciclo che consente di esplorare un DataSet di grandi dimensioni.
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 nel mio esempio originale. In tali situazioni, è possibile creare un clone del ClientDataSet originale e avrà la struttura originale. Di conseguenza, qualsiasi campo creato nella posizione zero sarà ancora in quella posizione, indipendentemente da ciò che un utente ha fatto a un DBGrid che visualizza i dati ClientDataSets.
Questo è dimostrato nel seguente codice, che è associato al gestore di eventi OnClick del pulsante denominato 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;
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 l'effettiva struttura dei dati sottostanti non è influenzata. In particolare, se, dopo aver modificato l'ordine delle colonne in un oggetto DBGrid, si chiama il metodo SaveToFile di ClientDataSet, la struttura salvata è la struttura originale (true internal). Inoltre, se si copia la proprietà Data di un ClientDataSet su un altro, il ClientDataSet di destinazione mostra anche la struttura vera (che è simile all'effetto osservato quando una sorgente ClientDataSet è clonata).
Analogamente, le modifiche agli ordini colonna di DBGrid associati ad altri Dataset testati, inclusi TTable e AdoTable, non influiscono in realtà sulla struttura delle tabelle sottostanti. Ad esempio, un TTable che visualizza i dati dalla tabella di esempio sample.db di Paradox fornita con Delphi in realtà non cambia la struttura di quella tabella (né te la aspetti che lo aspetti).
Ciò che possiamo concludere da queste osservazioni è che la struttura interna del DataSet stesso rimane intatta. Di conseguenza, devo supporre che ci sia una rappresentazione secondaria della struttura del DataSet da qualche parte. E, deve essere associato al DataSet (che sembrerebbe essere eccessivo, poiché non tutti gli usi di un DataSet hanno bisogno di questo), associato a DBGrid (che ha più senso dal momento che il DBGrid utilizza questa funzione, ma che non è supportato dall'osservazione che il riordino di TField sembra persistere con il DataSet stesso) o è qualcos'altro.
Un'altra alternativa è che l'effetto è associato a TGridDataLink, che è la classe che fornisce ai controlli multirow-aware (come i DBGrid) la loro consapevolezza dei dati. Tuttavia, sono propenso a respingere anche questa spiegazione, poiché questa classe è associata alla griglia, e non al DataSet, ancora una volta poiché l'effetto sembra persistere con le classi DataSet stesse.
Che mi riporta alla domanda originale. Questo effetto è qualcosa di interno alla classe TDataSet, un artefatto di TDBGrid o qualcos'altro?
Permettetemi anche di sottolineare qualcosa qui che ho aggiunto a uno dei commenti seguenti. Più di ogni altra cosa, il mio post è progettato per rendere gli sviluppatori consapevoli del fatto che quando utilizzano DBGrids i cui ordini di colonne possono essere modificati, anche l'ordine dei TField potrebbe cambiare. Questo artefatto può introdurre bug intermittenti e seri che possono essere molto difficili da identificare e risolvere. E, no, non penso che questo sia un insetto Delphi. Sospetto che tutto funzioni come è stato progettato per funzionare. È solo che molti di noi non erano consapevoli che questo comportamento stava accadendo. Ora sappiamo.
Molto informativo, ma c'è una domanda qui da qualche parte? –
Grazie a @Cary, non ne avevo idea e sto usando DataSet.Field [x] molto spesso. Penso che dovresti segnalarlo sul sito di Embarcadero come un bug. – Wodzu
C'è una domanda, che appare nella seconda frase: "È qualcosa di interno alla classe TDataSet, un artefatto di TDBGrid o qualcos'altro?"Trascorro un po 'di tempo (circa un'ora) alla ricerca sia della sorgente TCustomGrid che di TDataSet, ma non ho visto dove stia accadendo Ancora più importante, ed è per questo che il mio post è così lungo, volevo almeno fare Gli sviluppatori di Delphi sono consapevoli di questo comportamento interessante: per chiunque utilizzi un controllo DBGrid o un'altra griglia simile che produce queste modifiche nell'ordine TField, potrebbe essere una fonte di un errore intermittente e di difficile individuazione. –