Главная страница

00.2 Уч-мет пос МПиС d4 (1). Лабораторная работа Формирование статистической таблицы с возможностью сортировки, фильтрации и группировки данных 4


Скачать 2.1 Mb.
НазваниеЛабораторная работа Формирование статистической таблицы с возможностью сортировки, фильтрации и группировки данных 4
Дата09.10.2022
Размер2.1 Mb.
Формат файлаdocx
Имя файла00.2 Уч-мет пос МПиС d4 (1).docx
ТипЛабораторная работа
#722618
страница8 из 9
1   2   3   4   5   6   7   8   9

Лабораторная работа № 7.
Работа с Microsoft
Excel из Delphi


Создание экземпляра Excel.Application

Модуль импортированной Excel TLB содержит описания всех интерфейсов. Подключаем Excel для последующего использования:
procedure TForm1.CreateExcel(NewInstance: boolean);

begin

ifnotAssigned(IXLSApp) thenbegin

FIXLSApp := TExcelApplication.Create(Self);

if NewInstance then FIXLSApp.ConnectKind := ckNewInstance;

FIXLSApp.Connect;

end;

end;
Delphi предоставляет более удобный сервис при импорте библиотек типов. Большой шаг вперед - появление класса ToleServer с поддержкой событий. Работа с существующими и создание новых OLE-серверов намного удобней. Отличная библиотека была создана Бином Ли (Binh Ly) в COM Nodes - Threading COM Library.

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

procedure TForm1.ShowExcel;

begin

if Assigned(FIXLSApp) thenbegin // а если он не создан?

FIXLSApp.Visible[0] := true;

if FIXLSApp.WindowState[0] = TOLEEnum(xlMinimized) then

FIXLSApp.WindowState[0] := TOLEEnum(xlNormal);

FIXLSApp.ScreenUpdating[0] := true;

end;

end;
Свойства Visible, WindowState и ScreenUpdating вызываются с индексом массива - 0. В модуле Excel TLB во многих свойствах и методах можете встретить параметр или индекс lcid.
Затем прячем Excel.

procedure TForm1.HideExcel;

begin

if Assigned(FIXLSApp) thenbegin

FIXLSApp.Visible[0] := false;

end;

end;

При закрытии приложения Excel будет закрыт. Освобождать все ресурсы надо самостоятельно.
procedure TForm1.ReleaseExcel;

begin

if Assigned(FIXLSApp) thenbegin

if (FIXLSApp.Workbooks.Count > 0) and (not FIXLSApp.Visible[0]) thenbegin

FIXLSApp.WindowState[0] := TOLEEnum(xlMinimized);

FIXLSApp.Visible[0] := true;

Application.BringToFront;

end;

end;

FIXLSApp := nil;

end;
Попробуйте: CreateExcel, ShowExcel, HideExcel, ReleaseExcel. Если оставить только присваивание в nil, то существовавший процесс не будет выгружен, но будет спрятан от пользователя с его открытой книгой.
procedure TForm1.ReleaseExcel;

begin

if Assigned(IXLSApp) thenbegin

if (IXLSApp.Workbooks.Count > 0) and (not IXLSApp.Visible[0]) thenbegin

IXLSApp.WindowState[0] := TOLEEnum(xlMinimized);

IXLSApp.Visible[0] := true;

ifnot(csDestroying in ComponentState) then Self.SetFocus;

Application.BringToFront;

end;

end;

FreeAndNil(FIXLSApp);

end;
Работаете уже не с интерфейсом напрямую, а с экземпляром класса TexcelApplcation. Если посмотреть его предков, то можно увидеть, что это настоящий класс, освободить который необходимо. Поэтому вместо присваивания в nil там написано FreeAndNil.
Шаблоны позволяют избежать ручного (в исходном тексте) форматирования. В общем случае, алгоритм выглядит просто: по шаблону создается книга, каким-то образом помеченные области заполняются данными и… (а дальше все уже готово).

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, начиная с DDE и заканчивая обычным присваиванием (типа Cell.Value := NewValue). Максимальную скорость передачи данных можно получить, только используя DDE. Итак, после нажатия кнопки CreateExcel имеем открытый шаблон с листом "Лист1" и областью с именем "TestRange". Опишем константный массив с тестовыми данными - TestDataArray. Именно эти данные я и передаю в ячейки области:
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) thenbegin

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 dobegin

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;
Создание или открытие книги

