Инфолиния

Продукция

О нас пишут


ИНТЕРМЕХ изнутри. IMBASE.


Кожемякин Николай Васильевич
В 1985 году закончил Минский Радиотехнический Институт.
5 лет работал программистом в МПО ВТ. 20 лет работает в области САПР.
С 1991 года работает в НПП “ИНТЕРМЕХ”.


В предыдущих статьях, посвященных системе управления базой данных стандартных изделий, материалов и пользовательских данных IMBASE , используемой в системах автоматизированного проектирования и ведения документации ИНТЕРМЕХ было рассказано об использовании этой системы в связке с другими программами ИНТЕРМЕХ. В этой статье пойдет речь о внутреннем устройстве информационной базы данных и о функциональных возможностях системы по связи с внешними программами сторонних разработчиков. Компания ИНТЕРМЕХ в своих разработках всегда делала большой упор на возможности расширения и адаптации своих программных продуктов для различных категорий пользователей, от небольших конструкторских бюро, в котором работает несколько конструкторов до корпоративных заказчиков, у которых количество реально (одновременно) работающих пользователей ( конструкторов, технологов, работников ОТД и других) исчисляется сотнями. В таких условиях работы от системы требуется не только наличие заявленных производителем функциональных возможностей, но и весьма специфические требования, количество которых прямо пропорционально количеству установленных на предприятии рабочих мест.

Для обеспечения возможности использования функциональности IMBASE для решения специфических задач наших клиентов силами своих программистов система предлагает набор функций, известный как API . Программный интерфейс, необходимый для связи IMBASE с собственными разработками заказчиков находится внутри исполняемого модуля, файла Imbase . exe . Простой, но в достаточной мере иллюстрирующий возможности API IMBASE , пример предлагается для рассмотрения в этой статье. Пример будет использовать в качестве среды разработки очень популярный у отечественных разработчиков Borland Delphi , но все нижесказанное относится и к C ++ Builder той же фирмы и к Microsoft Visual Studio .

 

Импортирование заголовков интерфейсов.

Перед тем, как начать работать с функциями API надо дать возможность среде разработки узнать о существовании этих функций. Как уже было отмечено выше, вся эта информация содержится внутри исполняемого файла. Для этого, IMBASE должен быть зарегистрирован в операционной системе вашего компьютера в качестве сервера COM . Только после этого можно начинать импорт заголовков интерфейсов. Итак, начнем. Запускаем Delphi , создаем новый проект и импортируем описание функций IMBASE . Для этого в меню Project выбираем Import Type Library… (см. Рис. 1).

 

Рис.1. Добавление в проект библиотеки типов IMBASE

 

В появившемся диалоговом окне Вы увидите список COM -серверов, доступных для импорта и использования в Вашем проекте, но нас интересует IMBASE , поэтому в предложенном списке попробуем его найти. Мы ищем строчку вида ImBase Library ( Version 1.0) (см. Рис. 2).

Рис.2. Выбор библиотеки типов IMBASE

 

Если такая строчка найдена, нажимаем кнопку CreateUint и среда разработки сгенерирует на базе информации, взятой из IMBASE программный код, необходимый для использования API и поместит ссылку на него в исходный текст Вашего проекта. Если же строчки с COM -сервером Вы в списке не нашли, значит IMBASE еще ни разу не был запущен и Вам надо это сделать, после чего повторить попытку создания заголовков.

 

В исходном коде Вашего проекта должны появится строчки, похожие на эти:

 

program Project1;

uses

Forms,
Unit1 in 'Unit1.pas' {Form1},
ImBase_TLB in '..\Imports\ImBase_TLB.pas';

После этих нехитрых подготовительных операций можно приступать к написанию кода, который будет использовать API IMBASE , а заодно и предоставлять пользователю информацию о содержимом базы данных.

Подключение к IMBASE

Первым шагом перед использованием в Вашей программе функций API является активизация сервера приложений, которым в нашем случае является IMBASE . Код, который выполняет активизацию сервера и ожидает вход в систему оформим в виде процедуры и назовем его CheckImbaseConnection. Выполнять этот код удобнее всего автоматически про отображении главной формы нашего приложения. Для этого подпишемся на событие OnShow главной формы.

 

procedure TForm1.FormShow(Sender: TObject);

begin

CheckImbaseConnection;

BuildMainNodes;

end;


var

Form1: TForm1;

ImBase : IImbaseApplication;

 

//-------------------------------------------------------------

procedure CheckImbaseConnection;

var

Status:ImBaseLoadStatus;

begin

if( Imbase = nil) then

begin

ImBase := CoImbaseApplication.Create;

while true do

begin

Status := ImBase.Status ;

Application.ProcessMessages;

if Status = IST_READY then Break;

end;

end;

end;

 

В теле главной формы объявлена глобальная переменная ImBase типа IimbaseApplication . Эта переменная является экземпляром COM -сервера IMBASE . Строка кода : ImBase := CoImbaseApplication . Create ; Создает в памяти COM объект, запускает приложение imbase . exe .

После запуска IMBASE просит пользователя выполнить регистрацию путем ввода имени и пароля. ( Рис. 3)

 

Рис. 3. Окно ввода имени пользователя и пароля

 

Наш код входит в цикл ожидания этих действий и проверяет состояние системы путем проверки значения, которое возвращается в методе Status до тех пор, пока это значение не станет равным значению константы IST _ READY , описанной в файле Imbase _ tlb . pas , полученном на этапе импорта заголовков интерфейсов.

 

После удачного подключения начинаем использовать полученный интерфейс на сервер IimbaseApplication и строим корневые папки иерархии Каталогов и Справочников. Для этого расположим на форме элемент управления типа TTreeView и назовем его просто: treeView 1.

 

procedure TForm1.FormShow(Sender: TObject);

begin

CheckImbaseConnection;

BuildMainNodes;

end;

 

//-------------------------------------------------------------

procedure TForm1.BuildMainNodes;

var

RootNode : TTreeNode;

begin

RootNode := TreeView1.Items.GetFirstNode;

RootNode.Data := Pointer(Imbase);

RootNode.HasChildren := true;

RootNode.ImageIndex := iiRoot;

RootNode.SelectedIndex := iiRoot;

end ;

 

Код в процедуре BuildMainNodes просто инициализирует главный узел дерева и подготавливает его к интерактивной работе с пользователем. Важным моментом здесь является то, что узлу устанавливается свойство HasChildren в true . Эта техника используется и дальше (при формировании узлов Каталогов и папок) для того, чтобы отобразить символ + возле названия узла, но не формировать его содержимое сейчас, потому что возможно пользователь и не захочет дальше раскрывать этот узел. Таким образом, экономится память и время.

 

Главная форма обрабатывает события, которые возникают от раскрытия узла дерева. При этом происходит анализ, какой узел раскрывается на основании данных, сохраненных в свойстве Data этого узла. В нашем примере там хранится интерфейс отображаемого элемента.

При нажатии пользователем на символ + у узла возникает вышеописанное событие. В процедуре обработки проверятся данные в поле Data выбранного узла и в зависимости от того, интерфейс какого объекта там находится, выполняется соответствующая обработка.

 

При раскрытии главного узла, в котором хранится интерфейс IimbaseApplication , выполняется сканирование всех каталогов.

 

 

procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean);

var

NodeItf,Unk : IInterface;

begin

NodeItf := IInterface(Node.Data);

AllowExpansion := true;

if(Node.HasChildren and (Node.Count = 0)) then // проверка на неисследованный узел

begin

try

TreeView1.Items.BeginUpdate;

Node.HasChildren := false;

if( NodeItf <> nil) then

begin

// Главный узел

if NodeItf.QueryInterface( IID_IImbaseApplication ,Unk) = S_OK

then PopulateCatalogs (Node,Unk As IImbaseApplication)

else

// Узел Каталога

if NodeItf.QueryInterface (IID_IImbaseCatalog ,Unk) = S_OK

then PopulateCatalog (Node,Unk As IImbaseCatalog)

else

// Узел папки

if NodeItf.QueryInterface( IID_IImbaseFolder ,Unk) = S_OK

