Несколько раз меня спросили о том, как перемещать данные между листами. Я бы сделал это вот так:
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 (в архиве с данной статьёй).
Январь
29,
2008
— Filed under: Delphi
Метки: SampleExcel, Перемещение данных
Предлагаю поискать все ячейки, содержащие строку (или подстроку) «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;
Январь
21,
2008
— Filed under: Delphi
Метки: Excel Type Library, Find, Поиск данных
Имея ввиду все вышеописанное, можно просто организовать чтение данных из поименованной области. Я часто нахожу такой код в Сети и в книгах, приведенный в качестве примера. Вот он:
procedure TForm1.btnReadArrayClick(Sender: TObject);
var Values: OLEVariant;
ISheet: Excel8TLB._Worksheet;
IRange: Excel8TLB.Range;
i, j: integer;
begin
if Assigned(IWorkbook) then
try
ISheet := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
try
IRange := ISheet.Range['TestRange2', EmptyParam];
Values := VarArrayCreate([1, IRange.Rows.Count, 1, IRange.Columns.Count], varVariant);
for i := 1 to IRange.Rows.Count do
for j := 1 to IRange.Columns.Count do begin
Values[i, j] := IRange.Item[i, j];
ShowMessage( Values[i, j]);
end;
finally
IRange := nil;
ISheet := nil;
end;
except
raise Exception.Create(‘Не могу прочитать данные в массив!’);
end;
end;
Я создал на форме кнопку, по которой из заранее подготовленной области с именем «TestRange2″ все значения ячеек будут получены в вариантный массив Values. Вызов ShowMessage добавлен сюда только для контроля над процессом. Как видно, получить значения ячеек области достаточно просто. Вы создаете вариантный массив с количеством строк и колонок, равными размерам области, а затем, проходя по очереди все ячейки области, запоминаете их значения в массиве. Но в этом коде есть одна проблема. Чтение из ячеек можно организовать еще проще. Вот так:
procedure TForm1.btnReadArrayClick(Sender: TObject);
var Values: OLEVariant;
ISheet: Excel8TLB._Worksheet;
IRange: Excel8TLB.Range;
begin
if Assigned(IWorkbook) then
try
ISheet := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
try
IRange := ISheet.Range['TestRange2', EmptyParam];
Values := IRange.Value; // <<—
for i := 1 to IRange.Rows.Count do
for j := 1 to IRange.Columns.Count do begin
ShowMessage( Values[i, j]);
end;
finally
IRange := nil;
ISheet := nil;
end;
except
raise Exception.Create(‘Не могу прочитать данные в массив!’);
end;
end;
Дело в том, что строки Values := IRange.Value вполне достаточно. Свойство Value интерфейса Range в состоянии вернуть вариантный массив. Этот код, по моему мнению, более прост и производителен, особенно на больших объемах данных. Уберите отсюда циклы с ShowMessage и убедитесь в этом.
А вот пример кода, который вернет в массиве значения всех ячеек из используемой области на листе. Проще сказать, вернет весь лист:
var Values: OLEVariant;
ISheet: Excel8TLB._Worksheet;
IRange: Excel8TLB.Range;
i, j: integer;
begin
if Assigned(IWorkbook) then
try
ISheet := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
try
IRange := ISheet.UsedRange[0];
Values := IRange.Value;
finally
IRange := nil;
ISheet := nil;
end;
except
raise Exception.Create(‘Не могу прочитать данные в массив!’);
end;
end;
Здесь я использую свойство UsedRange листа. Это прямоугольная область, заключенная между «левой верхней непустой» и «правой нижней непустой» ячейками. (Кто-нибудь понял? Впрочем, в два часа ночи разве напишешь понятней!). Конечно, если в этой прямоугольной области будет много пустых ячеек, то массив получится с избыточными данными. Что бы убедиться в этом, попробуйте создать циклы с ShowMessage из предыдущего примера.
В комментариях проекта-примера вы найдете еще несколько интересных конструкций, которые мне приходится использовать для получения массивов со значениями ячеек. В качестве параметра в UsedRange я передаю 0. Это lcid, описанный в предыдущей статье.
Кстати, об lcid. В прошлый раз меня подвела зрительная память. И в самом деле, «любимый классик» пишет, что туда можно смело передавать 0. Но другой, не менее любимый классик с этим не согласен и рекомендует передавать туда результат функции GetUserDefaultLCID. Думаю, последнее более правильно. Однако В некоторых случаях, чаще в гремучей смеси Windows 2000 и Excel 2000, оба решения не проходили. Причем, выдавалось сообщение о попытке «использовать библиотеку старого формата:» и что-то еще. Так вот, вместо GetUserDefaultLCID я применяю теперь константу LOCALE_USER_DEFAULT. Более ничего объяснить не могу, так как до сих пор, проштудировав основательно MSDN, не разобрался, что же в таком случае хочет получить Microsoft в методы и свойства интерфейсов Excel, где одним из параметров требует lcid. Кто бы объяснил?..
Есть еще несколько способов чтения данных из книги, которые, впрочем, я не в силах описать здесь. Один их таких способов, это использование DDE, самый быстрый и экономичный (по ресурсам) способ, который известен еще со времен Windows 3.1.
Январь
19,
2008
— Filed under: Delphi
Метки: MSDN, Windows 3.1, Чтение данных, ячеек
Написав этот заголовок, я подумал о том, как часто я «беру» данные из книги. Это случается весьма редко, ибо Excel я использую как средство построения отчетов. То есть, намного чаще эти данные я туда передаю. Поэтому хотелось бы описать не столько чтение данных, сколько способы обращения к ячейкам. Я использую разные способы обращения к ячейкам от привычного в Excel Cells(x,y) до коллекции Names. Вот некоторые примеры:
procedure TForm1.btnReadDataClick(Sender: TObject);
var Value: OLEVariant;
ISheet: Excel8TLB._Worksheet;
begin
if Assigned(IWorkbook) then
try
ISheet := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
try
case rgWhatRead.ItemIndex of
0: Value := ISheet.Cells.Item[2, 1].Value;
1: Value := ISheet.Range['A2', EmptyParam].Value;
2: Value := ISheet.Range['TestCell', EmptyParam].Value;
3: Value := IWorkbook.Names.Item(‘TestCell’, EmptyParam,
EmptyParam).RefersToRange.Value;
end;
ShowMessage(Value);
finally
ISheet := nil;
end;
except
raise Exception.Create(‘Не могу прочитать данные!’);
end;
end;
На главную форму проекта я добавил кнопку, по которой можно прочитать данные из ячейки «А2″ открытой книги, и RadioGroup к ней, чтобы выбрать способ получения этих данных. Из приведенного кода видна одна из «гнуснейших» моих привычек — освобождать все полученные интерфейсы явно (ISheet := nil). Я не могу побороть ее уже долгое время, поэтому прошу прощения у мастеров программирования на Delphi за то, что эта строчка здесь абсолютно лишняя.
Самый повторяющийся вопрос в моей почте, это вопрос о Cells. Почему-то многие уверены, что конструкция Cells[x, y].Value должна работать. В VBA это так. Но при раннем связывании это не соответствует истине. Свойство Cells объявлено у всех интерфейсов как
property Cells: Range read Get_Cells;
Отсюда видно, что это область (Range). И нет там никаких индексов, чтобы пробовать писать [x, y]. Один из корреспондентов мне написал, что он как-то обращался с этой проблемой к Наталье Елмановой, и она ему не помогла, написав, что «есть предположение, что кодогенератор Delphi их как-то не так «переваривает», генерируя XXX_TLB.pas, но полной уверенности нет». А дело в том, что «кодогенератор» правильно генерирует свойства _Defaul и Item (у многих интерфейсов в Excel Type Library есть такое свойство) у интерфейса Range. Вот только свойство _Default должно быть свойством по умолчанию. Поэтому стандартное объявление этих свойств
property _Default[RowIndex: OleVariant; ColumnIndex: OleVariant]: OleVariant dispid 0;
property Item[RowIndex: OleVariant; ColumnIndex: OleVariant]: OleVariant dispid 170;
можно исправить на такой вариант
property _Default[RowIndex: OleVariant; ColumnIndex: OleVariant]: OleVariant dispid 0; default;
property Item[RowIndex: OleVariant; ColumnIndex: OleVariant]: OleVariant dispid 170;
и смело писать Cells[x, y].Value.
Понятное дело, что это нехорошо — редактировать код, полученный автоматически из умного «кодогенератора» Delphi. Но «Там», ведь, тоже люди работают и ошибаются они не реже наших. Кстати, в импортированной Excel Type Library (независимо от версии Delphi — 4 или 5) некоторые свойства, имеющие dispid 0, почему-то все-таки объявлены как default. Почему?!
В приведенном выше примере кода я показал не только использование Cells. К ячейкам можно получить доступ и через свойство Range интерфейса Worksheet. Это свойство объявлено как
property Range[Cell1: OleVariant; Cell2: OleVariant]: Range read Get_Range;
В Cell1 / Cell2 можно передать ячейки (только в формате А1, RC вызовет исключение), описывающие границы области — левый верхний угол и правый нижний. Я же использовал только указание одной ячейки, мне необходимой. Где-то в Рунете я встретил предположение о том, что, если передать в оба параметра «A1″, то в выбранный Range попадет вся колонка. Сначала я подумал, — «А почему не вся строка?!» Но, решил, все-таки проверить это предположение — в область попала одна ячейка.
В Excel можно присваивать имена любым ячейкам и даже наборам ячеек. Это можно сделать, либо используя «комбо-бокс», который находится левее строки формул, либо пункт меню «Вставка\Имя\Присвоить». Ячейке «А2″ я присвоил имя «TestCell» и, используя все то же свойство Range листа, получил значение ячейки по этому имени.
И последний вариант, без которого я не смог бы обойтись при создании всех своих отчетов, это использование коллекции Names книги. Не смотря на некоторую неуклюжесть кода, этот способ я использую довольно часто. Почему? Потому, что очень часто использую именованные ячейки и области, разбросанные по разным листам и даже книгам. Но останавливаться на нем смысла не вижу, оставляя благодарному читателю возможность обратиться непосредственно к первоисточнику — справке по Excel VBA.
Январь
15,
2008
— Filed under: Delphi
Метки: Excel VBA, данные, ячейки
Повторюсь, не смотря на то, что я уже писал об этом в предыдущей статье. В главной форме проекта-примера я объявил свойство IWorkbook. Оно будет содержать интерфейс книги, которую мы будем создавать и использовать. Естественно, в обработчике FormDestroy я его освобождаю.
property IWorkbook: Excel8TLB._Workbook read FIWorkbook;
Книгу можно создать разными способами и с разными намерениями. Если необходимо создать абсолютно чистую книгу, достаточно выполнить следующий код:
if Assigned(IXLSApp) and (not Assigned(IWorkbook) ) then
FIWorkbook := IXLSApp.Workbooks.Add(EmptyParam, 0);
Вопрос в том, зачем нам может понадобиться новая книга, с количеством пустых листов, выставленным по умолчанию. Сегодня, я не могу уже ответить на этот вопрос, ибо не создаю новых пустых книг.
Коллекция Workbooks содержит все открытые книги и предоставляет возможность кое-как управлять всем этим.
Боже, как убоги коллекции от Microsoft, и особенно поиск в них! Я отклонюсь, но это надо видеть. Вот пример поиска книги с заданным именем, приведенный как совет в MSDN Office Developer.
Public Function SheetExists(strSearchFor As String) As Boolean
SheetExists = False
For Each sht In ThisWorkbook.Worksheets
If sht.Name = strSearchFor Then
SheetExists = True
End If
Next sht
End Function
Это вам не IndexOf писать. Сами ищите! А я так иделаю. Но, далее…
Метод Add этой коллекции (читай, метод интерфейса) позволяет добавить книгу к этой коллекции, пустую либо по шаблону. Первый параметр этого метода, Template (из справки по Excel VBA), может принимать имя файла с путем.
Поэтому, выполнив код
if Assigned(IXLSApp) and (not Assigned(IWorkbook) ) then
FIWorkbook := IXLSApp.Workbooks.Add(ExtractFilePath(ParamStr(0)) + ‘Test.xls’, 0);
вы получите книгу, идентичную файлу «Test.xls» с именем Test1.xls. Именно этим способом я создаю все свои отчеты, так как создаю их по заранее разработанным шаблонам. Естественно, что это шаблоны XL Report.
Если же необходимо просто открыть уже существующий файл, то используйте метод Open этой же коллекции:
if Assigned(IXLSApp) and (not Assigned(IWorkbook) ) then
FIWorkbook := IXLSApp.Workbooks.Open(ExtractFilePath(ParamStr(0)) + «Test.xls’, EmptyParam,
EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam, false, 0);
Понимаю, что в душе нормального программиста такой код вызовет отвращение. Как-то я даже получил гневное письмо о собственной ненормальности из-за того, что использую ранее связывание и кучу EmptyParam. Впрочем, я не сильно агрессивный человек (правда, только в переписке), и отвечать не стал. В конечном итоге, раннее связывание дает мне немного преимуществ, но я за него. Я не могу помнить все методы и их параметры из Excel Type Library, поэтому получаю их (только при раннем связывании, естественно) из подсказок редактора Delphi — продуманная вещь этот редактор. А чтобы не мучаться с написанием такого количества EmptyParam, можно написать и так:
if Assigned(IXLSApp) and (not Assigned(IWorkbook)) then
IDispatch(FIWorkbook) := OLEVariant(IXLSApp.Workbooks).Open(
FileName := ExtractFilePath(ParamStr(0)) + ‘Test.xls’);
Но, мы отклонились. Что же стоит за таким количеством параметров по умолчанию в методе Open? Да, много чего. Из этого «громадья» я использую лишь несколько вещей. Их я и опишу, а заинтересовавшихся остальными отсылаю к справке по Excel VBA. Вот объявление этого метода в импортированной библиотеке типов:
function Open(const Filename: WideString; UpdateLinks: OleVariant; ReadOnly: OleVariant;
Format: OleVariant; Password: OleVariant; WriteResPassword: OleVariant;
IgnoreReadOnlyRecommended: OleVariant; Origin: OleVariant;
Delimiter: OleVariant; Editable: OleVariant; Notify: OleVariant;
Converter: OleVariant; AddToMru: OleVariant; lcid: Integer): Workbook; safecall;
В FileName необходимо передать имя открываемого файла, желательно указав путь его нахождения. Иначе, этот файл Excel будет искать в каталоге по умолчанию. Чтобы файл был запомнен в списке последних открытых файлов, в AddToMru можно передать true. Иногда я знаю, что файл рекомендован только для чтения (не путать с «парольной» защитой книги). Тогда при открытии выдается соответствующее сообщение. Чтобы игнорировать его, можно передать в IgnoreReadOnlyRecommended true. Вот, пожалуй, и все мои скудные знания об этом методе. Впрочем, с помощью его мне приходилось открывать и файлы текстовых форматов с разделителями. Но тогда я обращался к чудесному «пишущему» плейеру VBA и записывал с его помощью макросы, затем правил их по необходимости и все отлично получалось. Этим же способом разрешать «всяческие» тонкие вопросы рекомендую и вам.
На главной форме проекта-примера я создал кнопку, с помощью которой можно открыть (или создать) файл и RadioGroup к ней, где можно указать каким из приведенных выше способов файл этот открывается. Для полного удовлетворения сюда же была добавлена обработка исключения. Вот что у меня получилось:
procedure TForm1.btnCreateBookClick(Sender: TObject);
var FullFileName: string;
begin
FullFileName := ExtractFilePath(ParamStr(0)) + ‘Test.xls’;
if Assigned(IXLSApp) and (not Assigned(IWorkbook)) then
try
case rgWhatCreate.ItemIndex of
// По шаблону
0: FIWorkbook := IXLSApp.Workbooks.Add(FullFileName, 0);
// Просто откроем
1: FIWorkbook := IXLSApp.Workbooks.Open(FullFileName,
EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, false, 0);
// Пустая книга
2: FIWorkbook := IXLSApp.Workbooks.Add(EmptyParam, 0);
end;
except
raise Exception.Create(‘Не могу создать книгу!’);
end;
end;
Далее во всех примерах я подразумеваю, что вы всякий раз будете создавать новую книгу по шаблону с помощь кнопки «Create workbook». Книга-шаблон названа как прежде Test.xls и включена в проект. Все остальные примеры опираются именно на эту книгу и ее листы. В этой книге я подготовил кое-какие данные и поименованные области для последующих примеров работы с Excel. Для каждого примера кода я буду добавлять кнопку и, возможно, RadioGroup к ней с возможностью выбора варианта работы. Не судите меня строго за то, что главная и единственная форма проекта-примера получится громоздкой и некрасивой. Не это здесь главное. Итак, всегда создавайте по кнопке книгу. Далее нажимайте кнопку, на которую указывает конкретный пример кода, и наблюдайте. Буду рад, если кто-то из читателей создаст более приемлемый демонстрационный проект для этой статьи.
Январь
10,
2008
— Filed under: Delphi
Метки: Книга-шаблон, открытие книги
Существует масса способов передать данные в Excel, начиная с DDE и заканчивая обычным присваиванием (типа Cell.Value := NewValue ). Конечно, максимальную скорость передачи данных можно получить, только используя DDE. Но я отказался от этого пути из-за некоторых ограничений и давно смущающего меня флажка в настройках Excel («Игнорировать DDE-запросы»). Поэтому здесь я опишу менее эффективный, но работоспособный, путь решения этой проблемы. Итак, после нажатия кнопки CreateExcel имеем открытый шаблон с листом «Лист1″ и областью с именем «TestRange». Для чистоты эксперимента (скорей из лени, великая вещь — собственная лень) я описал константный массив с тестовыми данными — TestDataArray. Именно эти данные я и передаю в ячейки области:
Delphi 4.0
procedure TForm1.btnDataToBookClick(Sender: TObject);
var LaunchDir: string;
IWorkbook: Excel8TLB._Workbook;
ISheet: Excel8TLB._Worksheet;
IRange: Excel8TLB.Range;
NewValueArray, V: OLEVariant;
i: integer;
begin
if Assigned(IXLSApp) then begin
LaunchDir := ExtractFilePath( ParamStr(0) );
IWorkbook := AddWorkbook(LaunchDir + ‘Test.xls’);
try
ISheet := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
IRange := ISheet.Range['TestRange', EmptyParam];
NewValueArray := VarArrayCreate([0, 20, 1, 4], varVariant);
for i := 0 to 20 do begin
NewValueArray[i, 1] := TestDataArray[i].V1;
NewValueArray[i, 2] := TestDataArray[i].V2;
NewValueArray[i, 3] := TestDataArray[i].V3;
NewValueArray[i, 4] := date + i;
end;
IRange.Value := NewValueArray;
finally
IRange := nil;
ISheet := nil;
IWorkbook := nil;
end;
end;
end;
Delphi 5.0
procedure TForm1.btnDataClick(Sender: TObject);
type
var LaunchDir: string;
IWorkbook: Excel97.ExcelWorkbook;
ISheet: Excel97.ExcelWorksheet;
IRange: Excel97.Range;
NewValueArray, V: OLEVariant;
i: integer;
begin
if Assigned(IXLSApp) then begin
LaunchDir := ExtractFilePath( ParamStr(0) );
IWorkbook := AddWorkbook(LaunchDir + ‘Test.xls’);
try
ISheet := IWorkbook.Worksheets.Item['Лист1'] as Excel97.ExcelWorksheet;
IRange := ISheet.Range['TestRange', EmptyParam];
NewValueArray := VarArrayCreate([0, 20, 1, 4], varVariant);
for i := 0 to 20 do begin
NewValueArray[i, 1] := TestDataArray[i].V1;
NewValueArray[i, 2] := TestDataArray[i].V2;
NewValueArray[i, 3] := TestDataArray[i].V3;
NewValueArray[i, 4] := date + i;
end;
IRange.Value := NewValueArray;
finally
IRange := nil;
ISheet := nil;
IWorkbook := nil;
end;
end;
end;
Я знаю, что многие профессионалы (к коим, к сожалению, я не принадлежу) взвоют от негодования при виде такого кода. Их можно понять, они знают намного более эффективные решения. Именно об этих решениях я и собираюсь написать в следующий раз. А вы пока не забудьте почитать комментарии в проекте-примере.
Январь
9,
2008
— Filed under: Delphi
Метки: шаблон, эффективные решения
Собственно говоря, при закрытии приложения Excel сам будет закрыт, если вы там не устели чего-нибудь отредактировать. И это правильно. Программисты Borland (Inprise до сих пор мне режет слух, да и некоторым в Inprise, судя по всему, тоже) позаботились об этом. Но я еще с Delphi 3 заимел дурную привычку освобождать все самостоятельно. Освобождать обычным присваиванием в nil (это касается проекта для D4). Труда это не составляет, да и проверка на Assigned удобна. Поэтому, и еще из кое-каких соображений, я делаю так:
Delphi 4.0
procedure TForm1.ReleaseExcel;
begin
if Assigned(FIXLSApp) then begin
if (FIXLSApp.Workbooks.Count > 0) and (not FIXLSApp.Visible[0]) then begin
FIXLSApp.WindowState[0] := TOLEEnum(xlMinimized);
FIXLSApp.Visible[0] := true;
Application.BringToFront;
end;
end;
FIXLSApp := nil;
end;
Ну вот, написал только про nil, а кода — на полстраницы. Опишу ситуацию.
Вы не запускали новый процесс, вы «законнектились» к уже существовавшему. В нем была открыта книга. Попробуйте: CreateExcel, ShowExcel, HideExcel (имеем право), ReleaseExcel. Если оставить только присваивание в nil, то существовавший процесс не будет выгружен (он же существовал до запуска нашего демо), но будет спрятан от пользователя с его открытой книгой.
Delphi 5.0
procedure TForm1.ReleaseExcel;
begin
if Assigned(IXLSApp) then begin
if (IXLSApp.Workbooks.Count > 0) and (not IXLSApp.Visible[0]) then begin
IXLSApp.WindowState[0] := TOLEEnum(xlMinimized);
IXLSApp.Visible[0] := true;
if not(csDestroying in ComponentState) then Self.SetFocus;
Application.BringToFront;
end;
end;
FreeAndNil(FIXLSApp);
end;
Практически тот же код. Только в D5 вы работаете уже не с интерфейсом напрямую, а с экземпляром класса TexcelApplcation. Если посмотреть его предков, то можно увидеть, что это настоящий класс, освободить который просто необходимо. Поэтому вместо присваивания в nil там написано FreeAndNil (помните такую процедуру?).
Часть 2: Лучшее решение — шаблоны.
Excel, интегрированный с моими приложениями, хорош (для меня — программиста) только по одной причине. Я всегда создаю шаблоны и использую их потом при построении отчетов. Шаблоны позволяют мне избежать ручного (в исходном тексте) форматирования. В общем случае, алгоритм выглядит просто: по шаблону создается книга, каким-то образом помеченные области заполняются данными и… (а дальше все уже готово). Как я создаю книгу по шаблону:
Delphi 4.0 / 5.0
function TForm1.AddWorkbook(const WorkbookName: string): Excel8TLB._Workbook;
begin
Result := nil;
if Assigned(FIXLSApp) and (trim(WorkbookName) <> ») then begin
Result := FIXLSApp.Workbooks.Add(WorkbookName, 0);
end;
end;
В этом коде нет ничего сложного. В принципе при работе с Excel я мало находил мест, где что-либо сделать было бы сложно. Чаще достаточно прочитать справку по VBA или записать макрос (благо, Microsoft встроила в Excel хороший пишущий player). После выполнения этого метода будет добавлена книга, близнец шаблона, с именем шаблона и порядковым номером (как «Книга1.xls» или «Книга228.xls»). Правда здесь есть одна тонкость. Эти «циферки» в имя книги Excel добавляет после поиска книг с таким же названием в каталоге по умолчанию. Я несколько раз наступал на грабли (больно!), когда пытался сохранять книги в другом каталоге и создавать новую — по этому же шаблону. К сожалению, не может эта «злобная» программа держать открытыми несколько книг с одинаковыми названиями, несмотря на то, что они лежат в разных каталогах.
Как я помечаю области, в которые необходимо разместить данные? В Excel существует возможность объединить ячейки в группу и поименовать эту группу. В терминах Microsoft это объект Range (область). Для своего проекта я создал тестовую книгу «Test.xls», в которой на листе «Лист1″ разместил область «TestRange» (см. рисунок). Более того, для ячеек этой области я указал форматы вывода (Field4 — дата, Field3 — красный цвет шрифта). Я надеюсь, что после переноса тестовых данных форматы сохранятся.
Январь
7,
2008
— Filed under: Delphi
Метки: Excel, xls