В главной форме проекта-примера объявляем свойство IWorkbook. Будет содержать интерфейс книги, которую будем создавать и использовать. В обработчике FormDestroy его освобождаем.
property IWorkbook: Excel8TLB._Workbook read FIWorkbook;
Книгу можно создать разными способами. Если необходимо создать абсолютно чистую книгу, достаточно выполнить следующий код:
if Assigned(IXLSApp) and (not Assigned(IWorkbook)) then

FIWorkbook := IXLSApp.Workbooks.Add(EmptyParam, 0);
Вопрос в том, зачем может понадобиться новая книга, с количеством пустых листов, выставленным по умолчанию.

Коллекция Workbooks содержит все открытые книги и предоставляет возможность кое-как управлять контекстом.

Пример поиска книги с заданным именем:
Public Function SheetExists(strSearchFor As String) AsBoolean

SheetExists = False

ForEach sht In ThisWorkbook.Worksheets

If sht.Name = strSearchFor Then

SheetExists = True

EndIf

Next sht

EndFunction
Метод 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. Именно этим способом создаются отчеты.

Если же необходимо просто открыть уже существующий файл, то используйте метод 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, можно написать и так:
if Assigned(IXLSApp) and (not Assigned(IWorkbook) ) then

IDispatch(FIWorkbook) := OLEVariant(IXLSApp.Workbooks).Open(

FileName := ExtractFilePath(ParamStr(0)) + 'Test.xls');
Что же стоит за таким количеством параметров по умолчанию в методе Open? Из этого «громадья» используем лишь несколько вещей. Объявление этого метода в импортированной библиотеке типов:
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.

На главной форме проекта-примера создаем кнопку, с помощью которой можно открыть (или создать) файл и 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;
Работа с листами книги

ActiveSheet и ActiveWorkbook, а также возможность работы с Cells и Range без указания, к какому листу или книге они принадлежат. Прежде, чем работать с отдельными ячейками, всегда получаем интерфейс на конкретный и вполне осязаемый лист книги:
var ISheet: Excel8TLB._Worksheet;



ISheet := IWorkbook.Worksheets.Item['Лист1'] as Excel8TLB._Worksheet;
Коллекция Worksheet подобна всем остальным коллекциям из Excel TLB. Можете удалять листы, вставлять новые, изменять их порядок.

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

Используются разные способы обращения к ячейкам от привычного в 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). Самый повторяющийся вопрос, это вопрос о Cells. Почему-то многие уверены, что конструкция Cells[x,y].Value должна работать. В VBA это так. Но при раннем связывании это не соответствует истине. Свойство Cells объявлено у всех интерфейсов как:
property Cells: Range read Get_Cells;
Отсюда видно, что это область (Range). И нет там никаких индексов, чтобы пробовать писать [x,y]. Стандартное объявление этих свойств:
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.

В приведенном выше примере кода показано не только использование Cells. К ячейкам можно получить доступ и через свойство Range интерфейса Worksheet. Это свойство объявлено как:
property Range[Cell1: OleVariant; Cell2: OleVariant]: Range read Get_Range;
В Cell1 / Cell2 можно передать ячейки (только в формате А1, RC вызовет исключение), описывающие границы области - левый верхний угол и правый нижний.

В Excel можно присваивать имена любым ячейкам и даже наборам ячеек. Это можно сделать, либо используя «комбо-бокс», который находится левее строки формул, либо пункт меню «Вставка\Имя\Присвоить». Ячейке «А2» присвоим имя «TestCell» и, используя все то же свойство Range листа, получим значение ячейки по этому имени.

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

Имея ввиду все вышеописанное, можно просто организовать чтение данных из поименованной области:
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 dobegin

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 dobegin

ShowMessage( Values[i, j]);

end;

finally

IRange := nil;

ISheet := nil;

end;

except

raise Exception.Create('Не могу прочитать данные в массив!');

end;

end;
Дело в том, что строки Values := IRange.Value вполне достаточно. Свойство Value интерфейса Range в состоянии вернуть вариантный массив. Этот код более прост и производителен, особенно на больших объемах данных.

А вот пример кода, который вернет в массиве значения всех ячеек из используемой области на листе, вернет весь лист:
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.

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

Предлагаю поискать все ячейки, содержащие строку (или подстроку) "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, работающие с ошибками. В таких случаях используем приведенный ниже прием:
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) thenbegin

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. Причем, совсем не важно, совпадают ли размеры источника и получателя, так как данные копируются начиная с левой верхней ячейки получателя в количестве, определенном размером источника. Код, который выполняет ту же задачу, но через буфер обмена:

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;

Типичные ошибки
Речь идет о тривиальном переносе данных с помощью обычного присваивания свойству ячейки 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).
Львиная доля времени уходит на вызовы интерфейсов внешнего COM-сервера. И, не смотря на то, что мы используем ранее связывание с библиотекой типов. Задача - избавиться от вызова 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;

whilenot tblCust.EOF dobegin

Column := 1;

for i := 0 to tblCust.Fields.Count - 1 dobegin

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" переносим данные из массива в ячейки этой области.
XL Report очень долго использовал только буфер обмена для передачи данных из приложения в Excel. Дело в том, что при таком варианте достигается практически максимальная скорость переноса данных. Дело в том, что в буфер обмена "кладется" длинная строка, содержащая строковые значения полей набора данных (AsString), разделенные символом табуляции. Записи отделяются друг от друга переводом строки (#10). Собственно, этот формат известен в научных кругах как CSV (разделитель между значениями).

Для того чтобы "выжать" из Excel максимальное быстродействие, приходится использовать определенные методы и свойства его интерфейсов. А их использование не оставляет ничего, кроме как уничтожение содержимого буфера обмена. Решение по переносу данных - CSV:
procedure TForm1.ToCSV(ISheet: IxlWorksheet);

var i: integer;

IR1, IR2: IxlRange;

Buff: String;

begin

Buff := '';

tblCust.First;

whilenot tblCust.EOF dobegin

for i := 0 to tblCust.Fields.Count - 1 dobegin

Buff := Buff + FieldToStr(tblCust.Fields[i]);

if i < (tblCust.Fields.Count - 1) then

Buff := Buff + #9;

end;

tblCust.Next;

ifnot 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". Без вмешательства в содержимое поля (аналогично добавлению одиночной кавычки при вариантах) нельзя обойти никак.
Один из переключателей вариантов имеет название, состоящее из сочетания букв - DDE. Тем не менее, решим "главную" проблему - сохранение содержимого буфера обмена:
procedure TForm1.ToDDE(ISheet: IxlWorksheet);

var xlDDE: TDDEClientConv;

i: integer;

IR1, IR2, IRange: IxlRange;

Buff: string;

begin

Buff := '';

tblCust.First;

whilenot tblCust.EOF dobegin

for i := 0 to tblCust.Fields.Count - 1 dobegin

Buff := Buff + FieldToStr(tblCust.Fields[i]);

if i < (tblCust.Fields.Count - 1) then

Buff := Buff + #9;

end;

tblCust.Next;

ifnot 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;
В этой процедуре проходим по всей таблице, собирая в строковый буфер значения полей, разделенные символом табуляции. Записи же разделяются символом перевода строки. Затем я получаю интерфейс на область, куда необходимо поместить данные из таблицы. Это первая часть кода. Далее - самое интересное.

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

Попробуйте несколько раз подряд быстро нажать кнопку "Send data" с этим вариантом передачи данных. Excel просто виснет. Точнее, он что-то делает, загружая на все сто процессор. Почему это происходит? Смутила вот эта строка в реализации TDDEClientConv:
hdata := DdeClientTransaction(Pointer(hszDat), DWORD(-1), FConv, hszItem,

FDdeFmt, XTYP_POKE, TIMEOUT_ASYNC, nil);

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



hdata := DdeClientTransaction(Pointer(hszDat), DWORD(-1), Conv, hszItem,

CF_XLTABLE, XTYP_POKE, xddeTransactionTimeOut, nil);



Контрольное задание
Индивидуально (согласно заданию преподавателя) сформировать документ MSExcel и подключить к BorlandDelphi. Прописать функциональные кнопки аналогично приведенным формам и алгоритмам.
1   2   3   4   5   6   7   8   9


написать администратору сайта