Функция excel в delphi если

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Видимо любители экономить килобайты оперативной памяти могут меня закидать помидорами или ещё чем по-хуже, но все-таки я скажу, что интеграция приложений (мегабайты оперативы) — это большой плюс нынешней разработки приложений.
Как ни крути, а время DOS и килобайтов оперативной памяти прошло. Вряд ли кто-то всерьез сейчас задумывается над тем куда это с винчестера пропал мегабайт? Да и использование в своих приложениях функциональности программ, которых ты не писал, но которые выполняют что-то лучше — это всё-таки больший прогресс, нежели корпеть год-два над программой, а потом узнать, что время-то прошло.

Введение

Итак, цель сегодняшней статьи — поделиться с Вами опытом работы с Microsoft Excel в приложениях, написанных на Delphi.
Вспомнился сейчас один случай. Когда я только начинал работать по своей специальности, пригласили меня написать программу-расчётник для экологов нашего нефтезавода. В принципе ничего серьёзного — программа считает выброс от нагревательной печи и выдает табличку результатов, которую необходимо распечатать и уложить в толстую папку с отчётами. Естественно, что в области разработки подобных приложения я далеко не пионер, поэтому дали взглянуть на аналог будущей программы, который работал ещё под DOS и печатались отчёты на дико скрипящем матричном принтере с 12-ю иголками. Ну посмотрел, элементарная таблица, расчёт немного запутан, но жить можно — начал по-тихоньку писать. И попалась мне тогда на глаза статейка про работу с Excel в Delphi. Вот я и решил попробовать выдавать отчёт не только на форму приложения, а ещё и скидывать весь ход расчёта с формулами и прочим делом в Excel…Надо сказать более сильно детской радости начальника отдела я не видел до сих пор :). Люди, всю жизнь проработавшие в DOS увидели как тот же самый расчёт может выглядеть в современных условиях. Вот теперь при определении технических заданий на каждую новую программу, обязательно присутствует пункт, гласящий, что программа должна передавать данные либо в MS Word либо в MS Excel.Соответственно и цена на разработку возрастает, иногда значительно.

Отсюда можно сделать простой и однозначный вывод — заказчики готовы пожертвовать лишними деньгами только для того, чтобы всё в программе было красиво. Excel может дать вашему приложению ту самую красоту и удобство.

Ну, а для того, чтобы каждый раз не утруждать себя выполнением однотипных операций, я разработал небольшой модуль для работы с Excel. Этот же модуль я в настоящее время дорабатываю под ещё одну задачу, но об этом после. Сегодня основы основ работы с Excel в Delphi.

Создаем новый модуль (unit) и подключаем в uses следующие модули:

uses ComObj, ActiveX, Variants, Windows, Messages, SysUtils, Classes;

теперь объявляем глобальную переменную:

Одну константу (для удобства):

const ExcelApp = 'Excel.Application';

И пишем простенькую функцию:

function CheckExcelInstall:boolean;
var
  ClassID: TCLSID;
  Rez : HRESULT;
begin
// Ищем CLSID OLE-объекта
  Rez := CLSIDFromProgID(PWideChar(WideString(ExcelApp)), ClassID);
  if Rez = S_OK then  // Объект найден
    Result := true
  else
    Result := false;
end;

Или ещё короче:

function CheckExcelInstall: boolean;
var
  ClassID: TCLSID;
begin
  Result:=CLSIDFromProgID(PWideChar(WideString(ExcelApp)), ClassID) = S_OK;
end;

Если функция CLSIDFromProgID находит CLSID OLE-объекта, то, соответственно — Excel установлен.

Но проверка на наличие установленного Excel — это только часть необходимых операций перед началом работы. Другой немаловажной проверкой является проверка на наличие уже запущенного экземпляра Excel. Если этого не делать, то может случиться такая нехорошая ситуация, когда в системе будет зарегистрировано …дцать процессов Excel и все оперативная память волшебным образом утекает «в трубу» — за такое могут и по ушам надавать. Но мы-то свои уши бережем. Пишем вторую функцию проверки.

2. Определяем запущен ли Excel

function CheckExcelRun: boolean;
begin
  try
    MyExcel:=GetActiveOleObject(ExcelApp);
    Result:=True;
  except
    Result:=false;
  end;
end;

Думаю тут лишних объяснений не потребуется? Всё предельно просто — если есть активный процесс Excel, то мы просто получаем на него ссылку и можем использовать Excel для своих корыстных целей. Главное — не забыть проверить — может кто-то этот самый Excel забыл закрыть, но это другой момент. Будем считать, что Excel в нашем полном распоряжении.

3. Как запустить Excel?

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

function RunExcel(DisableAlerts:boolean=true; Visible: boolean=false): boolean;
begin
  try
{проверяем установлен ли Excel}
    if CheckExcelInstall then
      begin
        MyExcel:=CreateOleObject(ExcelApp);
//показывать/не показывать системные сообщения Excel (лучше не показывать)
        MyExcel.Application.EnableEvents:=DisableAlerts;
        MyExcel.Visible:=Visible;
        Result:=true;
      end
    else
      begin
        MessageBox(0,'Приложение MS Excel не установлено на этом компьютере','Ошибка',MB_OK+MB_ICONERROR);
        Result:=false;
      end;
  except
    Result:=false;
  end;
end;

Здесь мы в начале проверяем, установлен ли Excel в принципе и, если он все же установлен, запускам. При этом мы можем сразу показать окно Excel пользователю — для этого необходимо выставить параметр Visible в значение True.

Также рекомендую всегда отключать системные сообщения Excel, иначе, когда программа начнет говорить голосом Excel — пользователь может занервничать.

Переходим к следующему этапу работы — созданию рабочей книги.

4. Создаем пустую рабочую книгу Excel

Для создания пустой рабочей книги я обычно использую вот такую функцию:

function AddWorkBook(AutoRun:boolean=true):boolean;
begin
  if CheckExcelRun then
    begin
      MyExcel.WorkBooks.Add;
      Result:=true;
    end
  else
   if AutoRun then
     begin
       RunExcel;
       MyExcel.WorkBooks.Add;
       Result:=true;
     end
   else
     Result:=false;
end;

Второй вариант (более лаконичный):

function AddWorkBook(AutoRun: boolean = true): boolean;
begin
  Result := CheckExcelRun;
  if (not Result) and (AutoRun) then
  begin
    RunExcel;
    Result := CheckExcelRun;
  end;
  if Result then
    MyExcel.WorkBooks.Add;
end;

То есть сразу проверяю запущен ли Excel и, если он не запущен, то либо запускаю и добавляю книгу, либо просто выхожу — всё зависит от ситуации.

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

function GetAllWorkBooks:TStringList;
var i:integer;
begin
  try
    Result:=TStringList.Create;
    for i:=1 to MyExcel.WorkBooks.Count do
      Result.Add(MyExcel.WorkBooks.Item[i].FullName)
  except
    MessageBox(0,'Ошибка перечисления открытых книг','Ошибка',MB_OK+MB_ICONERROR);
  end;
end;

Функция возвращает список TStringList всех рабочих книг Excel открытых в данный момент. Обратите внимание, что в отличие от Delphi Excel присваивает первой книге индекс 1, а не 0 как это обычно делается в Delphi при работе, например, с теми же индексами в ComboBox’ах.

Ну, и наконец, после того, как поработали с книгами — их требуется закрыть. Точнее сохранить, а потом уж закрыть.

5. Сохраняем рабочую книгу и закрываем Excel

Для того, чтобы сохранить рабочую книгу, я использовал такую функцию:

function SaveWorkBook(FileName:TFileName; WBIndex:integer):boolean;
begin
  try
    MyExcel.WorkBooks.Item[WBIndex].SaveAs(FileName);
    if MyExcel.WorkBooks.Item[WBIndex].Saved then
      Result:=true
    else
      Result:=false;
  except
    Result:=false;
  end;
end;

Если у Вас открыто 10 книг — просто вызываете функцию 10 раз, меняя значение параметра WBIndex и имени файла и дело в шляпе.

А закрывается Excel вот так:

function StopExcel:boolean;
begin
  try
    if MyExcel.Visible then MyExcel.Visible:=false;
    MyExcel.Quit;
    MyExcel:=Unassigned;
    Result:=True;
  except
    Result:=false;
  end;
end;

Вот набор тех основных функций с которых начинается вся интеграция Excel в приложения написанные на Delphi. В следующий раз займемся работой с конкретной книгой — научимся записывать и читать данные из книг.

Книжная полка

Название:Разработка приложений Microsoft Office 2007 в Delphi

Описание Описаны общие подходы к программированию приложений MS Office. Даны программные методы реализации функций MS Excel, MS Word, MS Access и MS Outlook в среде Delphi.

купить книгу delphi на ЛитРес

5
3
голоса

Рейтинг статьи

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Студворк — интернет-сервис помощи студентам

