ИНТЕРМЕХ изнутри. IMBASE
Импортирование заголовков интерфейсов
Application -> Catalogs -> Catalog -> Folders
Application -> Catalogs -> Catalog -> Folders -> Folder
Application -> Catalogs -> Catalog -> Folders -> Folder -> Record
В предыдущих статьях, посвященных системе управления базой данных стандартных изделий, материалов и пользовательских данных IMBASE, используемой в системах автоматизированного проектирования и ведения документации ИНТЕРМЕХ, было рассказано о применении 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 и назовем его просто: treeView1:
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 PopulateFolder(Node, Unk As IImbaseFolder);
end;
finally
TreeView1.Items.EndUpdate;
end;
end;
end;
Интерфейс IImbaseApplication, который мы получили в результате подключения к COM-серверу, является отправной точкой иерархической модели объектно-ориентированного API IMBASE. Через этот интерфейс можно получить все остальные объекты, реализованные через API.
Как видно из рис. 4, интерфейсы бывают двух типов:
Рис. 4. Иерархия API IMBASE
• интерфейс, который связан с объектом базы данных (Каталог, папка или таблица);
• интерфейс, содержащий коллекцию объектов базы данных.
В нашем примере мы подробно рассмотрим ветку 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;
Код этой процедуры очень простой, но в ней выполняются два важных действия:
• получаем у главного интерфейса список всех каталогов системы через свойство Catalogs;
• создаем для каждого из них свой узел в главном дереве и сохраняем в поле 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.
Конечно, в одной статье нельзя описать все возможности API IMBASE, поэтому в одном из следующих номеров журнала мы продолжим его описание и рассмотрим применение API для показа и извлечения данных из таблиц, а также работу с ключом IMBASE для хранения ссылок на записи таблицы во внешних приложениях.
Рис. 8. Построение списка таблицы папки
В завершение хочется еще раз напомнить читателям об открытости и адаптивности комплекса систем ИНТЕРМЕХ. Совместными усилиями мы сможем построить единую информационную среду предприятия, объединив уже используемые на предприятии системы автоматизации с решениями ИНТЕРМЕХ.