then PopulateFolde r(Node,Unk As IImbaseFolder);

end;

finally

TreeView1.Items.EndUpdate;

end;

end;

end;

 

Интерфейс IImbaseApplication , который мы получили в результате подключения к COM -серверу является отправной точкой иерархической модели объектно-ориентированного API IMBASE . Через этот интерфейс можно получить все остальные объекты, реализованные через API .

Рис. 4. Иерархия API IMBASE

 

Как видно из рисунка, интерфейсы бывают двух типов:

  1. Интерфейс, который связан с объектом базы данных ( Каталог, папка или таблица)
  2. Интерфейс, содержащий коллекцию объектов базы данных.

В нашем примере мы подробно рассмотрим ветку
Application->Catalogs->Catalog->Folders->Folder->Record->Table .

 

 

Application->Catalogs

В IMBASE каталоги бывают трех логических типов:

  • собственно Каталоги
  • Справочники
  • Технологические справочники

 

Для их группировки создадим в дереве три узла с соответствующими именами и поместим в них каталоги.

 

//-------------------------------------------------------------

procedure TForm1.PopulateCatalogs(Root: TTreeNode;Itf: IImbaseApplication);

var

Nodes: TTreeNodes;

CatNode,RefNode,TechNode,MainNode,Node : TTreeNode;

Catalogs: IImbaseCatalogs;

Catalog : IImbaseCatalog;

Count,Index : integer;

ImageIndex: integer;

begin

Nodes := Root.Owner;

CatNode := Nodes.AddChild(Root,' Каталоги ');

RefNode := Nodes.AddChild(Root,' Справочники ');

TechNode := Nodes . AddChild ( Root ,'Технологические справочники');

Catalogs := Itf . Catalogs ; // Получаем интерфейс на список Каталогов системы

Catalogs._AddRef;

CatNode.Data := Pointer(Catalogs);

RefNode.Data := Pointer(Catalogs);

TechNode.Data := Pointer(Catalogs);

Count := Catalogs . Count ; //Получаем количество Каталогов

for Index := 0 to Count-1 do // Цикл по всем каталогам

begin

Catalog := Catalogs.Item(Index); // Получаем интерфейс на Каталог

case Catalog . Type _ of // Определяем корень для каталога

ICT_CATALOG:

begin

MainNode := CatNode;

ImageIndex := iiCatalog;

end;

ICT_REF:

begin

MainNode := RefNode;

ImageIndex := iiRef;

end;

ICT_TECHREF:

begin

MainNode := TechNode;

ImageIndex := iiRef;

end;

end;

// создаем узел

Node := Nodes.AddChild(MainNode,Catalog.Name);

Node.Data := Pointer(Catalog);

Node.HasChildren:= true;

end ;

end ;

 

Код этой процедуры очень простой, но в ней выполняются два очень важных действия :

  1. Получаем у главного интерфейса список всех Каталогов системы через свойство Catalogs
  2. Создаем для каждого из них свой узел в главном дереве и сохраняем в поле Data интерфейс Каталога IImbaseCatalog .

 

После завершения процедуры PopulateCatalogs наше дерево приобретет вид, показанный на Рис. 5.

 

Рис. 5. Результат раскрытия узла Каталогов

 

Application->Catalogs->Catalog->Folders

Далее, при нажатии на символ “+” узда каталога программа снова попадает в метод обработки раскрытия узла. Так как в поле Data узла содержится ссылка на объект IimbaseCatalog , вызывается процедура формирования папок верхнего уровня каталога. Код этой процедуры получает у Каталога коллекцию папок верхнего уровня и добавляет соответствующие узлы в узел Каталога:

 

procedure TForm1. PopulateCatalog (Root: TTreeNode; Itf: IImbaseCatalog);

var

Nodes: TTreeNodes;

Node : TTreeNode;

Index,Count : integer;

Folders: IImbaseFolders;

Folder : IImbaseFolder;

begin

Nodes := Root.Owner;

Folders := Itf . Folders ; // получаем у Каталога коллекцию корневых папок

Count := Folders.Count;

for Index :=0 to Count -1 do // для каждой папки в коллекции создаем подузел

