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

Я наблюдал что-то на прошлой неделе, что я не ожидал и опишу ниже. Мне любопытно относительно того, почему это происходит. Это - что-то внутреннее к классу TDataSet, артефакту TDBGrid или чего-то еще?

Заказ областей в открытом ClientDataSet изменился. А именно, я создал ClientDataSet в кодексе, назвав CreateDatatSet после определения его использования структуры FieldDefs. Первая область в структуре этого ClientDataSet была областью Даты под названием StartOfWeek. Только несколько моментов спустя, код, который я также написал, который предположил, что область StartOfWeek была в zeroeth положении, ClientDataSet. Области [0], подведенный, так как область StartOfWeek больше не была первой областью в ClientDataSet.

После некоторого расследования я узнал, что было возможно, что каждая область в ClientDataSet, в данный момент, могла бы появиться в некотором положении, отличающемся от оригинальной структуры в то время, когда ClientDataSet был создан. Я не знал, что это могло произойти, и поиск на Google не поднял упоминания об этом эффекте также.

То, что произошло, не было волшебно. Области не сменили положение собой, и при этом они не изменялись на основе ничего, что я сделал в своем кодексе. То, что заставило области, физически казаться, сменить положение в ClientDataSet, было то, что пользователь изменил заказ Колонок в DbGrid, к которому ClientDataSet был приложен (через компонент DataSource, конечно). Я копировал этот эффект в Дельфи 7, Дельфи 2007 и Дельфи 2010.

Я создал очень простое приложение Дельфи, которое демонстрирует этот эффект. Это состоит из единственной формы с одним 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, который маркирован Шоу Структура ClientDataSet, содержит следующий обработчик событий 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, чтобы перестроить заказ показа областей. Нажмите кнопку Show ClientDataSet Structure еще раз. На этот раз Вы будете видеть что-то подобное показанному здесь:

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

То, что поразительно в этом примере, - то, что Колонки DBGrid перемещаются, но есть очевидный эффект на положение Областей в ClientDataSet, таком, что область, которая была в ClientDataSet. Область [0] положение однажды не обязательно там несколько моментов спустя. И к сожалению, это не отчетливо проблема ClientDataSet. Я выполнил тот же тест с ОСНОВАННЫМ НА ПРОЦЕССОРЕ БАЗ ДАННЫХ ФИРМЫ BORLAND TTables и ОСНОВАННЫМ НА СУМАТОХЕ AdoTables и получил тот же эффект.

Если Вы никогда не должны обращаться к областям в своем ClientDataSet, показываемом в DBGrid, то Вы не должны волноваться об этом эффекте. Для остальной части Вас я могу думать о нескольких решениях.

Самое простое, хотя не необходимый предпочтительный способ избежать этой проблемы состоит в том, чтобы препятствовать тому, чтобы пользователь переупорядочил области в DBGrid. Это может быть сделано, удалив dgResizeColumn флаг из собственности Вариантов DBGrid. В то время как этот подход эффективный, он устраняет потенциально ценный параметр экрана с точки зрения пользователя. Кроме того, удаление этого флага не только ограничивает переупорядочение колонки, это предотвращает изменение размеров колонки. (Чтобы изучить, как ограничить переупорядочение колонки, не удаляя колонку, изменяющую размеры выбора, см. http://delphi.about.com/od/adptips2005/a/bltip0105_2.htm.)

Вторая работа должна постараться не относиться к областям DataSet на основе их буквального положения (так как это - сущность проблемы). В словах заказа, если Вы должны обратиться к области графа, не используют DataSet. Области [2]. Пока Вы знаете название поля, Вы можете использовать что-то как DataSet. FieldByName ('граф').

Есть один довольно большой недостаток к использованию FieldByName, как бы то ни было. А именно, этот метод определяет область, повторяя через собственность Областей 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, и у него будет оригинальная структура. В результате, какой бы ни область была, создают в zeroeth положении, все еще будет в том положении, независимо от того, что пользователь сделал к DBGrid, который показывает данные ClientDataSets.

Это продемонстрировано в следующем кодексе, который связан с обработчиком событий OnClick кнопки маркированное Шоу Клонированная Структура ClientDataSet.

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, спасенная структура - оригинал (верный внутренний) структура. Кроме того, если Вы копируете собственность Данных одного ClientDataSet другому, место назначения, которое ClientDataSet также показывает истинной структуре (то, которое подобно эффекту, наблюдало, когда источник ClientDataSet клонирован).

Точно так же изменения порядка следования столбцов DBGrids, связанного с другими проверенными Наборами данных, включая TTable и AdoTable, на самом деле не затрагивают структуру базовых таблиц. Например, TTable, который показывает данные из customer.db типовой таблицы Парадокса, что суда с Дельфи на самом деле не изменяют структуру того стола (и при этом Вы не ожидали бы это к).

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

Другая альтернатива - то, что эффект связан с TGridDataLink, который является классом, который дает многострочно-осведомленные средства управления (как DBGrids) их осведомленность данных. Однако я склонен отклонить это объяснение также, так как этот класс связан с сеткой а не DataSet, снова так как эффект кажется, сохраняются с самими классами DataSet.

Который возвращает меня оригинальному вопросу. Этот эффект - что-то внутреннее к классу TDataSet, артефакту TDBGrid или чего-то еще?

Разрешите мне также подчеркивать что-то здесь, что я добавил к одному из ниже комментариев. Больше, чем что-нибудь, мой пост разработан, чтобы сделать разработчиков знающими что, когда они используют DBGrids, порядок следования столбцов которого могут быть изменены, что заказ их TFields может также изменяться. Этот артефакт может представить неустойчивые и серьезные ошибки, которые может быть очень трудно определить и зафиксировать. И, нет, я не думаю, что это - ошибка Дельфи. Я подозреваю, что все работает, поскольку это было разработано, чтобы работать. Это просто, что многие из нас не знали, что это поведение происходило. Теперь мы знаем.

12
задан Cary Jensen 31 December 2009 в 16:56
поделиться

3 ответа

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

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

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

"Изменение порядка расположения поля в наборе данных путем изменения значения 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;
[...]
3
ответ дан 2 December 2019 в 23:31
поделиться

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

Вот как на него следует ссылаться:

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

Эти поля НЕ подвержены влиянию описанного ранее поведения. Поэтому мы все еще можем ссылаться на поля по их индексу.

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

Правка:

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

Правка2:

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

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

Читайте полный поток здесь: https://forums.embarcadero.com/post!response.jspa?messageID=197287

1
ответ дан 2 December 2019 в 23:31
поделиться

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

Решение, к которому меня привело решение Водзу, было 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 был квалифицирован со ссылкой на Fields. Кроме того, параметр для этого метода должен находиться в диапазоне от 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, более читабельно и невосприимчива к изменениям физического порядка структуры таблицы (хотя и невосприимчива к изменениям названий ваших полей!).

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

.
1
ответ дан 2 December 2019 в 23:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: