Введение
Довольно часто в прикладных приложениях, работающих с базами данных, статистическими
данными и т.п., возникает проблема создания отчетов с последующим их анализом.
В качестве идеального средства анализа может быть использован Microsoft Excel,
широко известный продукт из поставки Microsoft Office, обладающий громадными
возможностями. Вы, может быть, подумали, что в статье я буду учить вас набивать
данные в Excel вручную, а затем писать скрипты, обрабатывающие эти данные? Вовсе
нет, мы займемся более интересным делом. Мы заставим Excel плясать под нашу
дудку, выполняя все, что мы пожелаем. Что для этого нужно? Во-первых, конечно,
сам Microsoft Excel, версия в принципе не важна, т.к. приведенные примеры будут
работать и под 97 и под 2000 офисом. Желательно при установке Microsoft Office
полностью установить файлы помощи - файл VBAXL8.HLP (или VBAXL9.CHM в случае
Office2000) содержит очень полезную информацию об иерархии объектов Excel, с
которыми нам придется работать. Второе требование - Borland C++Builder или Delphi.
Приведенные примеры подготовлены в среде Borland C++Builder 5.0, не думаю, что
составит большого труда переписать их под Delphi. И, наконец, самое главное
требование - это желание научиться чему-нибудь полезному и время для этого.
Начнем, пожалуй, с основ, с того, на чем все базируется.
Немного теории
Будем отталкиваться от того, что вы имеете представление, что такое указатель
и что такое класс в С++. Без этих знаний вам будет тяжеловато разобраться в
механизме автоматизации, а бездумное переписывание примеров ничего вам не даст.
Итак, как вы понимаете, в "процессе" принимают участие 2 основных
объекта: приложение, которое мы собрались автоматизировать (или попросту говоря,
которым мы будем управлять) - оно называется сервером автоматизации
-
и наше приложение, оно называется контроллером автоматизации
. Сервер
автоматизации состоит из одного или (чаще) нескольких объектов или компонент
,
реализующих функциональность сервера. Автоматизация выполняется при помощи вызовов
методов этих объектов. Посмотрите на следующую диаграмму:
Объекты в составе сервера обычно образуют иерархическую структуру, подобную
этой (для Microsoft Excel):

Итак, с сервером и контроллером мы разобрались. Давайте разберемся с понятием
интерфейса. Начальные сведения о том, что такое интерфейс и как он используется
в программировании, можно почерпнуть из статьи "Использование
интерфейсов при работе с DLL"
. В нашем случае интерфейс - своего
рода буфер между объектом сервера автоматизации и контроллером автоматизации.
Интерфейс позволяет вызывать методы объекта и обращаться к его свойствам в простой
форме, по имени, что-то типа pIterface -> MemberFunction1(), а также обеспечивает
проверку типов параметров функций. Почему не обращаться к public-методам класса
реализующего объект напрямую, спросите вы? А потому, что тогда наш код должен
находиться в одном адресном пространстве с кодом класса, при изменении реализации
класса наш код должен перекомпилироваться, т.е. он сможет работать только с
одной реализацией (или, фактически, версией) сервера. При использовании интерфейса
же клиент абсолютно независим от реализации объекта. Из-за того, что интерфейс
физически представляет собой указатель, он легко может передаваться между процессами
или компьютерами. Кстати, раз уж мы заговорили об физической реализации интерфейса,
посмотрите, как все действительно выглядит:

