На что же уходит время в предыдущем варианте? Все просто! Львиная доля времени уходит на вызовы интерфейсов внешнего COM-сервера. И, не смотря на то, что мы используем ранее связывание с библиотекой типов, это так. Еще мой любимый классик (Калверт, знаете ли) писал о нетерпимости к вызовам интерфейсов внешних OLE-серверов в больших циклах. Как видите, классик прав.
Наша задача — избавиться от вызова Cell.Value в цикле. И это решаемо с помощью вариантных массивов. Вот так:
procedure TForm1.ToVarArray(ISheet: IxlWorksheet);
var Row, Column, i: integer;
IR1, IR2: IxlRange;
Arr: OLEVariant;
begin
Arr := VarArrayCreate([1, tblCust.RecordCount, 1, tblCust.Fields.Count], varVariant);
Row := 1;
tblCust.First;
while not tblCust.EOF do begin
Column := 1;
for i := 0 to tblCust.Fields.Count — 1 do begin
Arr[Row, Column] := FieldToVariant(tblCust.Fields[i]);
Inc(Column);
end;
Inc(Row);
tblCust.Next;
end;
IDispatch(IR1) := ISheet.Cells.Item[StartRow, StartColumn];
IDispatch(IR2) := ISheet.Cells.Item[StartRow + tblCust.RecordCount - 1,
StartColumn + tblCust.Fields.Count - 1];
ISheet.Range[IR1, IR2].Value := Arr;
end;
Здесь я использую вариантный массив Arr, который предварительно создается с размерами таблицы (количество записей на количество полей). Благо Microsoft построила очень четкую схему работы с вариантными массивами и интерфейсами, их «понимающими» (этим и пользуюсь). Из кода видно, что я по-прежнему прохожу всю таблицу, запоминая в элементах массива значения полей, полученных из вышеописанной функции FieldToVariant. Мы, ведь, снова используем варианты, и проблема строки «3/7″ остается. Последние три строки процедуры позволяют получить верхнюю левую и нижнюю правую ячейки области, в которую будут перенесены данные. А, затем, одним присваиванием в «Область.Value» я переношу данные из массива в ячейки этой области. Хорош способ, не правда ли? Код максимально прост. Время, полученное в ячейке A1 на порядок меньше. Правда, есть несколько проблем.
Главное, что бросилось бы в глаза опытного Delphi-разработчика, это создание массива в начале процедуры. Известно ли количество записей SQL-запроса после его открытия? Не всегда (FechAll). Хорошо, можно создать пустой массив и делать ему VarArrayRedim. Вряд ли! Так как количество записей — есть первое измерение вариантного массива (необходимо здесь тире или нет???). А я не нашел до сих пор способа изменить первую размерность вариантного массива при наличии второй. Может, кто подскажет начинающему (про начинающего — правда)!!! Возможно, было бы правильно создать массив массивов (понимаете о чем я?). Но, что-то не заладилось там, Наверху. Поэтому такое решение не проходит. Точнее проходит, но как-то не очень хорошо — попробуйте!
Тем не менее, этот вариант вполне «живуч» при осторожном его использовании и на небольших объемах данных. Скорость нормальная, проблем с «3/7″ нет. В общем, больной будет жить!
Февраль
7,
2008
— Filed under: Delphi
Метки: Delphi, SQL
Собственно, об этом варианте я мог бы и не писать. Во-первых, я встречал его в большинстве книг и статей, посвященных этой теме. Во-вторых, я, на самом деле, думаю, что так делать нельзя. Речь идет о тривиальном переносе данных с помощью обычного присваивания свойству ячейки Value нового значения. Тем не менее, я надеюсь что, описывая этот вариант, мне удастся обратить ваше внимание на несколько важных вещей.
Первый переключатель на форме (с заголовком «Value :=») скрывает за собой вызов процедуры ToNewValue. Вот ее исходный код:
procedure TForm1.ToNewValue(ISheet: IxlWorksheet);
var Row, Column, i: integer;
begin
tblCust.First;
Row := StartRow;
tblCust.First;
while not tblCust.EOF do begin
Column := StartColumn;
for i := 0 to tblCust.Fields.Count — 1 do begin
ISheet.Cells.Item[Row, Column].Value := FieldToVariant(tblCust.Fields[i]);
Inc(Column);
end;
Inc(Row);
tblCust.Next;
end;
end;
Что в ней особенного? Да, это обычный проход по всей таблице (First; while not EOF do Next;) и по всем ее полям (вложенный for). Но! Во-первых, в этом примере я всегда начинаю переносить данные с ячейки, определенной константами StartRow и StartColumn. Во-вторых, ожидаемый вами оператор присваивания «Cell.Value := Field.Value» я заменил на «Cell.Value := FieldToVariant(Field)». То есть, в отличие от классического, «из учебников», примера я использую свою функцию получения вариантного значения поля.
Если присмотреться к исходному тексту функции FieldToVariant,
function FieldToVariant(Field: TField): OLEVariant;
begin
Result := »;
case Field.DataType of
ftString, ftFixedChar, ftWideString, ftMemo, ftFmtMemo: Result := »» + Field.AsString;
ftSmallint, ftInteger, ftWord, ftLargeint, ftAutoInc: Result := Field.AsInteger;
ftFloat, ftCurrency, ftBCD: Result := Field.AsFloat;
ftBoolean: Result := Field.AsBoolean;
ftDate, ftTime, ftDateTime: Result := Field.AsDateTime;
end;
end;
то можно разглядеть причину. Кроме достаточно глупых «AsInteger», «AsFloat» и пр. я добавляю в начало значений строковых полей одиночную кавычку. Вы, наверняка, помните, что ввод в формулу ячейки первым символом одиночной кавычки заставляет Excel принимать остальные символы как текст. Но, это касается формул ячеек, а не их значений!? Попробуйте убрать добавление этой кавычки и перекомпилировать проект. Конечно, и в этом варианте все будет работать. Но (предлагаю кощунственный метод) отредактируйте поле «Company» в первой записи таблицы, введя туда строку «3/7″. Не увидите ли вы в полученной книге вместо этой строки дату или результат деления (зависит от языковых настроек ОС)? Столь же некорректный результат будет получен и при попытке передачи строки «0001″, которая будет воспринята как число 1. Благо, одиночная кавычка в начале строки решает эту проблему даже при присваивании в Value (а не в Formula).
Впрочем, я не намерен долго останавливаться на этом варианте. Все дело в значении ячейки A1 в полученной книге. На моем компьютере перенос всей таблицы занял более 4 секунд. И это на не более полусотне записей. А что было бы, если бы количество записей перевалило за пару десятков тысяч? Кстати, повторные запуски показали колебание этого времени в пределах от трех до пяти секунд. Думаю, что такие колебания были связаны только с файловым кэшем ОС. Так что, «Не делайте так!»
Февраль
5,
2008
— Filed under: Delphi
Метки: AsFloat, AsInteger
«Всяческие»! Да, каждый из нас использует в своих приложениях все многообразие типов данных, с которыми способен справиться компилятор и операционная система. В принципе, можно было бы описать решение проблемы для всего этого «многообразия». Но! Я всегда утверждал и буду утверждать, что типы данных, для которых нельзя написать Cell.Value = NewValue, бесполезно использовать в Excel. Я не «влюблен» в Excel. Но я твердо уверен в том, что Excel в сегодняшнем его состоянии — одно из мощнейших средств анализа корпоративных данных. И я до сих пор не могу найти другого применения картинкам в книгах Excel, кроме как наведение красоты. Поэтому я остановлюсь только на способах передачи целых и вещественных чисел, строк, дат и логических значений. В общем, всего того, что так надоело нам в наш быстротекущий media-век.
Важно! Проект-пример содержит одну форму, в обработчиках OnCreate и OnDestroy которой автоматически создается и освобождается Excel.Application. Причем, для этого я использовал методы из предыдущих примеров — CreateExcel, ShowExcel и ReleaseExcel. Особое внимание хочу обратить на ReleaseExcel, с помощью которого освобождается интерфейс Excel.Application. Если же необходимо закрыть Excel, вызывайте перед освобождением интерфейса метод Quit этого интерфейса (у себя я закомментировал эту строку). На форму я поместил таблицу (TTable) из DBDEMOS — CUSTOMER.DB. Чтобы видеть хоть что-то, я использовал для этой таблицы DBGrid и навигатор. В правой части формы вы увидите кнопку со странным названием «Send data» и группу переключателей под ней. С помощью этой группы вы сможете выбрать один из рассмотренных в этой статье вариантов передачи данных в Excel (данные я беру из вышеназванной таблицы). После выбора варианта передачи и нажатия кнопки в Excel создается новая книга на основе шаблона Test.xls — книги, которую я прилагаю вместе с проектом. В эту книгу из таблицы переносятся значения полей из всех записей. При переносе я измеряю количество миллисекунд, затраченных на этот перенос, и помещаю это количество в ячейку A1 созданной книги.
И еще. При создании примера я старался избегать лишнего кода, который в любом другом случае добавил бы законченность алгоритмам. Меня стоило бы основательно поругать за то, что я не запоминаю закладок при проходе по таблице, использую значения полей, забывая о DisplayText, и просто переношу значения в книгу без какого-либо форматирования ячеек. Наверняка, вы найдете еще несколько моментов, которые я совсем упустил из вида. Попросту, красота кода уступила свое место желанию сосредоточиться на единственной цели — эффективной передаче данных. Единственное, что я сделал, так это избавился от возможности вмешательства пользователя в процесс переноса данных, отключив (и включив затем) у Excel свойства Interactive и ScreenUpdating, а также вызвав DisableControls для набора данных.
Какие же варианты я предложу на суд читателя? Вариантов и возможностей передачи данных в Excel существует достаточно много — от очень «заумных» (BIFF) до экзотических (сохраним в текстовый файл и затем его откроем). Все они имеют какие-то свои достоинства и недостатки. В этой статье я расскажу об одних из самых простых и эффективных решениях этой проблемы (название раздела все-таки обязывает — HelloWorld!). Правда, первый описанный здесь способ, у меня ничего кроме зубной боли не вызывает. Итак…
Февраль
1,
2008
— Filed under: Delphi
Метки: BIFF, HelloWorld, данные