Создание контроллеров автоматизации с помощью C++Builder
Наталия Елманова
Тестирование сервера автоматизации
В предыдущей статье "Создание серверов автоматизации с
помощью C++Builder" мы
рассмотрели создание приложения, являющегося
сервером автоматизации. Теперь, основываясь на
информации о методах класса его объекта
автоматизации, содержащейся в библиотеке типов,
создадим приложение, управляющее этим сервером.
Такие приложения называются контроллерами
автоматизации.
Создание контроллера
На главной форме будущего
приложения-контроллера разместим компоненты TEdit,
TCheckBox, TOpenDialog, TSaveDialog, а также десять кнопок.
Главная форма контроллера
автоматизации
Создадим обработчики событий,
связанные с нажатием на кнопки (при этом следует
сослаться на h-файл модуля ComObj):
//———————————————————————————————#include
<vcl.h>
#pragma hdrstop
#include <ComObj.hpp>
#include “autocon.h”
//———————————————————————————————#pragma
package(smart_init)
#pragma resource “*.dfm”
TForm2 *Form2;
Variant Serv;
//———————————————————————————————__fastcall
TForm2::TForm2(TComponent* Owner)
: TForm(Owner)
{
}
//———————————————————————————————
void __fastcall TForm2::Button3Click(TObject *Sender)
{
Serv=CreateOleObject(“Project1.MyAuto3”);
}
//———————————————————————————————
void __fastcall TForm2::Button1Click(TObject *Sender)
{
if (VarType(Serv)==varDispatch) Edit1->Text=IntToStr(Serv.OlePropertyGet(“Width”));
// имеет смысл проверить, что именно находится
// в вариантной переменной…
}
//———————————————————————————————
void __fastcall TForm2::Button2Click(TObject *Sender)
{
if (VarType(Serv)==varDispatch) Serv.OlePropertySet(“Width”,StrToInt(Edit1->Text));
}
//———————————————————————————————
void __fastcall TForm2::Button12Click(TObject *Sender)
{
if (VarType(Serv)==varDispatch) CheckBox1->Checked=Serv.OlePropertyGet(“Visible”);
}
//———————————————————————————————
void __fastcall TForm2::Button13Click(TObject *Sender)
{
if (VarType(Serv)==varDispatch) Serv.OlePropertySet(“Visible”,CheckBox1->Checked);
}
//———————————————————————————————
void __fastcall TForm2::Button11Click(TObject *Sender)
{
if (VarType(Serv)==varDispatch) Serv=Unassigned;
}
//———————————————————————————————
void __fastcall TForm2::Button5Click(TObject *Sender)
{
if (VarType(Serv)==varDispatch)
{if (OpenDialog1->Execute())
Serv.OleProcedure(“OpenFile”,OpenDialog1->FileName);}
}
//———————————————————————————————
void __fastcall TForm2::Button6Click(TObject *Sender)
{
if (VarType(Serv)==varDispatch)
{ if (SaveDialog1->Execute())
Serv.OleProcedure(“SaveFile”,SaveDialog1->FileName);}
}
//———————————————————————————————
void __fastcall TForm2::Button7Click(TObject *Sender)
{
if (VarType(Serv)==varDispatch) Serv.OleProcedure(“NewFile”);
}
//———————————————————————————————
void __fastcall TForm2::Button4Click(TObject *Sender)
{
if (VarType(Serv)==varDispatch) Serv.OleProcedure(“AddLine”,Edit1->Text);
}
//———————————————————————————————
Некоторые комментарии
Теперь настало время пояснить, что именно
делает приведенный выше код. Для управления
сервером автоматизации мы создали переменную
типа Variant (в C++Builder для этой цели имеется
соответствующий класс) и вызвали функцию
CreateOleObject, содержащуюся в модуле ComObj библиотеки VCL.
При выполнении функции CreateOleObject произойдет
следующее. Эта функция, вызвав несколько функций
Win32 API, создаст экземпляр COM-объекта IDispatch и вернет
его внутри вариантной переменной. Этот объект, в
свою очередь, содержит интерфейс объекта (в
данном случае нашего сервера автоматизации),
методы которого мы хотим вызывать из приложения.
Если исследовать реализацию функции CreateOleObject в
исходном тексте модуля ComObj, можно обнаружить, что
она, в свою очередь, вызывает функцию Win32 API
CoCreateInstance, являющуюся частью спецификации COM,
назначение которой — создать объект из
исполняемого файла или DLL. Переменная типа Variant
может содержать разнообразные данные (строку,
число и др., в том числе и интерфейс COM-объекта).
Отметим, что в отличие от Visual Basic или Delphi C++Builder
не позволяет обращаться к методам и свойствам
вариантных переменных, существование которых
заранее неизвестно. Поэтому допустимый в Delphi код
вида
if VarType(Serv) = varDispatch then
Serv.Width := StrToInt(Edit1.Text);
не имеет аналога в C++Builder. Дело в том, что при
создании контроллеров с помощью Delphi в случае
объектов типа Variant в отличие от объектов другого
типа, например TForm, компилятор не проверяет,
имеется ли в действительности такое свойство (в
данном случае Width) у данного объекта. На этапе
выполнения такого кода происходит вызов функций
Win32 API, в результате работы которых меняется
свойство Width объекта, содержащегося не в адресном
проcтранстве созданного контроллера, а в
адресном пространстве сервера.
В С++Builder достичь такого же результата можно с
помощью следующего кода:
if (VarType(Serv) == varDispatch)
Serv.OlePropertySet(“Width”,StrToInt(Edit1->Text));
В этом случае на этапе выполнения производится
вызов тех же самых функций Win32 API, что и в
предыдущем случае. OlePropertySet представляет собой
оболочку для метода вариантной переменной Exec()
(наряду с OlePropertyGet, OleProcedure и OleFunction, позволяющими
получать значения свойств объектов
автоматизации и выполнять их методы). Отметим,
что в Delphi также можно использовать вызовы OlePropertySet, OlePropertyGet, OleProcedure,
OleFunction.
После запуска контроллера при нажатии кнопки
Connect запускается сервер. При нажатии кнопки Disconnect
он выгружается. При нажатии кнопок New File, Open File и
Save File происходит очистка окна редактирования,
загрузка текста в окно редактирования сервера из
файла, сохранение текста в файле. Кнопка Get Visible
показывает и скрывает окно сервера в зависимости
от наличия отметки возле надписи Visible, при этом в
невидимом состоянии сервер продолжает выполнять
свои функции. Нажатие кнопки Set Visible приводит
отметку возле надписи Visible в соответствие
значению свойства Visible сервера. Нажатие кнопки Get
Width приводит к тому, что в строке редактирования в
верхней части окна контроллера отображается
ширина окна сервера в пикселах. Если ввести в
строку редактирования другое число и нажать
кнопку Set Width, ширина окна сервера станет равной
введенному числу пикселов. Нажатие кнопки Add String
приводит к тому, что в редактируемый текст
добавляется строка,
находящаяся в этот момент в поле редактирования.
Контроллер и сервер автоматизации,
запущенные одновременно
Хотелось бы обратить внимание на то, что, хотя
мы и смогли протестировать свойства и методы
сервера автоматизации с помощью созданного
контроллера, у нас еще не было возможности
произвести отладку части кода, связанного с
автоматизацией. Естественно, если клиент
запускается под управлением среды разработки
C++Builder, использовать тот же самый экземпляр среды
разработки для отладки сервера невозможно.
Поэтому следует открыть проект сервера в
отдельном экземпляре среды разработки и
запустить его на выполнение. Если после этого
запустить приложение-контроллер (неважно, под
управлением другого экземпляра среды разработки
или просто средствами операционной системы) и
нажать кнопку Connect, контроллер соединится с уже
запущенным экземпляром сервера. Если в исходном
тексте сервера отмечены точки останова, при их
достижении выполнение кода сервера прекращается
и управление передается среде разработки.
Отметим один очевидный факт: COM-сервер и
COM-клиент могут быть написаны с использованием
любых средств разработки, поддерживающих
COM-технологию. Поэтому в принципе не возбраняется
написать сервер автоматизации с помощью Delphi, а
контроллер — с помощью C++Builder (или наоборот).
Как было сказано в предыдущей статье, наличие
тех или иных возможностей управления
произвольным сервером зависит от того, какие
объекты, свойства и методы сервера предоставлены
разработчиками сервера для автоматизации с
помощью внешних приложений. Каким образом можно
получить информацию о них?
Обычно такие сведения содержатся в
документации или файлах справочной системы,
поставляемых с данным сервером, как, например,
это сделано в MS Office или Seagate Crystal Reports Professional. Но в
принципе такую информацию можно получить и из
библиотеки типов.
В качестве примера рассмотрим использование
информации из библиотеки типов MS Excel как одного
из наиболее часто используемых серверов
автоматизации в практике отечественных
разработчиков. Надо заметить, что практически
все, что может сделать пользователь, работая с
этим приложением, равно как и с другими
приложениями MS Office, доступно для автоматизации.
Как и в предыдущем случае, для управления
сервером автоматизации следует создать
переменную типа Variant (в C++Builder для этой цели
имеется соответствующий класс) и вызвать функцию
CreateOleObject:
Variant XL;
……
XL=CreateOleObject(“Excel.Application.8”);
В качестве параметра функции CreateOleObject
передается имя объекта, который мы хотим создать.
Найти его можно в реестре Windows:
Запись в реестре, соответствующая
выбранному серверу
Внутри некоторых OLE-серверов (в частности,
приложений MS Office) существует иерархия вложенных
объектов примерно следующего вида:
Примерная иерархия вложенных
объектов OLE-сервера
Свойствами объектов Excel могут являться так
называемые коллекции объектов. Например,
коллекция Workbooks является свойством объекта
Excel.Application, при этом она содержит набор вложенных
объектов — рабочих книг Excel, а те, в свою очередь,
обладают свойством Worksheets, представляющим собой
коллекцию рабочих листов, каждый из которых
обладает свойством Cells, являющимся коллекцией
ячеек. Аналогично коллекция Charts также является
свойством рабочей книги, и, соответственно,
внутри свойствами объектов Word могут быть
коллекции Paragraphs, Words, Tables.
В С++Builder обращение к члену коллекции
производится следующим образом:
Variant MyWorkbook=XL.OlePropertyGet(“WorkBooks”).OlePropertyGet(“Item”,1);
Рассмотрим пример, использующий такие
коллекции. Пусть при нажатии на кнопку главной
формы приложения-клиента будут выполнены
следующие действия:
создание в Excel рабочей книги с двумя страницами
и какими-нибудь именами;
заполнение первых 10 ячеек двух первых колонок
числами;
вычисление в одиннадцатой ячейке их суммы;
изменение цвета и начертания шрифта в одной из
колонок, а также цвета ячеек;
сохранение полученной рабочей книги в файле.
Код, заставляющий Excel выполнить эти действия,
будет выглядеть следующим образом:
//———————————————————————————————#include
<vcl.h>
#pragma hdrstop
#include <ComObj.hpp>
#include “xlauto2.h”
//———————————————————————————————#pragma
package(smart_init)
#pragma resource “*.dfm”
TForm1 *Form1;
Variant XL,v0,v1,v2;
//Function Item(“Item”);
//———————————————————————————__fastcall
TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//———————————————————void __fastcall
TForm1::Button1Click(TObject *Sender)
{
XL=CreateOleObject(“Excel.Application.8”);
XL.OlePropertySet(“Visible”,true);
v0=XL.OlePropertyGet(“Workbooks”);
v0.OleProcedure(“Add”);
v1=v0.OlePropertyGet(“Item”,1);
v0=v1.OlePropertyGet(“Worksheets”) ;
v0.OlePropertyGet(“Item”,1).OlePropertySet(“Name”,”Бухгалтерия
желтая”);
v0.OlePropertyGet(“Item”,2).OlePropertySet(“Name”,”Бухгалтерия
красная”);
for (int j=1;j<3;j++)
{
v1=v0.OlePropertyGet(“Item”,j);
for (int i=1;i<11;i++)
{
v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,i,1).OlePropertySet(“Value”,i);
v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,i,2).OlePropertySet(“Value”,i*5);
v2=v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,i,2);
v2.OlePropertyGet(“Font”).OlePropertySet(“Color”,clBlue);
v2.OlePropertyGet(“Font”).OlePropertySet(“Bold”,true);
v2.OlePropertyGet(“Interior”).OlePropertySet(“ColorIndex”,9-3*j);
}
v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,11,1).OlePropertySet(“Value”,”=SUM(A1:A10)”);
v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,11,2).OlePropertySet(“Value”,”=SUM(B1:B10)”);
}
//отключить диагностику при
закрытии сервера
XL.OlePropertySet(“DisplayAlerts”,false);
XL.OlePropertyGet(“Workbooks”).OlePropertyGet(“Item”,1).OleProcedure(“SaveAs”,”test.xls”);
XL.OleProcedure(“Quit”);
}
//———————————————————————————————
Отметим, что для запуска Excel в фоновом режиме
без отображения его окна на экране достаточно
убрать из кода С++Builder строку:
XL.OlePropertySet(“Visible”,true);
Результат работы
приложения
Следует обратить внимание на то, что вариантная
переменная XL объявлена за пределами процедуры
манипуляции OLE-сервером. Это сделано для того,
чтобы существование вариантной переменной не
ограничивалось данной процедурой.
Использование информации из библиотек типов
Отметим, что в приведенном выше примере рабочая
книга и рабочие листы были созданы с применением
параметров, предлагаемых по умолчанию. Каким
образом можно изменить эти параметры?
Рассмотрим следующий пример кода:
XL=CreateOleObject(“Excel.Application.8”);
XL.OlePropertySet(“Visible”,true);
v0=XL.OlePropertyGet(“Workbooks”);
v0.OleProcedure(“Add”,-4109);
Данный пример заставляет Excel создать пустой
лист с диаграммой на основе шаблона, отличного от
принятого по умолчанию.
Откуда в данном случае взялась константа –4109? Каким образом можно узнать, какими
еще константами можно пользоваться при
автоматизации с использованием данного сервера?
Ответы на эти вопросы можно получить, открыв
библиотеку типов данного сервера. Сделать это
можно, выбрав пункт меню File/Open, указав в списке
возможных файлов Type Library и выбрав
соответствующую библиотеку типов (для Excel это
Excel8.olb, для Word – MSWord8.olb). При этом по истечении
некоторого времени (эти библиотеки очень велики)
будут созданы файлы с описанием всех
использованных констант, а также свойств и
методов содержащихся в сервере объектов (в
данном случае Excel_TLB.cpp и Excel_TLB.h для С++Builder либо
Excel_TLB.pas для Delphi). При создании этих файлов
появятся сообщения об ошибках, связанные с тем,
что в коде Excel и Word могут использоваться
зарезервированные слова Object Pascal или С++.
Редактор библиотеки типов при этом
представляет все вложенные объекты в виде
иерархической структуры, внутри которой можно
найти значения необходимых констант. В
частности, в разделе шаблонов можно найти список
констант, характеризующих различные типы
шаблонов листов Excel:
Значения констант, описывающих
шаблоны MS Excel 97
Именно там и содержится ссылка на константу
xlWBATChart = –4109, соответствующую шаблону диаграммы.
При необходимости использовать в коде
приложения-клиента именованные константы можно
сослаться на файл Excel_TLB.h или Excel_TLB.pas в тексте
модуля приложения.
Точно так же можно определить, каковы свойства
и методы вложенных объектов данного сервера
автоматизации:
Свойства и методы коллекции Workbooks
Используя сведения о константах, объектах,
свойствах и методах OLE-сервера, можно
модифицировать приведенный выше пример. Теперь
мы создадим в Excel график на основе данных из
рабочих листов, скопируем его в буфер обмена и
перенесем в документ Word (об объектах и константах
которого узнаем из библиотеки типов Word), снабдив
график подписью. Соответствующий код выглядит
следующим образом:
//———————————————————————————————#include
<vcl.h>
#pragma hdrstop
#include “ole2.h”
#include <ComObj.hpp>
//———————————————————————————————#pragma
package(smart_init)
#pragma resource “*.dfm”
TForm1 *Form1;
Variant XL,v0,v1,v2, v22, vrange, WD,a,b,c;
//———————————————————————————————__fastcall
TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//———————————————————————————————
void __fastcall TForm1::Button1Click(TObject *Sender)
{
XL=CreateOleObject(“Excel.Application.8”);
XL.OlePropertySet(“Visible”,true);
v0=XL.OlePropertyGet(“Workbooks”);
v0.OleProcedure(“Add”);
v1=v0.OlePropertyGet(“Item”,1);
v0=v1.OlePropertyGet(“Worksheets”) ;
v22=v1.OlePropertyGet(“Charts”) ;
v22.OleProcedure(“Add”);
v0.OlePropertyGet(“Item”,1).OlePropertySet(“Name”,”Бухгалтерия
желтая”);
v0.OlePropertyGet(“Item”,2).OlePropertySet(“Name”,”Бухгалтерия
красная”);
for (int j=1;j<3;j++)
{
v1=v0.OlePropertyGet(“Item”,j);
for (int i=1;i<11;i++)
{
v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,i,1).OlePropertySet(“Value”,i);
v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,i,2).OlePropertySet(“Value”,i*5);
v2=v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,i,2);
v2.OlePropertyGet(“Font”).OlePropertySet(“Color”,clBlue);
v2.OlePropertyGet(“Font”).OlePropertySet(“Bold”,true);
v2.OlePropertyGet(“Interior”).OlePropertySet(“ColorIndex”,9-3*j);
}
v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,11,1).OlePropertySet(“Value”,”=SUM(A1:A10)”);
v1.OlePropertyGet(“Cells”).OlePropertyGet(“Item”,11,2).OlePropertySet(“Value”,”=SUM(B1:B10)”);
}
vrange=v0.OlePropertyGet(“Item”,1).OlePropertyGet(“Range”,”A1:A10");
v1=v22.OlePropertyGet(“Item”,1);
v2=v1.OlePropertyGet(“SeriesCollection”);
v2.OleProcedure(“Add”,vrange);
vrange=v0.OlePropertyGet(“Item”,1).OlePropertyGet(“Range”,”B1:B10");
v2.OleProcedure(“Add”,vrange);
v1.OleProcedure(“Select”);
XL.OlePropertyGet(“Selection”).OleProcedure(“Copy”);
WD=CreateOleObject(“Word.Application.8”);
WD.OlePropertySet(“Visible”,true);
WD.OlePropertyGet(“Documents”).OleProcedure(“Add”);
a=WD.OlePropertyGet(“Documents”);
b=a.OleFunction(“Item”,1);
for (int i=1;i<5;i++)
{b.OlePropertyGet(“Paragraphs”).OleProcedure(“Add”);};
c=b.OleFunction(“Range”,1,2);
c.OleProcedure(“Paste”);
c=b.OleFunction(“Range”,3,3);
c.OlePropertySet(“Text”,”График,
скопированный из рабочей книги Excel “);
XL.OlePropertySet(“DisplayAlerts”,false);
XL.OlePropertyGet(“Workbooks”).OlePropertyGet(“Item”,1).OleProcedure(“SaveAs”,”test.xls”);
XL.OleProcedure(“Quit”);
WD.OlePropertySet(“DisplayAlerts”,false);
b.OleProcedure(“SaveAs”,”test2.DOC”);
WD.OleProcedure(“Quit”);
}
//———————————————————————————————
Результаты работы приложения представлены на
рисунке.
Результат автоматизации создания
диаграммы Excel и переноса ее в Word
Итак, создав код, подобный приведенному выше, мы
можем автоматизировать довольно сложный набор
рутинных пользовательских операций, что может
быть весьма удобно, если такие операции часто
повторяются. При этом создание такого кода в
общем случае, безусловно, требует гораздо
меньших усилий, чем написание отдельного
приложения, реализующего какие-то части
функциональности Word или Excel.
Следующая статья данного цикла будет посвящена
использованию OLE-документов в приложениях C++Builder.
Координаты автора:
Центр Информационных Технологий.
Тел.: (095) 932-92-12, 932-92-13,
elmanova@citmgu.ru, http://www.citmgu.ru,
|