PHP Delphi CSS HTML JavaScript Perl API ASP MySQL XML С++ VBasic WEB разработка *NIX CouchDB Hack Python
Главная Статьи Delphi По волнам интеграции3
Главная
 Главная  Контакты
 
Программинг
Статьи Книги ЧаВО
 
xBOOKi
Fresh Books Операционки Сети
 
Поиск
-------
 
Counters
Яндекс цитирования
Rambler's Top100
-------
 
CryptDisk.4h
Программа которая позволяет создать виртуальный шифрованный логический диск.

cryptdisk.4hack.com

-------
 
 
По волнам интеграции
Боже, как долго этот файл состоял только из заголовка! То настроение было совсем неподходящим, то обстановка не способствовала. К тому же, жизнь и работа временами походили на сводки с фронтов, со своими победами и поражениями, паническими отступлениями и жаркими атаками. Я все ждал настоящей тишины и настоящих мыслей, чтобы продолжить свое повествование. Вот, дождался. Не ругай меня строго, читатель (ты ведь студент в большей своей части), за столь большие сроки между моими статьями. Понятное дело, что тебе надо знать все и прямо сейчас. И чтобы ясно и просто было написано. И чтобы безошибочен был код. И чтобы все вопросы твои были предугаданы заранее. Однако, к великому сожалению, мне все это не под силу. Поэтому здесь, в этой статье, я полностью сосредоточусь на одной, самой критичной, по моему мнению, проблеме - как быстро и качественно передать данные в Microsoft Excel. Естественно, с использованием OLE Automation. Естественно, с использованием импортированной библиотеки типов Excel. Пришло, все-таки пришло время интеграции!

Как и прежде, я беру проект-пример из предыдущей своей статьи и переделываю его. Напомню, что в нем используется импортированная в Delphi 4 библиотека типов Excel (правда, исходный код я пишу уже в Delphi 5). Свои примеры я тестирую с помощью Excel 2000 с установленным пакетом обновлений Service Release 1. Впрочем, я уверен, что все примеры вы сможете откомпилировать в Delphi 4 и использовать с Excel 97 SR2 и Excel 2000 без SR1. Обращаю внимание на установку SR2 для Excel 97. Это обязательное условие, так как без этого обновления Excel содержит очень неприятную ошибку, периодически возникающую при закрытии книг. Поэтому, пожалуйста, будьте внимательны, господа!
С сожалением о собственных ошибках…

Каюсь. Все выглядело очень прискорбно. В проекте-примере для Delphi 4 в первой моей статье была ошибка. В модуле VBIDE8TLB в предложении uses был неверно указан модуль Office_TLB вместо Office8TLB. Не доглядел (это в ответ на очень гневное письмо о моей некомпетентности - да, такой я некомпетентный).

В предыдущей статье был приведен такой текст:
"Кстати, об lcid. В прошлый раз меня подвела зрительная память. И в самом деле, любимый классик пишет, что туда можно смело передавать 0. Но другой, не менее любимый классик с этим не согласен и рекомендует передавать туда результат функции GetUserDefaultLCID. Думаю, последнее более правильно. А классики пусть сами разбираются." (Канту и Калверт)

Боже, оба классика неправы! В некоторых случаях, чаще в гремучей смеси Windows 2000 и Excel 2000, оба решения не проходили. Причем, выдавалось сообщение о попытке "использовать библиотеку старого формата" и что-то еще. Так вот, вместо GetUserDefaultLCID я применяю теперь константу LOCALE_USER_DEFAULT. Более ничего объяснить не смогу, так как до сих пор, проштудировав основательно MSDN, не разобрался, что же в таком случае хочет получить Microsoft в методы и свойства интерфейсов Excel, где одним из параметров требует lcid. Кто бы объяснил?..

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

Всяческие! Да, каждый из нас использует в своих приложениях все многообразие типов данных, с которыми способен справиться компилятор и операционная система. В принципе, можно было бы описать решение проблемы для всего этого многообразия. Но! Я всегда утверждал и буду утверждать, что типы данных, для которых нельзя написать 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!). Правда, первый описанный здесь способ, у меня ничего, кроме зубной боли, не вызывает. Итак…

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

Собственно, об этом варианте я мог бы и не писать. Во-первых, я встречал его в большинстве книг и статей, посвященных этой теме. Во-вторых, я на самом деле думаю, что так делать нельзя. Речь идет о тривиальном переносе данных с помощью обычного присваивания свойству ячейки 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 секунд. И это на не более полусотне записей. А что было бы, если бы количество записей перевалило за пару десятков тысяч? Кстати, повторные запуски показали колебание этого времени в пределах от трех до пяти секунд. Думаю, что такие колебания были связаны только с файловым кэшем ОС. Так что Не делайте так!

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

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