Тут иногда бывают вопросы о том, как можно из Delphi работать с документами Excel. Решил написать такую инструкцию, где рассказано об основных действиях.

Для работы с OLE нужно к строке Uses добавить модуль ComObj. Так же нужно объявить переменную типа Variant.

Delphi
1
2
Uses ComObj;
Var Ap : Variant;

Что бы начать работу с Экселем, нужно создать OLE объект:
Ap := CreateOleObject(‘Excel.Application’);

После этого нужно либо создать новую книгу:
Ap.Workbooks.Add;
либо открыть файл:
Ap.Workbooks.Open(<имя файла>);
Что бы открыть файл только для чтения, нужно указать:
Ap.Workbooks.Open(<имя файла>,0,True);
где True и указывает, что файл открывается только для чтения.

По умолчанию окно Excel не будет отображаться… что бы оно появилось, нужно выполнить
Ap.Visible := True;
Но это желательно делать в последний момент, т.к. когда окно видимое, то все изменения в нём происходят медленнее. Поэтому, лучше оставить его невидимым, сделать там все необходимые изменения, и только после этого сделать его видимым или закрыть. Если вы его оставите невидимым, то процесс EXCEL.EXE останется висеть в памяти, даже когда будет закрыто ваше приложение.

Что бы закрыть Excel, выполните Ap.Quit или Ap.Application.Quit. Честно говоря, я не знаю, чем они отличаются.
Что бы при закрытии не выдавался запрос на сохранение файла, можно отключить сообщения:
Ap.DisplayAlerts := False;

Что бы записать или прочитать содержимое ячейки можно использовать Ap.Range[<имя ячейки>] или Ap.Cells[<позиция по Y>,<позиция по X>]
Ap.Range[‘D1’] := ‘Ляляля’;
Ap.Cells[1,4] := ‘Ляляля’;

Эти две строки выполняют одно и тоже действие: записывают строку «Ляляля» в ячейку D1
Читать значение из ячейки таким же образом:
S := Ap.Range[‘D1’];
или
S := Ap.Cells[1,4];
Так же можно записывать значение сразу в несколько ячеек… можно перечислить через точку с запятой или указать диапазон через двоеточие:

Delphi
1
2
3
4
5
6
7
8
Ap.Range['A2;B5;D1'] := 'Ляляля'; // записывает строку в 3 ячейки: A2, B5 и D1
Ap.Range['A2:D5'] := 'Ляляля'; // записывает строку во все ячейки диапазона A2:D5
Ap.Range[Ap.Cells[2,1],Ap.Cells[5,4]] := 'Ляляля'; // Тоже самое, что и предыдущая строка
Ap.Cells := 'Ляляля'; // Изметятся все ячейки
Ap.Rows['2'] := 'Ляляля'; // Изменятся все ячейки второй строки
Ap.Rows[2] := 'Ляляля';  // Тоже самое
Ap.Columns['B'] := 'aaa';  // Изменятся все ячейки в столбце B
Ap.Columns[2] := 'aaa';  // Тоже самое

Изменение свойств текста
Всё это можно применять как к отдельным ячейкам, так и к группам ячеек, строк, столбцов и т.п. Я буду показывать примеры на Ap.Cells… но Вам никто не мешает использовать Ap.Range[‘D5’], Ap.Range[‘A2:E8’], Ap.Columns[‘B:F’] и т.п.

Delphi
1
2
3
Ap.Cells.Font.Bold := True;  // Жирный шрифт
Ap.Cells.Font.Italic := True; // Курсив
Ap.Cells.Font.Underline := True; // Подчёркивание

Изменение цвета фона ячеек:

Delphi
1
2
Ap.Rows[1].Interior.Color := rgb(192, 255, 192); // Первую строку закрашиваем в зелёный цвет, используя RGB
Ap.Cells[2,1].Interior.Color := clRed; // Ячейку A2 закрашиваем в красный цвет, используя константу clRed

Наверное, вы обращали внимание, что в новом документе появляются 3 листа (их список отображается внизу программы Excel). По умолчанию, мы работаем с активным листом (сразу после создания это первый лист). Но при желании, мы можем использовать и другие листы. Доступ к ним осуществляется с помощью Worksheets[<номер листа>]. Обратите внимание, что нумеруются они начиная с 1 (а не с 0, как привыкли все программисты).

Delphi
1
2
Ap.Worksheets[1].Name := 'Первый лист'; // Изменить название первого листа
Ap.Worksheets[2].Name := 'Ещё один лист'; // Изменить название второго листа

Свойства страницы

Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Колонтитулы:
Ap.Worksheets[1].PageSetup.LeftFooter := 'Левый нижний колонтитул';
Ap.Worksheets[1].PageSetup.LeftHeader := 'Левый верхний колонтитул';
Ap.Worksheets[1].PageSetup.CenterFooter := 'Центральный нижний колонтитул';
Ap.Worksheets[1].PageSetup.CenterHeader := 'Центральный верхний колонтитул';
Ap.Worksheets[1].PageSetup.RightFooter := 'Правый нижний колонтитул';
Ap.Worksheets[1].PageSetup.RightHeader := 'Правый верхний колонтитул';
 
Ap.Worksheets[1].PageSetup.Draft := True; // Для черновой печати
Ap.Worksheets[1].PageSetup.BlackAndWhite := True;  // Для чёрно-белой печати
Ap.Worksheets[1].PageSetup.PrintGridlines := True; // При печати будет видна сетка
Ap.Worksheets[1].PageSetup.PrintHeadings := True;  // При печати будут печататься названия столбцов и номера строк
Ap.Worksheets[1].PageSetup.FirstPageNumber := 5;   // Начать нумерацию страниц с пятой
Ap.Worksheets[1].PageSetup.Orientation := 1;  // Ориентация бумаги: 1=Книжная, 2=Альбомная
Ap.Worksheets[1].PageSetup.PaperSize := 9; // Указать размер бумаги. 8=А3, 9=А4, 11=А5

Ниже приведён более полный список размеров бумаги из модуля ExcelXP:

Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const
  xlPaper10x14 = $00000010;
  xlPaper11x17 = $00000011;
  xlPaperA3 = $00000008;
  xlPaperA4 = $00000009;
  xlPaperA4Small = $0000000A;
  xlPaperA5 = $0000000B;
  xlPaperB4 = $0000000C;
  xlPaperB5 = $0000000D;
  xlPaperCsheet = $00000018;
  xlPaperDsheet = $00000019;
  xlPaperEnvelope10 = $00000014;
  xlPaperEnvelope11 = $00000015;
  xlPaperEnvelope12 = $00000016;
  xlPaperEnvelope14 = $00000017;
  xlPaperEnvelope9 = $00000013;
  xlPaperEnvelopeB4 = $00000021;
  xlPaperEnvelopeB5 = $00000022;
  xlPaperEnvelopeB6 = $00000023;
  xlPaperEnvelopeC3 = $0000001D;
  xlPaperEnvelopeC4 = $0000001E;
  xlPaperEnvelopeC5 = $0000001C;
  xlPaperEnvelopeC6 = $0000001F;
  xlPaperEnvelopeC65 = $00000020;
  xlPaperEnvelopeDL = $0000001B;
  xlPaperEnvelopeItaly = $00000024;
  xlPaperEnvelopeMonarch = $00000025;
  xlPaperEnvelopePersonal = $00000026;
  xlPaperEsheet = $0000001A;
  xlPaperExecutive = $00000007;
  xlPaperFanfoldLegalGerman = $00000029;
  xlPaperFanfoldStdGerman = $00000028;
  xlPaperFanfoldUS = $00000027;
  xlPaperFolio = $0000000E;
  xlPaperLedger = $00000004;
  xlPaperLegal = $00000005;
  xlPaperLetter = $00000001;
  xlPaperLetterSmall = $00000002;
  xlPaperNote = $00000012;
  xlPaperQuarto = $0000000F;
  xlPaperStatement = $00000006;
  xlPaperTabloid = $00000003;
  xlPaperUser = $00000100;

Распечатать

Delphi
1
Ap.Worksheets[1].PrintOut;

Выделение
Excel.Range[Excel.Cells[1, 1], Excel.Cells[5, 3]].Select;
а также любые другие комбинации выбора ячейки с окончанием .select — выбор 1 или группы ячеек

С выбранными ячейками возможны следующие преобразования:

1) объединение ячеек
Excel.Selection.MergeCells:=True;
2) перенос по словам
Excel.Selection.WrapText:=True;
3) горизонтальное выравнивание
Excel.Selection.HorizontalAlignment:=3;
при присваивании значения 1 используется выравнивание по умолчанию, при 2 — выравнивание слева, 3 — по центру, 4 — справа.
4) вериткальное выравнивание
Excel.Selection.VerticalAlignment:=1;
присваиваемые значения аналогичны горизонтальному выравниванию.
5) граница для ячеек
Excel.Selection.Borders.LineStyle:=1;
При значении 1 границы ячеек рисуются тонкими сплошными линиями.
Кроме этого можно указать значения для свойства Borders, например, равное 3. Тогда установится только верхняя граница для блока выделения:
Excel.Selection.Borders[3].LineStyle:=1;
Значение свойства Borders задает различную комбинацию граней ячеек.
В обоих случаях можно использовать значения в диапазоне от 1 до 10. ]

