Больному уже лучше. Правда, он все еще в реанимации.

На что же уходит время в предыдущем варианте? Все просто! Львиная доля времени уходит на вызовы интерфейсов внешнего 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 — Рубрика: Delphi
Метки: ,

Не делайте так - клиническая смерть!

Собственно, об этом варианте я мог бы и не писать. Во-первых, я встречал его в большинстве книг и статей, посвященных этой теме. Во-вторых, я, на самом деле, думаю, что так делать нельзя. Речь идет о тривиальном переносе данных с помощью обычного присваивания свойству ячейки 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 — Рубрика: Delphi
Метки: ,

Какие данные мы используем?..

“Всяческие”! Да, каждый из нас использует в своих приложениях все многообразие типов данных, с которыми способен справиться компилятор и операционная система. В принципе, можно было бы описать решение проблемы для всего этого “многообразия”. Но! Я всегда утверждал и буду утверждать, что типы данных, для которых нельзя написать 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 — Рубрика: Delphi
Метки: , ,