On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru


Управляющая компания доверительное управление инвестициями Инвест Эдвайзер в США.





 

Автоматизация Microsoft Excel.

Введение

Довольно часто в прикладных приложениях, работающих с базами данных, статистическими данными и т.п., возникает проблема создания отчетов с последующим их анализом. В качестве идеального средства анализа может быть использован 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++