//9 и 11 — лучшие

\\\\\\\\\\\\\\\\

Установка пароля для активной книги может быть произведена следующим образом:

Delphi
1
2
3
4
5
6
try
// попытка установить пароль
Excel.ActiveWorkbook.protect('pass');
except
// действия при неудачной попытке установить пароль
end;

где pass — устанавливаемый пароль на книгу.

Снятие пароля с книги аналогично, использовуем команду

Delphi
1
Excel.ActiveWorkbook.Unprotect('pass');

где pass — пароль, установленный для защиты книги.

Установка и снятие пароля для активного листа книги Excel производится командами

Delphi
1
2
Excel.ActiveSheet.protect('pass'); // установка пароля
Excel.ActiveSheet.Unprotect('pass'); // снятие пароля

где pass — пароль, установленный для защиты книги.
Вспомогательные операции в EXCEL

Удаление строк со сдвигом вверх:

Delphi
1
2
Excel.Rows['5:15'].Select;
Excel.Selection.Delete;

при выполнении данных действий будут удалены строки с 5 по 15.

Установка закрепления области на активном листе Excel

Delphi
1
2
3
4
5
6
// снимаем закрепление области, если оно было задано
Excel.ActiveWindow.FreezePanes:=False;
// выделяем нужную ячейку, в данном случае D3
Excel.Range['D3'].Select;
// устанавливаем закрепление области
Excel.ActiveWindow.FreezePanes:=True;

Спасибо VampireKB за дополнения




Подготовка

Откроем нашу среду разработки Delphi (в моем случае это Delphi 2010). Создадим новый проект. Должен сказать, что работа с Excel происходит через объекты COM. Поэтому нам надо подключить в раздел

USES

нашего проекта модуль

COMObj

. Так же нам понадобится модуль

ActiveX

. Это будет выглядеть примерно вот так:

unit Unit1;
 
interface
 
uses
  Windows,, ActiveX, COMObj;



Проверка наличия установленного Microsoft Excel в операционной системе

Теперь собственно нам нужно, как писалось выше, определить наличие Excel в системе. Если он не будет обнаружен, то мы сообщим об этом пользователю и завершим программу. Для этого создадим простенькую функцию и объявим её в секции

private

нашей формы

TForm1

:

private
    function CheckExcelInstalled(AValue: String): boolean;function TForm1.CheckExcelInstalled(AValue: String): boolean;
var
  FCLSID: TCLSID;
begin
  Result := (CLSIDFromProgID(PChar(AValue), FCLSID) = S_OK);
end;

Функция

CLSIDFromProgID

определена в модуле

ActiveX

и имеет следующее описание:

function CLSIDFromProgID(pszProgID: POleStr; out clsid: TCLSID): HResult;

Данная функция ищет

CLSID

, по соответствующему

ProgID

в реестре, в нашем случае мы ищем «Excel.Application». Если открыть реестр и перейти к ветке:


HKEY_LOCAL_MACHINESOFTWAREClasses

то там можно будет обнаружить много разделов. Среди них есть как раз нужный нам «Excel.Application» (при условии, что данное ПО установлено в системе). К сведению: у этого раздела имеется еще два подраздела:


HKEY_LOCAL_MACHINESOFTWAREClassesExcel.ApplicationCLSID

HKEY_LOCAL_MACHINESOFTWAREClassesExcel.ApplicationCurVer

Параметры функции:



pszProgID

: строка, содержащая ProgID.



clsid

: получает CLSID.

Возвращаемые значения:



S_OK

— CLSID создан успешно.



CO_E_CLASSSTRING

— CLSID зарегистрированный для ProgID неверен.



REGDB_E_WRITEREGDB

— ошибка записи в реестр.



E_OUTOFMEMORY

— нет памяти.



E_INVALIDARG

— указывает что один или более аргументов неверны. Стоит проверить правильность строки pszProgID.



E_UNEXPECTED

— неизвестная ошибка.

Теперь на событии

OnCreate

формы

TForm1

напишем следующий код:

procedure TForm1.FormCreate(Sender: TObject);
begin
  if not CheckExcelInstalled('Excel.Application') then
  begin
    Application.MessageBox(PChar('Для работы программы необходимо ' +
          'иметь установленное программное обеспечение Microsoft Excel.'#13#10
          + 'Приложение будет закрыто.'), 'Ошибка', MB_ICONERROR);
    Halt;
  end;
end;

При загрузке приложения ищем наличие Excel в системе. Если не находим, то выводим сообщение пользователю и закрываем программу с помощью метода

Halt

. Если находим, то просто показываем форму.



Проверить, запущен ли Microsoft Excel

Далее давайте кинем на форму одну кнопку TButton. На событии

OnClick

будем писать код работы с Excel. Первое что мы сделаем, это добавим локальную переменную типа

Variant

для доступа к Excel с именем, например,

FExcel

:

Теперь определимся, что мы хотим. Я всегда создавал новый экземпляр экселя, даже если он был уже запущен в системе. Но я это не вы. Поэтому я покажу вам, как можно использовать уже запущенный Excel. Создадим функцию и объявим ее так же в секции

private

:

privatefunction CheckExcelRun(AValue: String; var ADest: Variant): boolean;function TForm1.CheckExcelRun(AValue: String; var ADest: Variant): boolean;
begin
  try
    ADest := GetActiveOleObject(AValue);
    Result := true;
  except
    Result := false;
  end;
end;

Функция

GetActiveOleObject

определена в модуле

COMObj

и имеет следующее описание:

function GetActiveOleObject(const ClassName: string): IDispatch;

Функция возвращает ссылку на интерфейс

IDispatch

активного OLE-объекта указанного класса. Она использует информацию из таблицы активных объектов OLE (OLE running object table). Идентификатор класса объекта OLE в таблице активных объектов OLE передается в параметре

ClassName

. При невозможности выполнения функции GetActiveOleObject возникает исключение EOleSysError.

Теперь как пользоваться нашей функцией:

procedure TForm1.Button1Click(Sender: TObject);
var
  FExcel: Variant;
begin
  if not CheckExcelRun('Excel.Application', FExcel) then
    Exit;
end; 

Стоить пояснить. Если в системе запущена программа Excel, то функция

GetActiveOleObject

находит его, при этом функция

CheckExcelRun

возвращает

TRUE

. Ссылка на интерфейс передается через параметр «var ADest» нашей созданной переменной

FExcel

. Теперь вы можете работать с этим экземпляром Excel. Если же запущенный эксель не найден, то функция

GetActiveOleObject

вызывает исключение, простыми словами выходит ошибка. Поэтому мы поместили эту функцию в блок

try…except…end

. Вот в секции

except

мы присваиваем результат

FALSE

и т.о. выходим из процедуры

Button1Click

с помощью метода

Exit

. Но это все отступление. Давайте не будем искать уже запущенный ексель, а будем создавать новый и по завершению работы с ним корректно закроем его и высвободим память.



Создание нового экземпляра Microsoft Excel

Создать новый экземпляр можно с помощью функции

CreateOleObject

. Давайте сразу напишем шаблон.

 procedure TForm1.Button1Click(Sender: TObject);
var
  FExcel: Variant;
begin
  try
    // Создаем новый экземпляр Excel
    FExcel := CreateOleObject('Excel.Application');
    FExcel.Visible := true;
    ... // Тут ваш код
  finally
    // Если переменная не пустая, то...
    if not VarIsEmpty(FExcel) then
    begin
      // ...отключаем диалог с вопросом сохранять ли файл при выходе или нет
      FExcel.DisplayAlerts := false;
      // Закрываем Excel
      FExcel.Quit;
      // Присваиваем неопределенный тип, освобождая при этом 
      // процесс excel.exe, чтобы он мог завершиться. Если этого не
      // сделать, то процесс останется висеть в памяти.
      FExcel := Unassigned;
    end;
  end;
end;

Функция

VarIsEmpty

определена в модуле

Variants

и имеет следующее описание:

function VarIsEmpty(const V: Variant): Boolean;

Функция проверяет, определен ли тип значения

Variant

-переменной. Возвращает

TRUE

, если переменная имеет неопределенный тип (Unassigned). Если типу переменной соответствует значение какой-либо константы

VarType

типа

TVarData

, то функция возвращает

FALSE

.



Операции над программой Microsoft Excel из Delphi

Теперь давайте рассмотрим, что вообще можно делать с Excel из Delphi. Операций достаточно много. И все описать просто невозможно. Но если в моем описании не окажется нужного вам метода, то огорчаться не стоит. Методы можно узнать самому. Открываете Excel. Затем в меню