Т.е., как видите, интерфейс по существу является указателем на указатель на
массив указателей на функции-методы (и свойства) объекта. Немного запутано,
не правда ли? Давайте посмотрим, как все это выглядит на практике.
Переходим, собственно, к автоматизации
При программировании "автоматизирующих" приложений вы часто будете
использовать тип данных Variant. Этот тип данных позволяет работать со всеми
объектами сервера автоматизации при помощи функций OlePropertyGet, OlePropertySet
и OleProcedure. OlePropertyGet принимает строку в качестве параметра и возвращает
данные, содержащиеся в указанном свойстве объекта. OlePropertySet принимает
несколько параметров, первый из которых - строка, указывающая на изменяемое
свойство, а последующие параметры - данные, которые будут записаны в это свойство.
OleProcedure выполняет указанный метод объекта. Чтобы эти функции стали более
понятны вам, представьте, что строка
var1 = var2.OlePropertyGet("Prop1");
равносильна
var1 = var2 -> Prop1;
Аналогично,
var1.OlePropertySet("Prop1", Value)
|
=
|
var1 -> Prop1 = Value
|
var1.OleProcedure("Fun1")
|
=
|
var1 -> Fun1()
|
Итак,
Variant app, ws;
app = CreateOleObject("Excel.Application");
app.OlePropertySet("Visible", true);
app.OlePropertyGet("Workbooks").OleProcedure("Add");
ws = app.OlePropertyGet("Worksheets").OlePropertyGet("Item",1);
Ну вот, 5 строчек кода. 5 сточек кода, которые запустили Microsoft Excel и
добавили рабочую книгу с тремя листами. Этот фрагмент можно использовать как
шаблон для написания ваших собственных программ. Во второй строке мы создаем
объект Excel.Application, представляющий, собственно, приложение Microsoft Excel.
Третья строка делает его видимым. В четвертой добавляется рабочая книга (содержащая,
по умолчанию, три листа). И, наконец, в пятой строке мы получаем указатель на
первый лист, с которым будем впоследствии работать.
Что мы забыли? Вы, вероятно, разочаровались, создав проект и набрав эти строки
в обработчике нажатия какой-нибудь кнопки, откомпилировали проект и получили
сообщения об ошибке? Конечно, мы же забыли включить заголовочный файл, который
позволит работать нам с автоматизируемыми объектами. В 5-м C++Builder'е это
файл utilcls.h, в более ранних (если верить статьям Н.Елмановой) - comobj.hpp
- экспериментируйте.
А теперь создадим что-нибудь полезное и большое - то, что может пригодиться
в рельной задаче и что поближе познакомит вас с иерархией объектов Microsoft
Excel. Задачу сфорулируем так: пусть имеется таблица, содержащая информацию
о товарах (наименование, единица измерения, цена), а нам нужно создать на ее
основе прайс-лист фирмы. Мне нравится работать с таблицам с помощью TQuery,
если хотите изменить его на TTable - пожалуйста, поменять нужно всего пару строк.
Итак, сначала фрагмент - потом объяснения:
void __fastcall TForm1::Button1Click(TObject *Sender)
{ // -- 1
const int xlAlignCenter = -4108;
Variant app, ws, range;
int Row;
// -- 2
app = CreateOleObject("Excel.Application");
app.OlePropertySet("Visible", true);
app.OlePropertySet("SheetsInNewWorkbook", 1);
app.OlePropertyGet("Workbooks").OleProcedure("Add");
ws = app.OlePropertyGet("Worksheets").OlePropertyGet("Item", 1);
// -- 3
ws.OlePropertySet("Name", "Прайс-лист");
// -- 4
ws.OlePropertyGet("Columns").OlePropertyGet("Item",1).
OlePropertySet("ColumnWidth", 30);
// -- 5
range = ws.OlePropertyGet("Range", "A1:C1");
range.OleProcedure("Merge");
range.OlePropertySet("Value", "Прайс-лист нашей фирмы");
range.OlePropertyGet("Font").OlePropertySet("Size", 12);
range.OlePropertyGet("Font").OlePropertySet("Bold", true);
range.OlePropertySet("HorizontalAlignment", xlAlignCenter);
range.OlePropertySet("VerticalAlignment", xlAlignCenter);
range.OlePropertySet("RowHeight", 30);
// -- 6
ws.OlePropertyGet("Cells").
OlePropertyGet("Item", 2, 1).
OlePropertySet("Value", "Наименование товара");
ws.OlePropertyGet("Cells").
OlePropertyGet("Item", 2, 2).
OlePropertySet("Value", "Ед.изм.");
ws.OlePropertyGet("Cells").
OlePropertyGet("Item", 2, 3).
OlePropertySet("Value", "Цена");
range = ws.OlePropertyGet("Range", "A2:C2");
range.OlePropertyGet("Interior").OlePropertySet("Color", 0xC0C0C0);
range.OlePropertySet("HorizontalAlignment", xlAlignCenter);
// -- 7
Row = 3;
qryPrice -> Close();
qryPrice -> SQL -> Clear();
qryPrice ->
SQL -> Add("Select Name,Measure,Price from tblPrice order by Name");
qryPrice -> Open();
// -- 8
while(!qryPrice -> Eof)
{
ws.OlePropertyGet("Cells").
OlePropertyGet("Item", Row, 1).
OlePropertySet("Value", qryPrice -> FieldByName("Name") -> AsString);
ws.OlePropertyGet("Cells").
OlePropertyGet("Item", Row, 2).
OlePropertySet("Value", qryPrice -> FieldByName("Measure") -> AsString);
ws.OlePropertyGet("Cells").
OlePropertyGet("Item", Row, 3).
OlePropertySet("Value", qryPrice -> FieldValues["Price"]);
qryPrice -> Next();
Row++;
}
// -- 9
range = ws.OlePropertyGet("Range", "A2:C"+IntToStr(Row-1));
range.OlePropertyGet("Font").OlePropertySet("Name", "Verdana");
range.OlePropertyGet("Font").OlePropertySet("Size", 8);
// -- 10
for (int i=1; i<=4; i++)
range.OlePropertyGet("Borders").
OlePropertyGet("Item", i).
OlePropertySet("LineStyle", 1);
// -- 11
ws.OlePropertyGet("Range", "C3:C"+IntToStr(Row-1)).
OlePropertySet("NumberFormat", "# ##0.00р.");
// -- 12
app.OlePropertySet("DisplayAlerts", false);
app.OlePropertyGet("Workbooks").OlePropertyGet("Item", 1).
OleProcedure("SaveAs", "price.xls");
// app.OleProcedure("Quit");
}
Блок 1 - объявления переменных и констант. Константа xlAlignCenter будет использоваться
при задании выравнивания в ячейках, переменные
app - содержит указатель на объект приложения;
ws - указатель на рабочий лист;
range - указатель на подмножество ячеек;
Row - текущая строка, куда выводится информация.
Блок 2 совпадает с приведенным выше шаблоном за исключением установки свойства
SheetsInNewWorkbook в 1, что задает количество листов в создаваемой книге.
Блок 3 задает имя рабочему листу.
Блок 4 устанавливает ширину первого столбца ("А"), равную 30. Остальные
столбцы остаются без изменений.
В блоке 5 выводится название отчета - "Прайс-лист нашей фирмы" в
ячейку, образованную слиянием трех первых ячеек первой строки листа. В получившейся
ячейке устанавливается специфичный размер шрифта и выравнивание, а также задается
высота строки.
В блоке 6 создается шапка прайс-листа - выводятся заголовки колонок, фон этих
ячеек меняется на серый и снова выравнивание посередине.
Блок 7 инициализирует цикл вывода данных. Текущая строка становится 3 (так
как именно с нее мы начинаем выводить данные из таблицы), инициализируется и
открывается запрос.
Блок 8 - происходит вывод данных из таблицы на рабочий лист.
Последующие блоки просто форматируют уже полученную информацию. Блок 9 задает
шрифт всего прайс-листа. Блок 10 очерчивает каждую ячейку линией. Дело в том,
что рамка ячейки представляется четырьмя объектами - левая, правая, верхняя,
нижняя рамка. Мы прочерчиваем их все. В блоке 11 задается формат, используемый
при выводе цен - округление до двух знаков, пробел между тысячами, "р."
после числа. В блоке 12 рабочая книга сохраняется в рабочем каталоге программы
под именем price.xls. Уберите символы комментариев, если вам нужно, чтобы Excel
закрывался после сохранения файла на диске. В приведенном случае Excel остается
открытым, так что можно полюбоваться результатами своей (лучше сказать моей)
работы.
Возможные ошибки
Я думаю, вы не остановитесь на предложенном примере и захотите сделать что-нибудь
свое, единственное и неповторимое. При это у вас могут возникнуть следующие
ошибки (я не буду рассматривать ошибки, связанные с общими вопросами программирования):
- если программа не компилируется, убедитесь, что вы включили строку
#include <utilcls.h>
или
#include <comobj.hpp>.
- если сервер не выполняет какие-либо действия, а программа выдает что-нибудь
типа "Unknown name" ("Неизвестное имя"), то, скорее всего,
вы используете свойство или метод, не поддерживаемые данным объектом.
- если сервер не выполняет какие-либо действия, а программа выдает что-нибудь
типа "Exception occured" ("Возникло исключение"), то, скорее
всего, где-то что-то вы неправильно указали в качестве параметра функции (либо
передали данные неправильного типа, либо передали параметры там, где они не
нужны, либо не передали там, где нужны) или неверные данные присваиваете свойству.
Литература по C & C++
|