"Мы его теряем!"

Совсем недавно мне пришло очередное гневное послание на тему буфера обмена - Clipboard. Объясню. XL Report очень долго использовал только буфер обмена для передачи данных из приложения в Excel. Дело в том, что при таком варианте (а я его здесь опишу) достигается практически максимальная скорость переноса данных. Дело в том, что в буфер обмена кладется длинная строка, содержащая строковые значения полей набора данных (AsString), разделенные символом табуляции. Записи отделяются друг от друга переводом строки (#10). Собственно, этот формат известен в научных кругах как CSV (разделитель между значениями). Долгое время это меня устраивало, пока XL Report использовался только нашими разработчиками и ограниченным кругом клиентов фирмы. Но тут мы решили выложить это решение в Сеть. И...

Каюсь. Я никак не беспокоился за сохранность содержимого буфера обмена. Понятное дело, это не очень правильно. Так было. Так есть и сейчас. Правда, по другим причинам. Для того, чтобы "выжать" из Excel максимальное быстродействие, приходится использовать определенные методы и свойства его интерфейсов. А их использование не оставляет ничего, кроме как уничтожение содержимого буфера обмена. В общем, сейчас я покажу максимально возможное (в этой статье!!!) решение по переносу данных - CSV. Итак:

procedure TForm1.ToCSV(ISheet:IxlWorksheet);
var 
     i: integer;
     IR1, IR2: IxlRange;
     Buff: String;
begin
     Buff := '';
     tblCust.First;
     while not tblCust.EOF do 
           begin
            for i := 0 to tblCust.Fields.Count - 1 do 
                begin
                 Buff := Buff + FieldToStr(tblCust.Fields[i]);
                 if i < (tblCust.Fields.Count - 1) 
                    then Buff := Buff + #9;
                end;
            tblCust.Next;
            if not tblCust.EOF 
               then Buff := Buff + #10;
           end;
     BufferToClipboard(Buff);
  try
     IDispatch(IR1) := ISheet.Cells.Item[StartRow, StartColumn];
     IDispatch(IR2) := ISheet.Cells.Item[StartRow + tblCust.RecordCount - 1, 
                                         StartColumn + tblCust.Fields.Count - 1];
     OLEVariant(ISheet.Range[IR1, IR2]).PasteSpecial;
  finally
     Clipboard.Clear;
  end
end;

Как я и писал выше, в строковый буфер Buff собирается вся таблица. Строковые значения полей я разделяю символом табуляции, а в конце записи добавляю перевод строки. Все значения я заключаю дополнительно в двойные кавычки. Затем вызовом процедуры BufferToClipboard я помещаю содержимое этой переменной в буфер обмена и делаю "хитрый" вызов PasteSpecial для области, в которую будут помещены данные. Вот и все?! Нет, есть еще проблемы.

Во-первых, процедура BufferToClipboard - вещь нестандартная. Она создана как альтернатива методу SetTextBuf класса TClipboard. Наверняка все знают, что в VCL доступна глобальная переменная Clipboard, экземпляр класса TClipboard, инкапсулирующего свойства и методы доступа к этому самому буферу обмена. Собственно, вызов SetTextBuf позволяет поместить строку в буфер. Но!

SetTextBuf помещает в буфер обмена текст в формате CF_TEXT - обычный текст с однобайтовым представлением символов, что не есть хорошо. Точнее, это совсем не хорошо, если вы работаете с "русскими буквами" на разных операционных системах от MS, причем, разных с точки зрения локализации. Именно тогда и возникают у пользователей вопросы при попытке прочитать некий набор "закорючек", отдаленно напоминающих письмена племени зибару. Поэтому я предпочитаю UNICODE, вставка которого в буфер обмена и реализована в этой процедуре. Ее текст я не буду приводить в этой статье, так как это потребует лишних для этого объяснений. Сама процедура присутствует в исходных текстах проекта-примера. Поэтому знатоков этого дела отсылаю к ним, а таким начинающим, как и я, просто рекомендую ее использовать в последующих своих разработках (при необходимости).

UNICODE - это первая проблема, которая была решена. Но при использовании CSV есть и другие. И главная из них - "3/7". Без вмешательства в содержимое поля (аналогично добавлению одиночной кавычки при вариантах) ее нельзя обойти никак. Я добавляю пробел. Да, тривиального пробела вполне хватает для решения этой проблемы. И замеченная вами функция FieldToStr как раз это и делает.

По поводу "хитрого" PasteSpecial. При "правильном" вызове метода PasteSpecial интерфейса Range я столкнулся с неразрешимыми проблемами. Поэтому я привел к варианту область и заставил ОС и сам Excel самостоятельно разбираться с тем, что и с какими параметрами я вызываю. Часто при разработке с библиотеками типов от MS такой ход экономит время и главное, сохраняет здоровье и нормальное состояние нервной системы. Не используйте его все время, однако и не пренебрегайте им. Итак, при использовании CSV и буфера обмена мы достигли "громаднейшей" скорости передачи данных, обойдя при этом несколько проблем.

Недостатки, которые заметят критики и просто опытные разработчики. Переданное строковое значение "0001" предстает перед изумленным пользователем числом 1. Это можно обойти только через предварительное форматирование конкретной ячейки в текстовый формат. Подчеркиваю, предварительно, то есть перед переносом данных. И по-прежнему содержимое буфера обмена не сохраняется. Более того, оно затем нагло очищается вызовом Clipboard.Clear. Мы его теряем!

"Наш ответ Чемберлену" или "Больной будет жить"…

Думаю, вы уже заметили, что один из переключателей вариантов имеет название, состоящее из зловещего сочетания букв - DDE. Первое, что вы можете почувствовать при виде этих букв, - это застоялый запах плесени и старения. Да, DDE - пережиток старого доброго времени (а, кажется, это было совсем недавно - как молоды мы были), времени DOS-резидентов, командной строки, капитана Нортона и первых (несмотря на их номер - 3.0) "форточек". Черт, тут жизнь уже проходит, а DDE все еще жив. И слава Богу! Ведь мы решим "главную" проблему - сохранение содержимого буфера обмена. Вот так:

procedure TForm1.ToDDE(ISheet:IxlWorksheet);
var 
     xlDDE: TDDEClientConv;
     i: integer;
     IR1, IR2, IRange: IxlRange;
     Buff: string;
begin
     Buff := '';
     tblCust.First;
     while not tblCust.EOF do 
           begin
            for i := 0 to tblCust.Fields.Count - 1 do 
                begin
                 Buff := Buff + FieldToStr(tblCust.Fields[i]);
                 if i < (tblCust.Fields.Count - 1) 
                    then Buff := Buff + #9;
                end;
            tblCust.Next;
            if not tblCust.EOF 
               then Buff := Buff + #10;
           end;
     IDispatch(IR1) := ISheet.Cells.Item[StartRow, StartColumn]; 
     IDispatch(IR2) := ISheet.Cells.Item[StartRow + tblCust.RecordCount - 1, 
                                         StartColumn + tblCust.Fields.Count - 1];
     IRange := ISheet.Range[IR1, IR2];

     xlDDE := TDDEClientConv.Create(Self);
  try
     if xlDDE.SetLink('EXCEL', ISheet.Name) 
        then xlDDE.PokeData(OLEVariant(IRange).Address[ReferenceStyle:=xlR1C1],
                                                                    PChar(Buff));
  finally
     xlDDE.Free;
  end
end;

Все реже я встречаю книги, где было бы описано взаимодействие приложений посредством DDE. Многие и вовсе думают, что DDE уже давно умер. Ан нет, жив курилка. Более того, это одно из самых быстрых решений по передаче данных в Excel. И, что самое интересное, Excel по-прежнему поддерживает DDE и все DDE-команды, которые стали доступны со времен версии 4.0. И по-прежнему будет несказанно рад тот счастливчик, который обнаружит в непроходимых джунглях Сети файл под названием "excel40macro.hlp", ибо там он найдет все, что нужно для быстрой и качественной работы с Excel. Это вам не интерфейсы "пользовать". Однако вернемся к исходному тексту.

В этой процедуре я, как было описано выше, прохожу по всей таблице, собирая в строковый буфер значения полей, разделенные символом табуляции. Записи же разделяются символом перевода строки. Затем я получаю интерфейс на область, куда необходимо поместить данные из таблицы. Это первая часть кода. Далее - самое интересное.

Переменная xlDDE используется для доступа к Excel посредством DDE. Если опустить теорию, напрямую обратившись к практике, то можно увидеть следующий алгоритм ее использования. Во-первых, создается экземпляр класса TDDEClientConv. Во-вторых, вызовом SetLink происходит соединение через DDE с запущенным Excel. SetLink возвращает true, если это соединение успешно. А далее происходит вызов метода PokeData, одним из параметров которого является строковый буфер Buff. Второй параметр - это адрес области в формате R1C1. Вот и все. Думаю, это заработает и у вас. Скорость сравнима с CSV через буфер обмена. Плюс здесь буфер обмена мы совсем не используем. Но!

Попробуйте несколько раз подряд быстро нажать кнопку "Send data" с этим вариантом передачи данных. У меня Excel просто виснет. Точнее, он что-то делает, загружая на все сто процессор. Благо, Windows NT безболезненно позволяет снять задачу. Тем, у кого Win9x, повезло меньше и, видимо, им придется перезагрузиться. Почему это происходит? Меня смутила вот эта строка в реализации TDDEClientConv:

hdata := DdeClientTransaction(Pointer(hszDat), DWORD(-1), FConv, hszItem,
                                      FDdeFmt, XTYP_POKE, TIMEOUT_ASYNC, nil);

Точнее, параметр TIMEOUT_ASYNC, позволяющий передавать данные асинхронно. Вот и сыплется Excel, не выдерживая реализации DDE-клиента от Borland/Inprise. Впрочем, Borland тоже не причем. Для себя я создал потомка класса TDDEClientConv, добавив ему новый метод xlPokeData, в котором просто заменил эту строку на:

const xddeTransactionTimeOut = 100000;
...
hdata := DdeClientTransaction(Pointer(hszDat), DWORD(-1), Conv, hszItem,
                              CF_XLTABLE, XTYP_POKE, xddeTransactionTimeOut, nil);
...

И все в порядке: работает.

Я не стану описывать подробности взаимодействия процессов через DDE по, думаю, понятным вам причинам. Все это давно описано в классике жанра - документации по Delphi. Тем не менее, по-прежнему остается проблема со строкой "0001". И останется она при этом варианте нерешенной, так как здесь используются строковые представления всех значений полей. И где же выход, спросите вы? Выход прост.

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

Чтобы в нем использовались native представления данных, где число было бы не строкой, а привычным набором из четырех (восьми) байт. Ведь, напомню, целые и вещественные числа и даты - это, в конечном счете, вещественное число в Excel. Для себя я решение нашел - Fast Table Format. Однако это уже не из этой статьи…

И напоследок…

Я виню себя за сие столь короткое и отнюдь не содержательное повествование. Я виню себя за то, что не могу объяснить всего, что есть в Delphi и Excel. Однако я уверен, что любой такой же начинающий Delphi-разработчик, как и я, уловит направление моих мыслей и будет самостоятельно двигаться вперед, захватывая очередные плацдармы истины, отделяя эту самую истину от разной шелухи междометий и знаков препинания между ними в этой статье. И я горд за то, что большинство опытных зубров и юных студентов в нашей большой стране (СССР) находятся в непрерывном поиске решений задач, которые ставит им жизнь. Все-таки мы способны на большее, чем "написать" Excel и Delphi. Мы можем заставить их работать вместе! И как работать! Только вот…

Это прекрасно - печатать "платежки" из Delphi-приложений в Excel. Это прекрасно - готовить в Excel кассовые расходные ордера, накладные и счета-фактуры. Но все же позвольте напомнить, что Excel не только для этого создавался. По моему глубочайшему убеждению, для столь частых и небольших печатных форм существует немало отличных инструментов. Первое, что приходит в голову - QuickReport. Разве нельзя сделать этого там? Нет, не первое. Первым всегда и везде должен звучать Fast Report - этот прекрасный инструмент для создания отчетов в Delphi. Ему нет альтернативы, помните об этом. А Excel? Excel скорее пригодится вам для удобного интерактивного анализа данных. Аналитикам! Аналитикам в экономике, бухгалтерии и управленческом учете отдавайте Excel, наполнив его предварительно данными из ваших корпоративных баз данных и подготовив по ним сводные таблицы и диаграммы. Дайте им почувствовать себя настоящими профессионалами. Такими же профессионалами, как и вы!

С уважением, Евгений Старостин
Автор XL Report - http://www.afalinasoft.com/rus/
14 ноября 2000

Проект - SampleExcel3.zip (130 K)



Свежее
Резервное копирование rsync-ом
DNS Amplification (DNS усиление)
Алгоритм Шинглов — поиск нечетких дубликатов текста
Metasploit Framework. Обзор
Использование CouchDB
-------



 
Copyright © 2003-2009   Frikazoid.
Rambler's Top100