Сервис – Макрос – Начать запись

. Там жмем ОК и выполняем необходимые нам операции в Excel. Когда закончите, жмем на

Стоп

. Затем нажимаем комбинацию клавиш

Alt+F11

и перед нами открывается

редактор Visual Basic

. Вот в нем слева вверху есть древовидный список. В нем находится папка с названием

Modules

. В свою очередь в ней располагаются модули. Вот эти модули и есть ваши макросы. Кликнем два раза на тот модуль, который мы только что записали (если это первая запись, то модуль будет один). И тут будут отображены все ваши действия в виде исходного кода на языке Visual Basic. Вам остается только перевести это дело на Delphi.

По поводу констант. В Delphi придется объявлять их самим. Посмотреть чему они равны можно в том же редакторе Visual Basic. Делается это так. Вписываем любую нужную нам константу в редакторе. Кликаем правой клавишей мыши, чтобы появилось контекстное меню. И там выбираем пункт

«Quick Info»

. При этом будет показана всплывающая подсказка со значением этой константы. Все просто! Единственное оговорюсь. В Visual Basic будет показано так, например:

&HFFFFEFF4

, но Delphi этого не поймет. Надо объявлять в нем так:

$FFFFEFF4

.


Работа с окном (формой) Excel


FExcel.Visible := true;


Делать Excel видимым (True)/не видимым(False). При установке

Visible

в

FALSE

, программа Excel Так же пропадает из панели задач Windows.


FExcel.Application.EnableEvents := false;


Показывать (True)/не показывать(False) системные сообщения Excel. Рекомендую отключать сообщения при построении отчета. А сам код писать после создания экземпляра Excel. Это увеличит скорость создания отчета. Пример:

FExcel := CreateOleObject('Excel.Application');
FExcel.Visible := false;
FExcel.Application.EnableEvents := false;


FExcel.DisplayAlerts := false;


Показывать (True)/не показывать(False) предупреждающие сообщения.


FExcel.WindowState := xlMaximized;


Состояние окна Excel:


const xlMaximized = $FFFFEFD7 (или -4137)

— развернуть Excel на весь экран.


const xlNormal = $FFFFEFD1 (или -4143)

— восстановить Excel.


const xlMinimized = $FFFFEFD4 (или -4140)

— свернуть Excel на панель задач.


FExcel.ScreenUpdating := false;


Включить (True)/отключить (False) перерисовку окон. Используется для ускорения работы макроса, т.к. в это время не обновляется экран. Я в своих проектах не использовал данный метод, но возможно построение отчета будет выполняться быстрее, если отключить обновление окон Excel. После построения отчета перерисовку окон можно будет включить обратно.


Работа над книгой (Workbooks)


FExcel.Workbooks.Open(path);


Открытие существующей книги (где path — путь к фалу). Функция Open описана так:

function Open(
  const Filename: String; 
  UpdateLinks: Variant; 
  ReadOnly: Variant;
  Format: Variant; 
  Password: Variant; 
  WriteResPassword: Variant; 
  IgnoreReadOnlyRecommended: Variant; 
  Origin: Variant;
  Delimiter: Variant; 
  Editable: Variant; 
  Notify: Variant;
  Converter: Variant; 
  AddToMru: Variant
): Workbook;


FileName

— обязательный элемент. Имя открываемого файла, желательно с полным путем, иначе Excel будет искать этот файл в каталоге по умолчанию;


UpdateLinks

— Необязательный параметр. Определяет способ обновления связи в открываемом файле. Если данный аргумент отсутствует, то пользователю будет выдан запрос на определение способа обновления связей. Данный параметр имеет следующие допустимые значения:

0 – никакие связи не обновляются;

1 – обновляются внешние ссылки, но не обновляются удаленные ссылки;

2 – обновляются удаленные ссылки, но не обновляются внешние ссылки;

3 – обновляются оба типа ссылок


ReadOnly

— необязательный параметр. Если файл имеет атрибут только для чтения, то при открытии его в Excel выдается соответствующее предупреждение*. Чтобы его игнорировать, передайте в качестве данного параметра True.


Format

— необязательный параметр. При открытии текстового файла этот параметр определяет вид символов-разделителей. Ниже представлены допустимые значения данного аргумента:

1 – символы табуляции;

2 – запятые;

3 – пробелы;

4 – точка с запятой;

5 – разделители отсутствуют;

6 – вид разделителя определяется пользователем (с помощью параметра Delimiter).


Password

— необязательный параметр, задающий строку пароля, необходимого для открытия защищенной рабочей книги. Если при открытии защищенной книги данный аргумент отсутствует, то пользователю будет выдан запрос на ввод пароля*.


WriteResPassword

— необязательный параметр. Предназначен для задания пароля, необходимого для записи в рабочую книгу. Если при открытии защищенной книги данный аргумент отсутствует, то пользователю будет выдан запрос на ввод пароля*.


IgnoreReadOnlyRecommended

— необязательный параметр. В случае задания ему значения True этот аргумент позволяет устранить вывод сообщения с рекомендацией открытия книги только для чтения (в том случае, если данная рабочая книга была сохранена с параметром Read-Only Recommended).


Origin

— необязательный параметр. При открытии текстового файла этот параметр указывает, где был создан этот файл, что необходимо для правильного распознавания страницы кодировки. Значениями данного аргумента может быть одна из констант: xlMacintosh, xlWindows или xlMSDOS**. Если данный аргумент отсутствует, то используются текущие параметры операционной системы.


Delimiter

— необязательный параметр. Если открывается текстовый файл и параметр Format имеет значение, равное 6, то этот аргумент определяет символ-разделитель.


Editable

— необязательный параметр. Если файл является файлом MS Excel 4.0, то этот параметр при задании ему значения True позволяет открыть надстройку как видимое окно. По умолчанию ему присвоено значение False.


Notify

— необязательный параметр. Если файл не может быть открыт для записи, то при задании данному аргументу значения True файл будет добавлен в список уведомления. Если данный аргумент равен False или отсутствует, то открыть файл будет невозможно.


Converter

— необязательный параметр. Предназначен для определения индекса фильтра, который будет использован при открытии данного файла. Если фильтр не сможет распознать формат файла, то будут последовательно использованы все доступные фильтры.


AddToMRU

— необязательный параметр типа Variant. Если данный параметр имеет значение True, то рабочая книга будет добавлена в список недавно использованных файлов. По умолчанию ему присвоено значение False.



Примечание:


* Сообщения будут выведены на экран, если вы не отключили их с помощью:


FExcel.Application.EnableEvents := false;


Показывать (True)/не показывать(False) системные сообщения Excel.

** Delphi не знает о существовании констант, поэтому чтобы посмотреть их значения, нужно воспользоваться средствами VBA. Эти константы соответственно равны 1, 2 и 3.

***


FExcel.SheetsInNewWorkbook := X;


Задать количество страниц в книге, где X — это количество страниц. Диапазон значений 1..255. Данный метод применяется только с


FExcel.Workbooks.Add;


и в коде Delphi пишется перед методом Add.


FExcel.Workbooks.Add(path);

// path — путь к фалу

Создание новой книги. Причем создается стандартная книга с тремя листами. В этом методе можно указать стандартный тип шаблона Excel. Если же в нем указать имя (с полным путем) подготовленного файла (шаблоном может быть и «обычный» файл XLS, а не только файл XLT), то можно открыть книгу на диске как шаблон.


FExcel.Workbooks.Add(Template);


Template — Необязательный параметр. Этот параметр определяет, как будет создана рабочая книга. Если этот параметр будет иметь значение строки, определяющей имя файла (включая путь) существующей рабочей книги, то создается рабочая книга с использованием данного файла как шаблона. В случае когда данный параметр является одной из констант:

xlWBATExcel4IntlMacroSheet = 4;



xlWBATExcel4MacroSheet = 3;



xlWBATWorksheet = $FFFFEFB9 (или -4167);



xlWBATChart = $FFFFEFF3 (или -4109);


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

SheetslnNewWorkbook

.

***


FExcel.Workbooks[1].SaveAs(path);

// path — путь к фалу

Сохранить книгу как… Чтобы укоротить код можно создать переменную

FWorkbook

типа

Variant

и присвоить ей ссылку либо на первую[1] книгу, либо на вновь созданную:


FWorkbook := FExcel.Workbooks[1];

либо

FWorkbook := FExcel.Workbooks.Add;


Тогда сохранение может выглядеть вот так:


FWorkbook.SaveAs(path);

// path — путь к фалу

Функция SaveAs описана так:

function SaveAs(
  FileName: Variant; 
  FileFormat: Variant; 
  Password: Variant;
  WriteResPassword: Variant;
  ReadOnlyRecommended: Variant;
  CreateBackup: Variant;
  AccessMode: Variant;
  ConflictResolution: Variant;
  AddToMru: Variant;
  TextCodePage: Variant;
  TextVisualLayout: Variant
): Workbook;