begin

Folder := Folders.Item(Index);

Node := Nodes.AddChild(Root,Folder.Name);

Node.ImageIndex := iiFolder;

Node.SelectedIndex := iiFolderOp;

Folder._AddRef;

Node.Data := Pointer(Folder); // сохраняем ссылку на интерфейс папки

Node.HasChildren := (Folder.SubFoldersCount > 0); // проверяем наличие подпапок

end;

end;

 

Основное отличие кода этой процедуры состоит в том, что у папки запрашивается количество ее подпапок через свойство SubFoldersCount и и символ “+” в дереве изображается только в том случае, если они есть. Результат выполнения этой процедуры показан на Рис. 6.

.

Рис. 6. Результат раскрытия корневых папок Каталога

 

Application->Catalogs->Catalog->Folders->Folder

И, наконец, последний штрих в построении дерева иерархии справочной базы: раскрытие содержимого папки IMBASE .

 

procedure TForm1. PopulateFolder (Root: TTreeNode; Itf: IImbaseFolder );

end ;

 

Здесь отличия в коде от процедуры PopulateCatalog минимальные и заключаются в том, что в качестве аргумента в процедуру передается не интерфейс Каталога, а интерфейс папки. Результат работы процедуры представлен на Рис. 7.

 

 

Рис.7. Результат раскрытия папок

 

Как видно из приведенных примеров, построение иерархии не является сложной задачей, и может быть выполнено очень быстро и с минимальным использованием пользовательского кода.

 

Application->Catalogs->Catalog->Folders->Folder->Record

 

Кроме коллекции вложенных папок, каждая папка IMBASE содержит коллекцию записей Каталога ( IImbaseRecords ), которая доступна через свойство Records . Эта коллекция представляет собой одну или несколько записей Каталога, которые в свою очередь могут содержать ссылку непосредственно на таблицу с данными. Содержится или нет в записи каталога ссылка на таблицу, определяется при создании Каталога Администратором системы.

 

В нашем примере в правой части окна содержится элемент управления типа TListView , в котором отображается содержимое выбранного в дереве узла папки.

 

При выборе узла с папкой вызывается метод, который получает коллекцию записей папки и выводит ее в виде списка. Фрагмент кода, который это реализует приведем ниже:

Records := Folder.Records;

Count := Records.Count;

for Index:=0 to Count-1 do

begin

Item := Items.Add;

Record_ := Records.Item(Index);

Table := nil;

try

Table := Record_.Table;

except

end;

if( Table <> nil) then begin

Table.Bind;

Item.Caption := Table.Name;

Item.SubItems.Add(Таблица Imbase ');

Item.ImageIndex := iiTable;

Table._AddRef;

Item.Data := Pointer(Table);

end

else begin

Item.Caption := Record_.Values[0];

Item . SubItems . Add (' Запись в Каталоге ');

Item.ImageIndex := iiRecord;

end ;

end ;

Результат работы этого фрагмента кода приведен на Рис. 8.


Рис.8. Построение списка таблицы папки

 

К сожалению, объем одной статьи не позволяет описать все возможности API IMBASE , поэтому в одном из следующих номеров журнала мы продолжим его описание и рассмотрим применение API для показа и извлечения данных из таблиц, а также работу с ключом IMBASE для хранения ссылок на записи таблицы во внешних приложениях.

 

В завершении статьи еще раз хочется напомнить читателям об открытости и адаптивности комплекса систем ИНТЕРМЕХ. Совместными усилиями мы сможем построить единую информационную среду предприятия, объединив уже используемые на предприятии системы автоматизации с решениями ИНТЕРМЕХ.


     




  ИНТЕРМЕХ

Наш адрес:

Республика Беларусь, 220004, Минск, ул. Короля, 51

Телефон приемной: (+375 17) 306-21-50
факс: (+375 17) 306-21-53

E-mail: cad@intermech.ru

Отдел маркетинга:

(+375 17) 306-21-30, 306-21-32, 306-21-35, 306-21-36, 306-21-37

Техническая поддержка:

(+375 17) 306-21-43, 306-21-45, 306-21-46