Я заметил что-то на прошлой неделе, чего я не ожидал, и опишу ниже. Мне любопытно, почему это происходит. Это что-то внутреннее для класса TDataSet, артефакт TDBGrid или что-то еще?
Изменен порядок полей в открытом ClientDataSet. В частности, я создал ClientDataSet в коде, вызвав CreateDatatSet после определения его структуры с помощью FieldDefs. Первым полем в этой структуре ClientDataSet было поле Date с именем StartOfWeek. Спустя несколько мгновений код, который я также написал, который предположил, что поле StartOfWeek находится в нулевой позиции, ClientDataSet.Fields [0] потерпело неудачу, поскольку поле StartOfWeek больше не было первым полем в ClientDataSet.
После некоторого расследования я узнал, что возможно, что каждое поле в ClientDataSet может в определенный момент времени появиться в некоторой позиции, отличной от исходной структуры на момент создания ClientDataSet. Я не знал, что это может произойти, и поиск в Google не стал упоминать об этом эффекте.
Случилось не волшебство. Поля не меняли позиции сами по себе, и они не менялись в зависимости от того, что я сделал в своем коде. Что физически изменило поля в позиции ClientDataSet, так это то, что пользователь изменил порядок столбцов в DbGrid, к которому был прикреплен ClientDataSet (конечно, через компонент DataSource). Я воспроизвел этот эффект в Delphi 7, Delphi 2007 и Delphi 2010.
Я создал очень простое приложение Delphi, которое демонстрирует этот эффект. Он состоит из одной формы с одним DBGrid, DataSource, двумя ClientDataSets и двумя кнопками. Обработчик события OnCreate этой формы выглядит следующим образом:
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, который помечен как Show ClientDataSet Structure, содержит следующий обработчик события 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;
Чтобы продемонстрировать эффект движущегося поля, запустите это приложение и нажмите кнопку "Показать структуру ClientDataSet". Вы должны увидеть что-то вроде показанного здесь:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
Затем перетащите столбцы DBGrid, чтобы повторно упорядочить порядок отображения полей. Еще раз нажмите кнопку Показать структуру ClientDataSet. На этот раз вы увидите нечто похожее на показанное здесь:
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
Label
StartOfWeek
Active
Count
Что примечательно в этом примере, так это то, что столбцы DBGrid перемещаются, но есть явное влияние на положение полей в ClientDataSet, так что поле, которое находилось в ClientDataSet.Field [0] положение в одной точке не обязательно бывает мгновенно. И, к сожалению, это не является проблемой ClientDataSet. Я выполнил те же тесты с помощью тестов на базе BDE и ADO-таблиц и получил тот же эффект.
Если вам никогда не нужно ссылаться на поля в вашем ClientDataSet, которые отображаются в DBGrid, вам не нужно беспокоиться об этом эффекте. Для остальных из вас я могу придумать несколько решений.
Простейшим, хотя и не необходимым, предпочтительным способом избежать этой проблемы является предотвращение изменения пользователем полей в DBGrid. Это можно сделать, удалив флаг dgResizeColumn из свойства Options DBGrid. Хотя этот подход эффективен, он устраняет потенциально ценный параметр отображения, с точки зрения пользователя. Кроме того, удаление этого флага не только ограничивает переупорядочение столбцов, но и предотвращает изменение размера столбцов. (Чтобы узнать, как ограничить переупорядочение столбцов, не удаляя параметр изменения размера столбца, см. http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm.)
Во втором обходном пути следует избегать обращения к полям DataSet на основе их литеральной позиции (так как это является сущностью проблемы). Для слов, если вам нужно обратиться к полю Count, не используйте DataSet.Fields [2]. Если вы знаете имя поля, вы можете использовать что-то вроде DataSet.FieldByName('Count').
Однако существует один довольно большой недостаток использования FieldByName. В частности, этот метод идентифицирует поле путем итерации через свойство Fields DataSet, ища совпадение на основе имени поля. Поскольку он делает это каждый раз, когда вы вызываете FieldByName, это метод, которого следует избегать в ситуациях, когда поле должно ссылаться много раз, например, в цикле, который перемещает большой DataSet.
Если вам нужно многократно ссылаться на это поле (и много раз), подумайте о том, чтобы использовать что-то вроде следующего фрагмента кода:
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;
Существует третье решение, но это доступно только в том случае, если ваш DataSet является ClientDataSet, как тот, который был в моем первоначальном примере. В таких ситуациях вы можете создать клон исходного ClientDataSet, и он будет иметь исходную структуру. В результате, какое бы поле не было создано в нулевой позиции, все равно будет находиться в этой позиции, независимо от того, что пользователь сделал с DBGrid, который отображает данные ClientDataSets.
Это показано в следующем коде, который связан с обработчиком события OnClick кнопки с надписью 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, как показано здесь.
The Structure of ClientDataSet1
- - - - - - - - - - - - - - - - -
StartOfWeek
Label
Count
Active
Приложение:
Важно отметить, что фактическая структура базовых данных не затрагивается. В частности, если после изменения порядка столбцов в DBGrid вы вызываете метод SaveToFile для ClientDataSet, сохраненная структура является исходной (истинной внутренней) структурой. Кроме того, если вы копируете свойство Data одного ClientDataSet в другой, целевой ClientDataSet также показывает истинную структуру (которая похожа на эффект, наблюдаемый при клонировании исходного клиента ClientDataSet).
Аналогично, изменения в порядках столбцов DBGrids, привязанных к другим проверенным наборам данных, включая TTable и AdoTable, фактически не влияют на структуру базовых таблиц. Например, TTable, который отображает данные из образца customer.db Парадокс, который поставляется с Delphi, фактически не меняет эту структуру таблицы (и вы не ожидаете этого).
Из этих наблюдений можно заключить, что внутренняя структура самого DataSet остается неповрежденной. В результате я должен предположить, что где-то есть вторичное представление структуры DataSet. И он должен быть либо связан с DataSet (который, казалось бы, будет излишним, поскольку это не все использование DataSet), связанное с DBGrid (что имеет больше смысла, поскольку DBGrid использует эту функцию, но которая не является что наблюдение за тем, что перегруппировка TField, похоже, сохраняется с самим DataSet), или что-то еще.
Другой альтернативой является то, что этот эффект связан с TGridDataLink, который является классом, который предоставляет многопользовательским средствам управления (например, DBGrids) их осведомленность о данных. Тем не менее, я склонен отклонять это объяснение, так как этот класс связан с сеткой, а не с DataSet, опять же, поскольку эффект, похоже, сохраняется с самими классами DataSet.
Что возвращает меня к первоначальному вопросу. Является ли этот эффект чем-то внутренним для класса TDataSet, артефакт TDBGrid или что-то еще?
Позвольте мне также подчеркнуть что-то здесь, что я добавил к одному из нижеследующих комментариев. Более того, мой пост предназначен для того, чтобы разработчики знали, что, когда они используют DBGrids, чьи порядки столбцов могут быть изменены, порядок их TFields также может меняться. Этот артефакт может вводить прерывистые и серьезные ошибки, которые могут быть очень трудно идентифицировать и исправить. И нет, я не думаю, что это ошибка Delphi. Я подозреваю, что все работает так, как было разработано для работы. Просто многие из нас не знали, что такое поведение происходит. Теперь мы знаем.