FileName

— необязательный параметр. Задает строку, определяющую имя сохраняемого файла. Можно указать полный путь или только имя файла (в этом случае файл будет сохранен в текущей папке).


FileFormat

— необязательный параметр. Данный параметр задает формат файла. Его значением может быть одна из констант, описанных в

таблице 1

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

Формат                                            Константа
 
Книга MS Excel                                    xlWorkbookNormal
Web-страница                                      xlHTML
Шаблон                                            xlTemplate
Текстовые файлы (с разделителями табуляции)       xlTextWindows
Текст Юникод                                      xlUnicodeText
Книга MS Excel 5.0/95                             xlExcel7
Книга MS Excel 95/97/2000                         xlExcel9795
CSV (разделители – запятые)                       xlCSVWindows
Файл MS Excel 4.0                                 xlExcel4
Файл MS Excel 3.0                                 xlExcel3
Файл MS Excel 2.1                                 xlExcel2
Книга MS Excel 4.0                                xlExcel4Workbook
WK4 (1-2-3)                                       xlWK4
WK3, FM3 (1-2-3)                                  xlWK3FM3
WK3 (1-2-3)                                       xlWK3
WK1, FMT (1-2-3)                                  xlWK1FMT
WK1, ALL (1-2-3)                                  xlWK1ALL
WK1 (1-2-3)                                       xlWK1
WKS (1-2-3)                                       xlWKS
WQ1 (Quattro Pro/Dos)                             xlWQ1
DBF4 (dBase IV)                                   xlDBF2
DBF3 (dBase III)                                  xlDBF3
DBF2 (dBase II)                                   xlDBF4
Форматированный текст (разделители – пробелы)     xlTextPrinter
Текст (Macintosh)                                 xlTextMac
Текст (MS-DOS)                                    xlTextMSDOS
CSV (Macintosh)                                   xlCSVMac
CSV (MS-DOS)                                      xlCSVMSDOS
DIF (Data Interchange Format)                     xlDIF
SYLK (Symbolic Link)                              xlSYLK
Надстройка MS Excel                               xlAddln

Таблица 1 — Форматы файлов MS Excel 2000 и соответствующие им константы.


Password

— необязательный параметр. Данный параметр определяет защитный пароль для сохраняемого файла. Пароль определяется строкой, содержащей не более 15 символов.


WriteResPassword

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


ReadOnlyRecommended

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


CreateBackup

— необязательный параметр. Определяет возможность создания резервного файла (значение True).


AccessMode

— необязательный параметр. Этот параметр определяет режим доступа к рабочей книге и может иметь одно из следующих значений:

xINoChange – режим доступа не изменяется (равно 1);

xlShared – общие листы (равно 2);

xlExclusive – монопольный доступ (равно 3).


ConflictResolution

— необязательный параметр. Определяет способ разрешения конфликтов в том случае, если книга имеет общие листы. Значением данного свойства может быть значение одной из констант:

xlUserResolution — отображает окно диалога для разрешения конфликтов (равно 1);

xlLocalSessionChanges — автоматически принимаются изменения локального пользователя (равно 2);

xlOtherSessionChanges — принимаются другие изменения вместо изменений локального пользователя (равно 3).


AddToMru

— необязательный параметр. Позволяет поместить сохраняемый файл в список сохраненных файлов в меню Файл (File). Для реализации этой возможности значение этого аргумента должно равняться True.


TextCodePage

— необязательный параметр. В локализованной версии MS Office не используется.


TextVisualLayout

— необязательный параметр. В локализованной версии MS Office не используется.

***


FWorkbook.Save;


Если книга была открыта, то файл перезапишется. Если была создана новая книга, то будет задан вопрос о сохранении файла. Если вы нажмете «Нет», то будет вызвано исключение.


FWorkbook.Close;


Закрыть книгу. При этом если Excel видимый, то появится диалог с вопросом, хотите ли вы сохранить файл.

Функция Close описана так:

function Close(
  SaveChanges: Variant; 
  FileName: Variant; 
  RouteWorkbook: Variant
): Workbook;


SaveChanges

— Необязательный элемент. Если данный параметр опущен, то будет выдан запрос на сохранение файла. Если равен

FALSE

, то книга закроется без сохранения и каких-либо оповещений, даже при установленном параметре

FileName

. Если равен

TRUE

, то сразу появится проводник с предложением выбрать место, куда сохранить файл. При установленном параметре FileName, книга будет сохранена без появления каких-либо диалогов.


FileName

— Необязательный элемент. Данный параметр определяет имя файла, в который будут записаны изменения.


RouteWorkbook

— Необязательный элемент. Если он имеет значение True, то рабочая книга будет послана следующему получателю.



Примечание:

если файл уже существует, то тогда будет выведен диалог с вопросом о перезаписи файла.


Пример:

// Отключаем предупреждающие сообщения,
// в нашем случае чтобы не было вопроса о том,
// перезаписывать ли файл, если таковой существует
FExcel.DisplayAlerts := false;
// Сохраняем книгу в файл "123.xls" на диск D:
FWorkbook.Close(true, 'D:123.xls');


Установка параметров страницы, предварительный просмотр и печать


FExcel.Workbooks[1].WorkSheets[1] .Name := ‘Отчёт’;


Присваиваем первому листу в первой книге имя «Отчет». Если будем работать с самим листом, то чтобы не писать много кода мы можем просто объявить новую переменную, например,

FSheet

типа

Variant

:

 procedure TForm1.Button1Click(Sender: TObject);
var
  FExcel, FSheet: Variant;
begin
  try
    ... // Тут ваш код
  finally
    ...
      // Обязательно! нужно все созданные Variant-переменные 
      // очищать перед выходом
      FSheet := Unassigned;
      FExcel := Unassigned;
    ...
  end;
end;



Примечание

: в принципе Delphi сама следит за корректным удалением переменных, но я считаю будем правильным сделать это вручную. Не стоит полагаться на систему.

И присвоим ей ссылку на наш Лист1 первой книги:

FSheet := FExcel.Workbooks[1].WorkSheets[1];
FSheet.Name := 'Отчёт';

Вот так вот будет проще. Теперь мы будет обращаться уже к переменной

FSheet

, а не к

FExcel

.


FSheet.PageSetup.Orientation := 2;


Ориентация страницы (1 – книжная, 2 – альбомная).


FSheet.PageSetup.LeftMargin := X;


Отступ от левого края страницы


FSheet.PageSetup.RightMargin := X;


Отступ от правого края страницы


FSheet.PageSetup.TopMargin := X;


Отступ от верхнего края страницы.


FSheet.PageSetup.BottomMargin := X;


Отступ от нижнего края страницы.


FSheet.PageSetup.HeaderMargin := X;


Высота верхнего колонтитула страницы.


FSheet.PageSetup.FooterMargin := X;


Высота нижнего колонтитула страницы.

Где X — это отступ в пикселях. Можно в принципе воспользоваться функцией перевода из, например, сантиметров в пиксели. Делается это так:


FSheet.PageSetup.BottomMargin := FExcel.Application.CentimetersToPoints(1.5);


Это будет полтора сантиметра, а как там переводится в пиксели это не наша забота.


FSheet.PageSetup.Zoom := false (или значение в процентах Z);


Масштаб. Принимает следующие значения:


Z

— «Установить Z% от натуральной величины», где X – это количество процентов. Т.е. если вы решите включить масштабирование, то вам необходимо вместо

FALSE

подставить число, например: FSheet.PageSetup.Zoom := 100; (что означает 100%).


FALSE

— «Разместить не более чем на: X стр. в ширину и Y стр. в высоту, где:


FSheet.PageSetup.FitToPagesWide := 1;


Количество страниц в ширину (X).


FSheet.PageSetup.FitToPagesTall := 100;


Количество страниц в высоту (Y).


FSheet.PageSetup.CenterFooter := ‘Стр. &С из &К’

;

Центральный нижний колонтитул. В нем будет информации о номере страницы из всего страниц. Данные переменные (&C, &K и т.п.) описаны в справочнике самого Excel. Аналогично:


FSheet.PageSetup.LeftFooter:=’Левый нижний колонтитул’;

FSheet.PageSetup.RightFooter:=’Правый нижний колонтитул’;

FSheet.PageSetup.LeftHeader:=’Левый верхний колонтитул’;

FSheet.PageSetup.CenterHeader:=’Центральный верхний колонтитул’;

FSheet.PageSetup.RightHeader:=’&7Правый верхний колонтитул’;

*



Примечание:

*для изменения размера шрифта добавьте к колонтитулу управляющий символ «&» и размер шрифта, в нашем случае 7


// const xlAutomatic = $FFFFEFF7 (или -4105)



FSheet.PageSetup.FirstPageNumber := xlAutomatic;


Номер первой страницы. В данном случае «Авто».


FSheet.PageSetup.Order := 1;


Последовательность вывода страниц на печать (1 — вниз, затем вправо; 2 — вправо, затем вниз).


