Подтвердить что ты не робот

Перемещение столбцов в DBGrid, похоже, перемещает прикрепленные поля DataSet

Я заметил что-то на прошлой неделе, чего я не ожидал, и опишу ниже. Мне любопытно, почему это происходит. Это что-то внутреннее для класса 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. Я подозреваю, что все работает так, как было разработано для работы. Просто многие из нас не знали, что такое поведение происходит. Теперь мы знаем.

4b9b3361

Ответ 1

По-видимому, поведение по дизайну. На самом деле это не связано с dbgrid. Это просто побочный эффект столбца, задающего индекс поля. Например, это утверждение,

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

приведет к соответствующим изменениям в строке "Показать структуру ClientDataSet", либо есть сетка, либо нет. Документация для состояний TField.Index;

"Измените порядок позиции поля в наборе данных, изменив значение индекса. Изменение значения индекса влияет на порядок отображения полей в сетях данных, но не на положение полей в физических таблицах базы данных".

Следует заключить, что обратное также должно быть истинным, а изменение порядка полей в сетке должно приводить к изменению индексов полей.


Код, вызывающий это, находится в TColumn.SetIndex. TCustomDBGrid.ColumnMoved устанавливает новый индекс для перемещенного столбца и TColumn.SetIndex устанавливает новый индекс для этого поля столбца.

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

Ответ 2

Кэри, я думаю, что нашел решение этой проблемы. Вместо использования полей оболочки VCL нам нужно использовать внутреннее свойство Fields объекта Recordset COM.

Вот как это должно быть указано:

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

Эти поля не зависят от поведения, которое вы описали ранее. Поэтому мы можем по-прежнему ссылаться на поля по их индексу.

Протестируйте это и скажите мне, что было результатом. Это сработало для меня.

Edit:

Конечно, он будет работать только для компонентов ADO, а не для TClientDataSet...

Edit2:

Кэри, я не знаю, является ли это ответом на ваш вопрос, однако я толкнул людей на форумах embarcadero, и Уэйн Ниддери дал мне довольно подробный ответ обо всем этом движении Поля.

Короче говоря: Если вы четко укажете свои столбцы в TDBGrid, индексы полей не перемещаются! У вас есть немного больше смысла, не так ли?

Прочитайте полную версию здесь: https://forums.embarcadero.com/post!reply.jspa?messageID=197287

Ответ 3

Wodzu опубликовал решение проблемы с переупорядоченным полем, которая была специфична для ADO DataSet, но он привел меня к решению, которое аналогично и доступно для всех наборов данных (независимо от того, было ли оно реализовано правильно во всех DataSet - это еще одна проблема). Обратите внимание, что ни этот ответ, ни Водзу, на самом деле не отвечают на исходный вопрос. Вместо этого это решение проблемы отмечено, тогда как вопрос касается того, откуда этот артефакт.

Решение, решение Wodzu привело меня к FieldByNumber, и это метод свойства Fields. Существует два интересных аспекта использования FieldByNumber. Во-первых, вы должны квалифицировать свою ссылку с свойством Fields вашего DataSet. Во-вторых, в отличие от массива Fields, который принимает нулевой индекс, FieldByNumber - это метод, который принимает один байт, чтобы указать позицию TField, которую вы хотите ссылаться.

Ниже приведена обновленная версия обработчика событий Button1, которую я опубликовал в моем исходном вопросе. Эта версия использует 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;

Для примера проекта этот код выводит следующий вывод независимо от ориентации столбцов в связанном DBGrid:

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

Чтобы повторить, обратите внимание, что ссылка на базовый TField требует, чтобы FieldByNumber был квалифицирован со ссылкой на поля. Кроме того, параметр для этого метода должен лежать в пределах от 1 до DataSet.FieldCount. В результате, чтобы ссылаться на первое поле в DataSet, вы используете следующий код:

ClientDataSet1.Fields.FieldByNumber(1)

Как и массив Fields, FieldByNumber возвращает ссылку TField. В результате, если вы хотите обратиться к методу, специфичному для определенного класса TField, вы должны отдать возвращаемое значение соответствующему классу. Например, чтобы сохранить содержимое TBlobField в файле, вам может понадобиться сделать что-то вроде следующего кода:

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

Обратите внимание, что я не предлагаю вам ссылаться на TFields в DataSet, используя целые литералы. Лично использование переменной TField, которая инициализируется посредством однократного вызова FieldByName, более читабельна и невосприимчива к изменениям в физическом порядке структуры таблицы (хотя и не застрахована от изменений в именах ваших полей!).

Однако, если у вас есть DataSets, связанные с DBGrids, чьи столбцы можно переупорядочить, и вы ссылаетесь на поля этих наборов данных, используя целые литералы в качестве индексаторов из массива Fields, вы можете рассмотреть возможность преобразования вашего кода для использования DataSet.Fields.FieldByName.