FSheet.PrintPreview;


Предварительный просмотр страницы. Если вы не продолжите печать, а нажмете «Отмена», то будет вызвано исключение.



Примечание

: Excel, перед выполнением этой команды, обязательно должен быть видимым (FExcel.Visible := true;).


FSheet.PrintOut;


Вывод страницы на печать. Функция PrintOut описана так:

function PrintOut(
  From: Variant; 
  To: Variant; 
  Copies: Variant;
  Preview: Variant;
  ActivePrinter: Variant;
  PrintToFile: Variant;
  Collate: Variant
): Workbook;


From

— необязательный параметр. Задает страницу, с которой начинается печать. Если этот аргумент отсутствует, то печать начинается с первой страницы.


To

— необязательный параметр. Задает последнюю печатаемую страницу. Если этот аргумент отсутствует, то печать продолжается до последней страницы.


Copies

— необязательный параметр. Задает количество печатаемых копий.


Preview

— необязательный параметр, принимающий одно из двух значений:


TRUE

— приводит к выводу перед печатью окна предварительного просмотра;


FALSE

(значение по умолчанию) — печать производится без предварительного просмотра.


ActivePrinter

— необязательный параметр. Задает имя активного принтера.


PrintToFile

— необязательный параметр. Если данный параметр имеет значение True, то производится печать в файл. При этом пользователю выдается запрос на ввод имени файла.


Collate

— необязательный параметр. Если ему задать значение True, то копии будут объединены.

Пример:

FExcel.Application.EnableEvents := true;
FExcel.DisplayAlerts := true;
FExcel.Visible := true;
FSheet.PrintOut(1, 2, 1, False, PrintToFile := True)

Печатаем с 1 по 2 страницы, одну копию, без предварительного просмотра в файл. Заметьте, что мы пропустили параметр ActivePrinter. Мы просто объявили PrintToFile := True.

***



Статья не окончена. Продолжение следует…

При составлении справочника я использовал собственные наработки и информацию со следующих сайтов:

1)

www.taurion.ru/excel

2)

www.superadm.net/index.php?name=pages&op=cat&id=19

3)

www.afalinasoft.com/rus/tips/

4)

www.codenet.ru/progr/delphi/stat/Excel-Export.php

Так же не обошлось без составления макросов в редакторе Visual Basic и последующим переводом кода из VBA в среду разработки Delphi 2010.

Поиск данных на листе.

Предлагаю поискать все ячейки, содержащие строку (или
подстроку) «Text», и изменить цвет фона этих ячеек. Для этого я использовал
методы Find и FindNext. На форму была добавлена кнопка, в обработчике которой
появился следующий код:

procedure TForm1.btnFindClick(Sender: TObject);
var ISheet: Excel8TLB._Worksheet;
IFirst, IRange: Excel8TLB.Range;
FirstAddress, CurrentAddress: string;
UsedRange: OLEVariant;
begin
if Assigned(IWorkbook) then
try
ISheet := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
try
UsedRange := ISheet.UsedRange[0];
IDispatch(IFirst) := UsedRange.Find(What:='Text', LookIn := xlValues, 
SearchDirection := xlNext);
if Assigned(IFirst) then begin
IRange := IFirst;
FirstAddress := IFirst.Address[EmptyParam, EmptyParam, xlA1, EmptyParam, EmptyParam];
repeat
IRange.Interior.ColorIndex := 37;
IDispatch(IRange) := UsedRange.FindNext(After := IRange);
CurrentAddress := IRange.Address[EmptyParam, EmptyParam, xlA1,
EmptyParam, EmptyParam];
until FirstAddress = CurrentAddress;
end;
finally
IRange := nil;
IFirst := nil;
ShowExcel;
end;
except
raise Exception.Create('Не могу чего-то сделать!');
end;
end;

Думаю, у каждого увидевшего этот код возникнет ощущение
неудовлетворенности. Да, в выделенной красным строке никаким ранним связыванием
и не пахнет. Более того, если вы попробуете вызвать метод Find с указанными
параметрами, заменив остальные на EmptyParam, вы получите исключение. Есть места
в Excel Type Library, работающие с ошибками. Я знаю достаточно этих мест. В
таких случаях я использую приведенный здесь прием. Я проверяю работоспособность
кода в редакторе VBA, а затем перехожу на позднее связывание. Так мне удалось
обойти несколько серьезных, по моему мнению, ошибок в Excel TLB. Раннее
связывание не должно быть догмой хотя бы из-за этого. Более того, перейдя
полностью на ранее связывание, мы получим более компактный, а следовательно и
читаемый код:

procedure TForm1.btnFindClick(Sender: TObject);
var ISheet: Excel8TLB._Worksheet;
UsedRange, Range: OLEVariant;
FirstAddress: string;
begin
if Assigned(IWorkbook) then
try
ISheet := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
UsedRange := ISheet.UsedRange[0];
Range := UsedRange.Find(What:='Text', LookIn := xlValues, SearchDirection := xlNext);
if not VarIsEmpty(Range) then begin
FirstAddress := Range.Address;
repeat
Range.Interior.ColorIndex := 37;
Range := UsedRange.FindNext(After := Range);
until FirstAddress = Range.Address;
ShowExcel;
end;
except
raise Exception.Create('Не могу чего-то сделать!');
end;
end;

Перемещение данных между листами.

Несколько раз меня спросили о том, как перемещать данные между
листами. Я бы сделал это вот так:

procedure TForm1.btnMoveDataClick(Sender: TObject);
var ISheetSrc, ISheetDst: Excel8TLB._Worksheet;
IRangeSrc, IRangeDst: Excel8TLB.Range;
begin
if Assigned(IWorkbook) then
try
ISheetSrc := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
ISheetDst := 
IWorkbook.Worksheets.Add(EmptyParam, ISheetSrc, 1, EmptyParam, 0) as _Worksheet;
IRangeSrc := ISheetSrc.Range['TestRange2', EmptyParam];
IRangeDst := ISheetDst.Range['D4', EmptyParam];
IRangeSrc.Copy(IRangeDst);
finally
IRangeDst := nil;
IRangeSrc := nil;
ISheetDst := nil;
ISheetSrc := nil;
end;
end;

Метод Copy интерфейса Range принимает в качестве параметра
любой другой Range. Причем, совсем не важно, совпадают ли размеры источника и
получателя, так как данные копируются начиная с левой верхней ячейки получателя
в количестве, определенном размером источника. (О, завернул!) Для затравки
хотелось бы показать код, который выполняет ту же задачу, но через буфер обмена
(а вдруг в Word вставлять будем):

procedure TForm1.btnMoveDataClick(Sender: TObject);
var ISheetSrc, ISheetDst: Excel8TLB._Worksheet;
IRangeSrc, IRangeDst: Excel8TLB.Range;
begin
if Assigned(IWorkbook) then
try
ISheetSrc := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
ISheetDst := 
IWorkbook.Worksheets.Add(EmptyParam, ISheetSrc, 1, EmptyParam, 0) as _Worksheet;
IRangeSrc := ISheetSrc.Range['TestRange2', EmptyParam];
IRangeDst := ISheetDst.Range[ 'D4', EmptyParam];
IRangeSrc.Copy(EmptyParam); // так кладем в Clipboard
ISheetDst.Paste(IRangeDst, EmptyParam, 0); // а вот так достаем оттуда
finally
IRangeDst := nil;
IRangeSrc := nil;
ISheetDst := nil;
ISheetSrc := nil;
end;
end;

Проект — SampleExcel (в архиве с данной
статьёй).

Часть 5. Передача данных разного типа.

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


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

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

Часть 6. Передача данных используя буфер обмена и DDE.
Clipboard.

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

Демо-Проект — SampleExcel3 (в архиве с данной
статьёй).

Часть 7: Пример обмена данными с Excel используя VCL и OLE.

Автор: Bird Feniks.

VCL.

В Delphi 5 и выше, для обмена данными между Вашим приложением
и Excel можно использовать компонент TExcelApplication, доступный на Servers
Page в Component Palette.

Создаем новый проект. На форму кладем TStringGrid, заполняем
его некоторыми данными и добавляем две кнопки, с названиями «To Excel» и «From
Excel». Так же на форму кладем компонент TExcelApplication и в свойстве «Name»
указываем «XLApp», а свойство «ConnectKind» меняем на «ckNewInstance».

Когда нам необходимо работать с Excel, то обычно мы открываем
ExcelApplication, затем открываем WorkBook и в конце используем WorkSheet.

Итак, несомненный интерес представляет для нас листы
(WorkSheets) в книге (WorkBook). Давайте посмотрим как всё это работает.

Посылка данных в Excel.

Это можно сделать с помощью следующей процедуры :

procedure TForm1.BitBtnToExcelOnClick(Sender: TObject); 
var
WorkBk : _WorkBook; // определяем WorkBook
WorkSheet : _WorkSheet; // определяем WorkSheet
I, J, K, R, C : Integer;
IIndex : OleVariant;
TabGrid : Variant;
begin
if GenericStringGrid.Cells[0,1] <> '' then
begin
IIndex := 1;
R := GenericStringGrid.RowCount;
C := GenericStringGrid.ColCount;
// Создаём массив-матрицу
TabGrid := VarArrayCreate([0,(R - 1),0,(C - 1)],VarOleStr);
I := 0;
// Определяем цикл для заполнения массива-матрицы 
repeat
for J := 0 to (C - 1) do
TabGrid[I,J] := GenericStringGrid.Cells[J,I];
Inc(I,1);
until 
I > (R - 1); 
// Соединяемся с сервером TExcelApplication
XLApp.Connect; 
// Добавляем WorkBooks в ExcelApplication 
XLApp.WorkBooks.Add(xlWBatWorkSheet,0); 
// Выбираем первую WorkBook 
WorkBk := XLApp.WorkBooks.Item[IIndex]; 
// Определяем первый WorkSheet 
WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet; 
// Сопоставляем Delphi массив-матрицу с матрицей в WorkSheet 
Worksheet.Range['A1',Worksheet.Cells.Item[R,C]].Value := TabGrid; 
// Заполняем свойства WorkSheet
WorkSheet.Name := 'Customers'; 
Worksheet.Columns.Font.Bold := True; 
Worksheet.Columns.HorizontalAlignment := xlRight; 
WorkSheet.Columns.ColumnWidth := 14;
// Заполняем всю первую колонку 
WorkSheet.Range['A' + IntToStr(1),'A' + IntToStr(R)].Font.Color := clBlue;
WorkSheet.Range['A' + IntToStr(1),'A' + IntToStr(R)].HorizontalAlignment := xlHAlignLeft;
WorkSheet.Range['A' + IntToStr(1),'A' + IntToStr(R)].ColumnWidth := 31; 
// Показываем Excel
XLApp.Visible[0] := True;
// Разрываем связь с сервером
XLApp.Disconnect;
// Unassign the Delphi Variant Matrix 
TabGrid := Unassigned;
end; 
end;

Получение данных из Excel.

Это можно сделать с помощью следующей процедуры :

procedure TForm1.BitBtnFromExcelOnClick(Sender: TObject);
var 
WorkBk : _WorkBook; 
WorkSheet : _WorkSheet; 
K, R, X, Y : Integer; 
IIndex : OleVariant; 
RangeMatrix : Variant; 
NomFich : WideString; 
begin
NomFich := 'C:MyDirectoryNameOfFile.xls'; 
IIndex := 1;
XLApp.Connect; 
// Открываем файл Excel
XLApp.WorkBooks.Open(NomFich,EmptyParam,EmptyParam,EmptyParam,EmptyParam, 
EmptyParam,EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam, 
EmptyParam,EmptyParam,0); 
WorkBk := XLApp.WorkBooks.Item[IIndex];
WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet; 
// Чтобы знать размер листа (WorkSheet), т.е. количество строк и количество 
// столбцов, мы активируем его последнюю непустую ячейку
WorkSheet.Cells.SpecialCells(xlCellTypeLastCell,EmptyParam).Activate; 
// Получаем значение последней строки
X := XLApp.ActiveCell.Row; 
// Получаем значение последней колонки
Y := XLApp.ActiveCell.Column; 
// Определяем количество колонок в TStringGrid
GenericStringGrid.ColCount := Y; 
// Сопоставляем матрицу WorkSheet с нашей Delphi матрицей
RangeMatrix := XLApp.Range['A1',XLApp.Cells.Item[X,Y]].Value; 
// Выходим из Excel и отсоединяемся от сервера 
XLApp.Quit; 
XLApp.Disconnect; 
// Определяем цикл для заполнения TStringGrid 
K := 1; 
repeat 
for R := 1 to Y do 
GenericStringGrid.Cells[(R - 1),(K - 1)] := RangeMatrix[K,R]; 
Inc(K,1); 
GenericStringGrid.RowCount := K + 1; 
until 
K > X;
// Unassign the Delphi Variant Matrix 
RangeMatrix := Unassigned; 
end;
OLE.

Ниже представлен пример создания новой таблицы в Excel 2000:

uses
ComObj, ActiveX;
var
Row, Col: integer;
DestRange: OleVariant;
Excel: Variant;
begin
Excel := CreateOleObject('Excel.Application.9');
Excel.Visible := True;
Excel.WorkBooks.Add; //Создать новую таблицу
//Можно помещать текст и значения в диапазон ячеек
//Поместить слово тест в диапазон ячеек
Excel.ActiveSheet.Range['A2', 'B3'].Value := 'Тест';
//Или число
Excel.ActiveSheet.Range['A4', 'B5'].Value := 42;
//А вот так задаётся формула
Excel.ActiveSheet.Range['A10', 'A11'].Formula := '=RAND()';
//Можно задавать номера ячеек и столбцов
Excel.ActiveSheet.Cells.Item[1, 1].Value := 'Первая ячейка';
Row:=1;
Col:=3;
Excel.ActiveSheet.Cells.Item[Row, Col].Value := 'Другая ячейка';
//Можно скопировать данный из одного диапазона ячеек в другой
DestRange := Excel.Range['D6', 'F10'];
Excel.Range['A1', 'C5'].Copy(DestRange);
//Можно задавать параметры шрифта в определённой ячейке
Excel.Range['A2', 'A2'].Font.Size := 20;
Excel.Range['A2', 'A2'].Font.FontStyle := 'Bold';
Excel.Range['A2', 'A2'].Font.Color := clFuchsia;
Excel.Range['A2', 'A2'].Font.Name := 'Arial';
//Можно ещё и так изменить цвет диапазона ячеек
Excel.Range['B2', 'C6'].Interior.Color := RGB(223, 123, 123);
end;

Далее представлен пример открытия и закрытия таблицы:

uses
ComObj, ActiveX;
var
Excel: Variant;
WBk : OleVariant;
SaveChanges: OleVariant;
begin
Excel := CreateOleObject('Excel.Application.9');
Excel.Visible := True;
//Открыть существующую таблицу
WBk := Excel.WorkBooks.Open('C:Test.xls');
...
WBk.Close(SaveChanges := True);
Excel.Quit;
end; 

Создание документа

Внимание!!! Всегда когда создаете объект интерфейса, заключайте  процедуру создания в модуль обработки ошибок:
try
создаем интерфейс;
формируем отчет;
освобождаем интерфейс;
Except
обрабатываем ошибки;
освобождаем интерфейс;
end;

Далее идет краткий справочник по основным функциям работы с EXCEL

try
// создаем обьект EXCEL
excel := CreateOleObject(‘Excel.Application’);

// Чтоб не задавал вопрос о сохранении документа
excel.DisplayAlerts := false;

// создаем новый документ рабочую книгу
excel.WorkBooks.Add;

// или загружаем его из директории с программой
excel.WorkBooks.Open(GetCurrentDir() + ‘отчет.xls’);

{ GetCurrentDir()- возвращает путь к директории с программой}

// Делаем его видимым данную функцию после отладки и тестирования лучше использовать в конце, после сформирования отчета (это ускоряет процесс вывода данных в отчет)
excel.Visible := true;

//задаем тип формул в формате R1C1
excel.Application.ReferenceStyle := xlR1C1;

// задаем тип формул в формате A1
excel.Application.ReferenceStyle := xlA1;

// Задаем название первому и второму листу
excel.WorkBooks[1].WorkSheets[1].Name := ‘Отчет1’;
excel.WorkBooks[1].WorkSheets[2].Name := ‘Отчет2’;

//задаем формат числа для первой и четвертой колонки формат числа
excel.WorkBooks[1].WorkSheets[1].Columns[1].NumberFormat := ‘0,00’;
excel.WorkBooks[1].WorkSheets[1].Columns[4].NumberFormat := ‘0,0’;

// задаем ширину первой и второй колонки
excel.WorkBooks[1].WorkSheets[1].Columns[1].ColumnWidth := 10;
excel.WorkBooks[1].WorkSheets[1].Columns[2].ColumnWidth := 20;

// задаем начертание, цвет, размер и тип шрифта для первого ряда
excel.WorkBooks[1].WorkSheets[1].Rows[1].Font.Bold := True; //жирный
excel.WorkBooks[1].WorkSheets[1].Rows[1].Font.Color := clRed;  // цвет красный
excel.WorkBooks[1].WorkSheets[1].Rows[1].Font.Size := 12;  //размер 12
excel.WorkBooks[1].WorkSheets[1].Rows[1].Font.Name := ‘Times New Roman’;  //шрифт

//присваиваем ячейке 1,4 и 2,4 значения (1 — ряд, 4 — колонка)
excel.WorkBooks[1].WorkSheets[1].Cells[1, 4] := ‘А так можно внести значение в ячейку’;
excel.WorkBooks[1].WorkSheets[1].Cells[2, 4] := ‘А так можно внести значение в ячейку’;

//ввод в ячейку ‘A12’ формулы ‘=b5+c4’
excel.WorkBooks[1].WorkSheets[1].Range[‘A12′].Formula:=’=b5+c4’;

// Выравнивам первый ряд по центру по вертикали
excel.WorkBooks[1].WorkSheets[1].Rows[1].VerticalAlignment := xlCenter;

// Выравнивам первый ряд по центру по горизонтали
excel.WorkBooks[1].WorkSheets[1].Rows[1].HorizontalAlignment := xlCenter;

// Выравнивам в ячейке по левому краю
excel.WorkBooks[1].WorkSheets[1].Cells[3, 2].HorizontalAlignment := xlLeft;

// Выравнивам в ячейке по правому краю
excel.WorkBooks[1].WorkSheets[1].Cells[3, 4].HorizontalAlignment := xlRight;

// Обьединяем ячейки ‘A1:A8’
excel.WorkBooks[1].WorkSheets[1].Range[‘A1:A8’].Merge;

// Поворачиваем слова под углом 90 градусов для второго ряда
excel.WorkBooks[1].WorkSheets[1].Rows[2].Orientation := 90;

// Поворачиваем слова под углом 45 градусов для диапазона ячеек ‘B3:D3’
excel.WorkBooks[1].WorkSheets[1].Range[‘B3:D3’].Orientation := 45;

//рисуем границы  выделенного диапазона левая
excel.Selection.Borders[xlEdgeLeft].LineStyle := xlContinuous; // стиль линии сплошная
excel.Selection.Borders[xlEdgeLeft].Weight := xlMedium;// толщина линии

//рисуем границы  выделенного диапазона верхняя
excel.Selection.Borders[xlEdgeTop].LineStyle := xlContinuous;
excel.Selection.Borders[xlEdgeTop].Weight := xlMedium;

//рисуем границы  выделенного диапазона нижняя
excel.Selection.Borders[xlEdgeBottom].LineStyle := xlContinuous;
excel.Selection.Borders[xlEdgeBottom].Weight := xlMedium;

//рисуем границы  выделенного диапазона правая
excel.Selection.Borders[xlEdgeRight].LineStyle := xlContinuous;
excel.Selection.Borders[xlEdgeRight].Weight := xlMedium;

//рисуем границы  выделенного диапазона вертикальные внутрениие
excel.Selection.Borders[xlInsideVertical].LineStyle := xlContinuous;
excel.Selection.Borders[xlInsideVertical].Weight := xlMedium;

//рисуем границы  выделенного диапазона горизонтальные внутрениие
excel.Selection.Borders[xlInsideHorizontal].LineStyle := xlContinuous;
excel.Selection.Borders[xlInsideHorizontal].Weight := xlMedium;

//автозаполнение выделенного диапазона
//для примера заполним область ячеек ‘A10:C10’ словом ‘привет’
//и размножим его вниз еще на пять ячеек ‘A10:C15’

excel.WorkBooks[1].WorkSheets[1].Range[‘A10:C10′].Value:=’привет’;

//выделяем диапазон ячеек ‘A10:C10’
excel.WorkBooks[1].WorkSheets[1].Range[‘A10:C10’].Select;

//автозаполняем (копируем) выделенным диапазоном область ячеек ‘A10:C15’
excel.selection.autofill(excel.WorkBooks[1].WorkSheets[1].Range[‘A10:C15’],xlFillDefault);

//отключаем предупреждения, чтобы не задавал вопросов о сохранении и других
excel.DisplayAlerts := False;

 //сохраняем документ в формате Excel 97-2003
 excel.ActiveWorkBook.Saveas(GetCurrentDir() + ‘отчет.xls’,xlExcel8);

 //сохраняем документ в текущем формате Excel 2007
 excel.ActiveWorkBook.Saveas(GetCurrentDir() + ‘отчет.xlsx’);

//закроем все книги
excel.Workbooks.Close;

//закрываем Excel
excel.Application.quit;

//освобождаем интерфейсы
excel := Unassigned;

Except

//обрабатываем ошибки
showmessage(‘Внимание! Произошла ошибка при создании MS Excel приложения’);

//закроем все книги
excel.Workbooks.Close;

//закрываем Excel
excel.Application.quit;

//освобождаем интерфейсы
excel := Unassigned;
end;
end;

При работе с листом Excel мы можем использовать следующие варианты:
— работать с областью Range[‘B3:D3’];
— работать с ячейкой Cells[2, 4] где 2 — ряд, 4 — колонка;
— работать с рядами Rows[1] или с диапазоном рядов Rows[‘1:5’];
— работать с колонками Columns[1] или диапазоном колонок Columns[‘A:I’];

Range[‘A1’] и Cells[1, 1] обозначают одно и тоже.

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

Например так:
//закроем все книги
excel.Workbooks.Close;

//закрываем Excel
excel.Application.quit;

//освобождаем интерфейсы
sheet:=Unassigned; //интерфейс листа если он был создан
WorkBook := Unassigned;//интерфейс рабочей книги если он был создан
excel := Unassigned;//интерфейс самого предложения если он был создан

end;

Ниже привожу пример вывода в Excel из Delphi таблицы умножения с подробными комментариями.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, comobj, StdCtrls, Excel_TLB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  excel: variant; // Переменная в которой создаётся объект EXCEL
  MyData: variant; // Переменная в которой формируется таблица умножения
  i,j:integer;
implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
try
// Обьект EXCEL
excel := CreateOleObject(‘Excel.Application’);
// Чтоб не задавал вопрос о сохранении документа
excel.DisplayAlerts := false;
// новый документ
excel.WorkBooks.Add;

//объявляем вариантный массив
MyData := VarArrayCreate([1,9,1,9],varVariant);
for I := 1 to 9 do
for J := 1 to 9 do
  MyData[i,j]:=i*j;

// Обьединяем ячейки ‘A1:I1’
excel.WorkBooks[1].WorkSheets[1].Range[‘A1:I1’].Merge;
//Пишем заголовок
excel.WorkBooks[1].WorkSheets[1].Range[‘A1′].Value:=’Таблица умножения’;
// Выравнивам заголовок по центру
excel.WorkBooks[1].WorkSheets[1].Range[‘A1’].HorizontalAlignment := xlCenter;
// задаем ширину  колонок с A по I
excel.WorkBooks[1].WorkSheets[1].Columns[‘A:I’].ColumnWidth := 3;

//выделяем область таблицы умножения [‘A2:I10’] и рисуем границы
excel.WorkBooks[1].WorkSheets[1].Range[‘A2:I10’].select;
//рисуем границы  выделенного диапазона левая
excel.Selection.Borders[xlEdgeLeft].LineStyle := xlContinuous; // стиль линии сплошная
excel.Selection.Borders[xlEdgeLeft].Weight := xlThin;// толщина линии
//рисуем границы  выделенного диапазона верхняя
excel.Selection.Borders[xlEdgeTop].LineStyle := xlContinuous;
excel.Selection.Borders[xlEdgeTop].Weight := xlThin;
//рисуем границы  выделенного диапазона нижняя
excel.Selection.Borders[xlEdgeBottom].LineStyle := xlContinuous;
excel.Selection.Borders[xlEdgeBottom].Weight := xlThin;
//рисуем границы  выделенного диапазона правая
excel.Selection.Borders[xlEdgeRight].LineStyle := xlContinuous;
excel.Selection.Borders[xlEdgeRight].Weight := xlThin;
//рисуем границы  выделенного диапазона вертикальные внутрениие
excel.Selection.Borders[xlInsideVertical].LineStyle := xlContinuous;
excel.Selection.Borders[xlInsideVertical].Weight := xlThin;
//рисуем границы  выделенного диапазона горизонтальные внутрениие
excel.Selection.Borders[xlInsideHorizontal].LineStyle := xlContinuous;
excel.Selection.Borders[xlInsideHorizontal].Weight := xlThin;

//присваиваем диапазону [‘A2:I10’] значения вариантного массива  MyData это значительно ускоряет работу, нежели вывод по //ячеечно

excel.WorkBooks[1].WorkSheets[1].Range[‘A2:I10’].Value:=MyData;

// Делаем его видимым
excel.Visible := true;
//освобождаем интерфейсы
MyData:= Unassigned;
excel := Unassigned;
Except
showmessage(‘Внимание! Произошла ошибка при создании MS Excel приложения’);
//закрываем Excel
excel.Application.quit;
//освобождаем интерфейсы
MyData:= Unassigned;
excel := Unassigned;
end;
end;

end.

При написании статьи использовался материал с сайта http://vlad2000.h1.ru/Frames/Statyi/page_14.html

Понравилась статья? Поделить с друзьями:
  • Функция excel а обр
  • Функция excel goal seek
  • Функция exact в excel на русском
  • Функция enter в excel
  • Функция end в excel