Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Подземелье Магов
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  09:49[Войти] | [Зарегистрироваться]

Приложение со свойствами платформы. Простая платформа.

Николай
дата публикации 28-11-2003 15:26

Приложение со свойствами платформы. Простая платформа.

Если читатель желает начать знакомство с самого начала, нужно найти исчезнувший журнал Программист, в котором были опубликованы статьи по данной платформе:

  • Приложение со свойствами платформы, №7, 2002г.,
  • Приложение со свойствами платформы. Типы полей, №2, 2003г.

Впрочем, можно особо не жалеть, если не удастся найти эти статьи, - суть будет здесь изложена в необходимой подробности.

Главная идея

Главная цель платформы состоит в том, чтобы освободить программиста-разработчика от участия в сопровождении многотиражных приложений на уровне программного кода, когда каждому заказчику нужно что-то свое при сохранении общей концепций обработки информации. И другая цель - обеспечить создание много рабочих мест, работающих с общей базой данных, но отличающихся по набору и последовательности выполняемых функций, а также наборами отображаемой информации, в том числе по запросам.

Такие требования характерны, например, для лечебного учреждения, в котором пациент приходит для записи на прием в регистратуру, а потом проходит осмотр и/или лечение у разных врачей: терапевта, окулиста, хирурга и т.д. Все врачи работают с одной и той же информационной единицей, т.е. с амбулаторной картой больного, или с медицинской картой, если это стационар. Но каждый из них имеет свою специфику. Если не принять специальных мер при создании программного обеспечения системы автоматизации лечебного процесса, то придется для каждого врача-специалиста создавать отдельное приложение, причем львиная доля программного кода будет одна и та же во всех этих приложениях.

Платформа в данном случае — это приложение, имеющее в своем составе набор необходимых базовых функций, из которых может быть создан любой алгоритм работы врача. При этом каждая функция пишется программистом один раз, а применяется в любом месте за счет создания соответствующего пункта меню для ее запуска. Впрочем, меню - это всего лишь одно из средств активизации функции. Можно, например, функцию выбирать из специально подготовленного списка. Но автор применил именно систему меню для оформления каждого рабочего места.

Приведенная концепция при реализации обросла дополнительными примочками, которые стали настолько серьезными, что стали уже играть самостоятельную роль. Одна из таких "примочек" - конструктор баз данных. Описываемая платформа обладает механизмом создания, модификации и удаления таблиц и полей баз данных, доступным на стадии эксплуатации. Развитие этого механизма привело к понятию системной базы данных, т.е. таблиц, скрытых от пользователя, в которых хранится информация о пользовательских таблицах и полях, а также о многочисленных прочих реквизитах, которые оказались полезны при построении интерфейса пользователя и создании главного меню приложения. Дальше больше. Появилась потребность прицепить к любому пользовательскому окну любую таблицу или набор данных запроса, давая при этом возможность отобрать нужные поля для отображения. Названия заголовков колонок таблиц и наборов данных запросов тоже потребовалось сделать редактируемыми при настройке.

Потребовался универсальный механизм вывода отображаемых данных на принтер или в Excel, что является своего рода отчетом. Дальнейшие накрутки пошли при работе с запросами. Составление запроса, как известно, целая наука. Очень утомительно писать запросы повторно, т.е. их нужно где-нибудь хранить. Решено было записывать запросы в ту же самую системную базу данных. Но тогда нельзя гарантировать, что записанный в системную базу запрос останется актуальным при очередной загрузке приложения. Любая таблица за время между запусками могла быть реконструирована так, что запрос, работавший с ним, уже был бы ошибочным. Отсюда появилась задача формирования текста запроса после загрузки информации о пользовательской базе данных. Точнее говоря, текст SQL-запроса формируется в момент использования запроса, автоматически контролируя при этом соответствие структуре базы данных.

Дальнейшее изложение реализованных возможностей в подробностях, пожалуй, уже будет не столь интересно, т. к. основная идея кажется уже очевидной. Конкретный перечень возможностей при этом в каждой предметной области может быть своим и сильно зависит от ряда факторов: уровня подготовки разработчика, его фантазии, наличия команды программистов, способных реализовать большие проекты, т.к. платформа может быть самых разных масштабов. Стоит лишь подчеркнуть, что богатство возможностей платформы будет определяться набором функций.

Добавлю еще, что есть потребность дать пользователю настроить для своих нужд и пользовательский интерфейс: если нужно, что-нибудь добавить в рабочее окно, или убрать какой-либо элемент оттуда, поменять шрифт у контролов, изменить надписи, размеры элементов интерфейса и т.д. Короче говоря, нужно иметь что-нибудь наподобие инспектора свойств из Delphi, но только на русском языке чтобы все показывало.

Вот такая задача.
Перейдем, однако, на сухой язык формального описания идей платформы.

Архитектура платформы

Платформа реализована по схеме клиент - сервер на СУБД MS SQL Server 2000. Перевести ее в разряд трехуровневой архитектуры – голубая мечта автора. В ней уже присутствует частичная адаптация для сервера Oracle, об особенностях которой есть шанс рассказать отдельно.

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

Платформа имеет два режима запуска: КОНФИГУРАТОР и ПОЛЬЗОВАТЕЛЬСКИЙ РЕЖИМ, напоминая чем -то 1С. Идея этих режимов действительно навеяна 1С.

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

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

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

Первая компонента (TDbInterface – интерфейс баз данных) ведает обработкой структур памяти, хранящих информацию о пользовательской базе данных, включая информацию о запросах и некоторых других объектах, о которых речь впереди.

  TDbInterface = class (TComponent)
  private
    FDatabaseName : String;
    // Список указателей на структуры категорий информации
    FInfoCategoryList : TInfoCategoryList;
    // Список указателей на структуры таблиц
    FTablesList : TList;
    // Список имен таблиц
    FTableNames : TStrings;
    // Список ссылок на комбинированные типы данных
    FFbCommonTypeList : TFbCommonTypeList;
    // Тип драйвера доступа к данным
    FDrvType : TDrvType;
    // Загрузка системной информации - установка имени FDatabaseName
    Procedure Set_DatabaseName(Value : String);
  public
    Constructor Create(AOwner : TComponent); Override;
    Destructor Destroy; Override;
    // Создание новой структуры таблицы
    Function  New_pTTableInfo(ATableName : String;
      AUpdateTypes : Boolean) : pTTableInfo;
    // Создание новой структуры поля
    Function  New_pTFieldInfo : pTFieldInfo;
    // Освобождение памяти, занятой структурой таблицы
    Function  Dispose_pTTableInfo(ApTTableInfo: pTTableInfo;
      ADisposeFields, AUpdateTypes : Boolean): Bool;
    // Освобождение памяти, занятой структурой поля
    Function  Dispose_pTFieldInfo(ApTFieldInfo: pTFieldInfo): Bool;
    Property TablesList : TTablesList read FTablesList;
    // Список категорий информации БД
    Property FbCommonTypeList : TFbCommonTypeList read FFbCommonTypeList;
    Property InfoCategoryList : TInfoCategoryList read FInfoCategoryList;
    // Новая таблица, поле
  published
    Property DrvType : TDrvType read FDrvType write FDrvType;
    Property DatabaseName : String read FDatabaseName write Set_DatabaseName;
  end;

Вторая компонента (TArmInterface – интерфейс системы управления) ведает обработкой структур памяти, хранящих информацию о специальных атрибутах, так называемых элементах системы управления (СУ), из которых создается главное меню рабочего места.

  TArmInterface = class (TObject)
  private
    FDatabaseName : String;
    FFbSUObjectL  : TFbSUObjectList; // Обобщенный список элементов СУ
    FFbMedTreeV   : TTreeView;       // Дерево конфигурации АРМ
    FArmMainMenu  : TMainMenu;       // Главное меню конфигурации АРМ
    FForm         : TForm;
    FDbInterface  : TDbInterface;

    // Загрузка системной информации - установка имени FDatabaseName
    Procedure Set_DatabaseName(Value : ShortString);

    // Запуск прикладной функции, вызываемой по номеру ID - приводится в
    // действие при выборе пункта меню, по значению его свойства Tag
    Procedure StartFb_Procedure(Sender : TObject);
  Public
    // Создание структуры TFbSUObject и возврат ссылки на нее
    Function New_pTFbSUObject(AFbSUType : TFbSUType) : pTFbSUObject;
    // Освобождение памяти, занимаемой структурой TFbSUObject по ссылке
    Procedure Free_pTFbSUObject(ApTFbSUObject: pTFbSUObject);

    // Создание меню АРМ по информации в FFbMedTreeV
    Procedure LoadArmMenu(ApTFbSUObject : pTFbSUObject);

  published
    Property DatabaseName : String read FDatabaseName write Set_DatabaseName;
    Property DbInterface : TDbInterface read FDbInterface write Set_FDbInterface;
  end;

В обоих компонентах есть ключевая операция – установка имени поля FDatabaseName, которая приводит в действие процедуру Set_DatabaseName. При загрузке приложения сначала нужно создать экземпляр TDbInterface, т.к. в компоненте TArmInterface нужно указывать ссылку FDbInterface на существующий экземпляр TDbInterface.

В процессе выполнения процедуры Set_DatabaseName осуществляются следующие действия.

Компонента DbInterface:
  1. Определяется тип драйвера баз данных BDE, используемый для данного подключения и он сохраняется в поле FDrvType.
  2. Производится запрос информации из системной таблицы T_Tables, хранящей информацию о пользовательских таблицах и для каждой записи полученного набора данных в памяти создается структура для хранения информации о таблице:
  3.   // Структура таблицы
      TTableInfo = record
        // Атрибуты
        sTableAttr : TStrings; 
          { sTableName    - имя таблицы  }
          { sTableCaption - наименование }
          { sTableDescr   - описание     }
        sFieldsL : TList;
        // Связанные DataSet и DataSource ...
        sQuery        : TQuery;
        sQrDataSource : TDataSource;
      end;

    В этой структуре показана только часть полей, смысл которых понятен из комментариев к структуре. Обратите внимание, что структура содержит компоненты TQuery и TDataSource. Это – принципиальный момент. Платформа не имеет других компонент доступа к данным, кроме тех, что содержатся в структурах TTableInfo и аналогичных им, применяемых для работы с запросами пользователей. Впрочем, имеются очень редкие исключения из этого правила, не носящие принципиального характера. Следующий принципиальный момент – для каждой таблицы в памяти создается только одна структура TTableInfo. Для того, чтобы структуру таблицы можно было использовать в самых различных местах приложения, ведется список FTablesList ссылок pTTableInfo в объекте TDbInterface. Список имен таблиц FTableNames также содержит ссылки pTTableInfo в поле Objects. Избыточность информации здесь вполне оправдана, т.к. в приложении масса случаев, когда нужно получить ссылку на структуру таблицы, зная имя таблицы.

    Обратим внимание на список sFieldsL, содержащий список ссылок на структуры полей. Упрощенный вид структуры поля имеет вид

      // Структура поля
      TFieldInfo  = record
        // Атрибуты
        sFieldAttr : TStrings; 
          { sFieldName    - Имя поля     }
          { sFieldCaption - Наименование }
          { sFieldDescr   - Описание     }
    
        sFieldType   : TFieldType;
        sFieldSize   : Integer;
        sFieldMBytes : Integer;
      end;

    В этой структуре sFieldType тип поля, sFieldSize – размер поля согласно BDE, а sFieldMBytes – количество байт, занимаемых в памяти данным типом. Остальные поля структуры ясны из комментариев.

Компонент TArmInterface:
В данном случае производится считывание из системных таблиц информации о атрибутах приложения, применяемых для формирования его Главного меню. Эти атрибуты служат исходной информацией для структур TFbSUObject, входящих в компоненту TArmInterface. Поговорим о них чуть подробнее, хотя полный смысл будет ясен немного позже при рассмотрении более или менее функционального программного кода.

Атрибуты АРМ

Каждое рабочее место должно иметь свой набор меню, поэтому необходим механизм создания структуры меню. Чтобы решить эту задачу, поступают следующим образом. В системной базе данных хранится ряд списков, в которых содержатся описания атрибутов, используемых для конструирования рабочих мест, т.е. меню приложения. В этом подходе реализация меню отождествляется с реализацией набора функций или же рабочего места в целом, молчаливо предполагая, что имеется некий банк функций, откуда их можно выбирать для выполнения конкретных задач. Такой банк обязан быть, и он будет определять товарную ценность платформы. Как он создается мы рассмотрим в свое время. Итак, перечислим наши атрибуты для конструирования АРМ.

АРМ, т.е. автоматизированное рабочее место. Этот атрибут носит смысл вывески на фасаде здания, т.к. попросту используется для обозначения рабочего места. В частности, наименование АРМ выводится в заголовки экранных форм.

Окно. Может быть использовано как самостоятельное окно Windows, и в этом случае включение такого окна в меню означает возможность запуска конкретной, программно-реализованной формы. Другое назначение этого атрибута – служить верхним уровнем меню, содержащим список подменю, предназначенных для решения ряда схожих задач, образующих в совокупности требуемый режим работы АРМ Например, в верхнем пункте меню Прием звонка (режим работы) могут быть подменю или подпункты (содержание этого режима): Определение номера телефона, Карточка клиента, История обращений, Запись на очную консультацию.

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

Алгоритм представляет собой атрибут, содержащий список для хранения последовательности функций, которые необходимо выполнить при выборе пункта меню, к которому он прикреплен. Функции алгоритма выполняются строго в той последовательности, как они следуют в его списке. Если в алгоритме нет ни одной функции, то пункт меню, к которому он прикреплен, становится невыполнимым и возникает исключение.

Функция – это последний уровень в иерархии системы управления функциональностью платформы. Она обязательно содержит указатель на программный компонент - форму, процедуру или функцию на языке Object Pascal. Все перечисленные атрибуты хранятся в памяти в специальных схожих по типу структурах. Например, для хранения реквизитов АРМ используется структура

// Структура АРМ
TArm  = record
  sTopInfo  : TTopInfo;
  sOknoPtrL : TList;
end;
где TTopInfo – представляет собой структуру
// Структура универсальной шапки
TTopInfo  = packed record
  sFbSUType : TFbSUType;  // Тип структуры
  sID       : TFbMedID;   // Идентификатор
  sCaption  : TFbMedName; // Наименование
  sDescr    : TFbMedDesc; // Описание
end;

Данная шапка используется во всех структурах, поэтому в ней есть специальное поле sFbSUType, определяющее тип структуры. Тип структуры - перечислимый тип:

// Тип структуры объекта управления
TFbSUType = (apArmType, apOknoType, apMItemType, apAlgorType,
apFuncType, apChannelBox, apNoneType);

Вот теперь можно раскрыть вид структуры TFbSUObject, входящей в компоненту TArmInterface. Она представляет собой вариантную запись:

// Обобщенная структура объекта управления
TFbSUObject = packed record
  FbSUType : TFbSUType;
  case TFbSUType of
    apArmType : (Arm : pTArm);
    apOknoType : (Okno : pTOkno);
    apMItemType : (MItem : pTMItem);
    apAlgorType : (Algor : pTAlgor);
    apFuncType : (Func  : pTFunc);
    apChannelBox : ();
    apNoneType : ();
end;

В этой структуре pTArm, pTOkno, pTMItem и т.д. – ссылки на соответствующие структуры TArm, TOkno, TMItem и т.д.

Компонента TArmInterface содержит набор функций для работы с приведенными атрибутами. Две из них, - для создания и удаления нового атрибута типа TFbSUObject, приведены выше.

Итак, окна, меню, алгоритмы и функции – суть элементы, позволяющие конструировать рабочее место. Атрибут, именуемый функцией, имеет особенность, заключающуюся в том, что его структура содержит поле sFormName, в которое записывается имя прикрепленной формы, запускаемая при активизации структуры, т.е. в момент выборки функции из списка алгоритмов:

// Структура функции
TFunc  = record
  sTopInfo   : TTopInfo;
  sFormName  : TFbMedFormName;
  sAddressPtr: Pointer;
end;

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

То же самое относится к окну, имеющему программную реализацию в виде формы. В отличие от функции, структура окна может не содержать ссылки на форму, тогда она начинает играть роль пункта меню верхнего уровня.

Настройщик, главная после программиста фигура, непосредственно занимающаяся созданием функционально законченных решений, создает столько АРМ, сколько ему нужно иметь различных рабочих мест, снабжая каждое из них наименованием и комментарием, смысл которых соответствует предметной области. Фактически, при этом создаются структуры TArm. Настройщик создает и все необходимые структуры других типов, а также редактирует реквизиты структур функций, чтобы они полностью соответствовали области применения. Затем он формирует дерево управления, в каждый узел которого добавляет один из описанных выше атрибутов, соблюдая принятые соглашения. Это дерево сохраняется либо в системной базе данных, либо в локальных файлах конфигурации. При запуске приложения из дерева управления выбирается нужный корневой узел, т.е. АРМ. Таким образом, корневые узлы дерева управления содержат ссылки на АРМ. Затем специальная система запуска формирует главное меню системы выбранного АРМ, которое и определяет его облик.

Тот, кто дочитал до этого места, вероятно устал от обилия новых понятий и уже пришел к мысли, что описываемая конструкция – одна из многих возможных. Так оно и есть. Согласитесь, - не всегда хочется углубляться в детали программной конструкции, когда непонятна конечная цель, - ведь идея уже изложена. Реализация идеи – личное дело каждого архитектора программы. А посему время от времени будем переводить наше изложение в практическую плоскость.

Начнем мы с последовательности действий настройщика, когда он готовит поставляемый продукт для конкретной задачи заказчика. Затем мы дадим описание ключевых программных решений, так как к этому времени у читателя возникнет ряд каверзных вопросов. Получать ответы на них уже будет не так скучно.

Что делает настройщик

Допустим, что у настройщика установлена чистая платформа, т.е. нет ни одной таблицы пользовательской базы данных, более того, на SQL-сервере нет даже системной базы данных. Производится первый запуск приложения платформы. Опустим операции, связанные с подключением к серверу баз данных, т.к. они сейчас не представляют интереса. Исходное состояние системы после загрузки приложения описывается так:

  • На MS SQL-сервере создана база данных DbExample,
  • В BDE создан псевдоним alDbExample для этой базы данных,
  • В базе данных DbExample нет ни одной системной таблицы и ни одной пользовательской таблицы.
  • В памяти созданы все необходимые списки. Пока мы знакомы со списком таблиц FTablesList и списком имен таблиц FTableNames компоненты TDbInterface, а также с обобщенным списком FFbSUObjectL элементов СУ компоненты TArmInterface. На самом деле создается значительно большее число списков. Однако нам важно пояснить принцип работы платформы, поэтому обойдемся необходимым минимумом. Более менее полный список рабочих списков, используемых в платформе, можно найти в прилагаемом учебном приложении.
Так как пользовательская база данных пуста, то список таблиц и список их имен пока не содержат ни одного элемента.

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

На рис.1 показано, как выглядит один из ключевых моментов запуска штатной системы.


рис. 1

В этих условиях существует лишь одна возможность продвинуться вперед – продолжить загрузку в режиме конфигуратора, установив флаг Конфигуратор. Как видите, у системы нет даже имени. Сразу после входа в конфигуратор настройщик устанавливает ряд параметров, в том числе название системы.

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

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

Создание таблицы

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

К этому добавлю, что для желающих автор всегда готов рассказать о штатной платформе. Итак, в памяти создается структура TTableInfo, содержащая атрибуты, вводимые через специальный диалог. Затем создается набор структур для полей таблицы. Для выполнения этой работы нужно разработать по крайней мере два диалога, - один для заполнения структуры TTableInfo, а другой для заполнения структуры TFieldInfo и ее добавления в структуру TTableInfo. Упрощенные реализации этих диалогов имеют вид, приведенный в листингах L1 и L2.

Листинг L1. Модуль диалога для создания структуры таблицы.
DFM-файл для модуля, приведенного в листинге L1.

Рассмотрим некоторые детали этого диалога, относящиеся к общей идеологии платформы, о которых пока не было ничего сказано. Это касается свойства ppTInfoCategory, обеспечивающего доступ к полю FpTInfoCategory : pTInfoCategory, содержащему ссылку на структуру

  // Структура категории информации
  TInfoCategory = record
    sInfoID     : TFbMedID;
    sTFbDbType  : TFbDbType;
    sEnumName,
    sInfoName   : TFbMedName;
    sInfoDescr  : TFbMedDesc;
    sCount      : Integer;
    sPrefix     : String[5];
    sMainPrefix : String[3];
    sSubPrefix  : String[2];
    sStatusID   : Byte;
  end;

Эта структура введена в связи с тем, что вся пользовательская информация в рассматриваемой платформе разбита на ряд категорий, чтобы легче было оперировать множеством таблиц. В частности, в медицинской тематике используются такие категории информации как Справочники, Клиенты, Персонал и др. Информация о том, что конкретная таблица относится к той или иной категории, содержится в специальном префиксе имени таблицы (поле sPrefix структуры TInfoCategory). Кроме того используется префикс для всех таблиц платформы, который позволяет отличить таблицы на сервере, с которыми оперирует платформа, от других типов таблиц, в частности, системных таблиц. Рассмотрим конкретный пример таблицы с именем Fb_c_pclients. Префикс ‘Fb_’ указывает на то, что данная таблица относится к пользовательской базе данных платформы. Далее идет префикс ‘c_’, который указывает на то, что данная таблица относится к категории Клиенты. Как видим, префиксы отделяются друг от друга и от имени таблицы знаками подчеркивания. В приведенной структуре для нашего примера могут быть, например, такие значения:

    sInfoID     = ‘001111’,
    sTFbDbType  = icClient,
    sEnumName   = ‘icClient’,
    sInfoName   = ‘InfoName_001111’,
    sInfoDescr  = ‘Клиенты’,
    sPrefix     = ‘Fb_c’,
    sMainPrefix = ‘Fb_’,
    sSubPrefix  = ‘c_’.

Поля sCount и sStatusID имеют специальное назначение, выходящее за пределы обсуждения, и пока практически в платформе не используются. Для работы с категориями введены следующие конструкции:

специальный перечислимый тип

  // Тип категорий информации 
  TFbDbType  = (
    icOther,     // Прочие
    icSpr,       // Справочники
    icClient,    // Клиенты
    icStaff,     // Персонал
    icStatist,   // Статистика
    icJournal,   // Журналы
    icAll,       // Все категории
    icNoCateg);  // Без категории,

вспомогательные типы для хранения описаний категорий информации

  // Тип паспорта категории информации
  TFbDbTypeItem  = Array [1..2] of String;
  // Тип массива паспортов категорий информации
  TFbDbTypeArray = Array [TFbDbType] of TfbDbTypeItem, 

и константа-массив префиксов для выбранных категорий информации

  apDbTypeArray : TFbDbTypeArray =
    ( { Категория информации и префиксы имен таблиц }
      { <Категория>,        <Префикс> }
      ('Прочие',            'Fb_o_'),
      ('Справочники',       'Fb_s_'),
      ('Клиенты',           'Fb_c_'),
      ('Персонал',          'Fb_m_'),
      ('Статистика',        'Fb_r_'),
      ('Журналы',           'Fb_j_'),
      ('Виртуальная БД',    'Fb_v_'),
      ('Все категории',     'Fb___'),
      ('Без категории',     'Fb_'  )
    )

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

Вернемся к нашему диалогу формирования структуры таблицы. Суть его работы сводится к формированию специальной буферной структуры таблицы FN_pTTableInfo для текущего экземпляра интерфейса к базам данных (по клику на кнопке ОК) в методе FDbInterface.Init_NpTTableInfo и переходу в диалог работы со списком полей TbDefFr. Если при выходе из диалога TbDefFr пользователь откажется от создания таблицы, то буферная структура будет удалена из памяти. Если же процесс создания таблицы будет завершен успешно, то судьба буферной структуры поля окончательно будет решена после выхода из диалога TbDefFr. Сама буферная структура FN_pTTableInfo является принадлежностью интерфейса к базам данных FDbInterface.

Ниже приводится программный код для диалога создания структуры поля.

Листинг L2. Модуль диалога для создания структуры поля.
DFM-файл для модуля, приведенного в листинге L2.

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

Типы данных

Некоторые задачи обеспечения целостности базы данных и адаптации ее к предметной области оказались легко решаемыми посредством ввода специальных структур для хранения в памяти информации о типах данных, причем в них содержатся не только традиционные сведения, такие, как тип поля, ее размер и т.д., но и дополнительная информация, вводимая для повышения удобства работы с ними настройщику и пользователю. К такой информации относится, прежде всего, смысловое наименование типа, понятное для пользователя, не обладающего специальными знаниями в области программирования. Так, например, программисту все ясно, когда он видит обозначение типа как ftInteger, а для пользователя лучше, когда он увидит в качестве обозначения типа фразу «Целое число». Для ряда задач оказалось удобным создать специальный набор структур, предназначенных для хранения информации о типах полей, используемых для связей между таблицами, а также типов полей для хранения данных, выбираемых пользователем из собственных списков, по существу являющихся справочниками небольшого объема. В таких структурах дополнительной информацией являются смысловые названия полей и списков. В связи с этим были введены так называемые группы данных, каждая из которых представляет собой определенный набор типов данных, имеющих общий признак, т.е. описываемых структурой одного и того же типа. Обратим внимание, что фактически все поля, с которыми манипулирует наша платформа, имеют типы, предусмотренные в BDE, и здесь ничего нового нет. Новое понятие групп данных связано с характером той дополнительной информации, которая сопровождает параметры типа поля в указанных структурах. Если эта информация сводится просто к названию типа и нет других особенностей, связанных с полем, имеющим этот тип (например, просто поле имеет тип «Целое число»), то мы будем такие поля относить к базовой группе данных, а во всех остальных случаях – к той или иной группе данных, в зависимости от характера дополнительной информации, которая важна с точки зрения назначения системы. В этих случаях уже неважно, как называется тип по канонам BDE, - эта информация в структуру даже не вводится, так как дополнительная информация, вводимая в структуру, косвенно содержит описание типа. Так, если поле предназначено для хранения информации из списков, то косвенно это означает, что тип поля ftString. Важно еще подчеркнуть, что групп данных априори может быть сколько угодно. В описываемой платформе пока таких групп реализовано четыре.

Базовая группа данных
Прежде всего, разработчику следует решить, какие типы данных, реализуемые BDE, он собирается использовать в своей платформе. Вряд ли целесообразно организовывать поддержку почти сорока типов, перечисленных в BDE. Следует поддерживать часто используемые типы, такие как ftString, ftInteger, ftFloat, ftDate, ftTime, ftDateTime. Для автоинкрементных полей понадобится тип ftAutoInc. Для работы с текстами, графикой и вообще с бинарными данными нужны ftBlob, ftMemo, и, возможно, ftGraphic. Дальнейшее расширение базовой группы скорее носит узкоспециализированный характер.
Чтобы настройщик и пользователь могли свободно манипулировать выбранным подмножеством типов данных, целесообразно ввести структуру:

// Структура базового типа данных
TFbBaseType  = record
  sType: TFieldType;    // идентификация типа BDE
  sBytes,               // количество байтов под данный тип
  sSize,                // размер типа, аналог из BDE
  sInc: Integer;        // признак включения типа в платформу
  sDescr: ShortString;  // краткое описание типа
end;

Она служит хранилищем сведений о типе поля. Поля sBytes и sSize приведенной структуры содержат соответственно число байтов, занимаемых данным типом в памяти и стандартное значение размера типа по соглашениям BDE. Ввод в структуру поля sBytes обусловлен желанием явно указать размер отводимой памяти в тех случаях, когда в BDE параметр Size равен 0. Для строковых и аналогичных ему типов этот параметр конкретизируется в момент формирования поля базы данных. Создав массив базовых типов
TFbFieldArray = Array[TFieldType] of TFbBaseType,
а затем для тех ее членов, в которых поле sInc=1, заполнив поля sDescr, можно создать необходимые списки для работы с типами данных в конфигураторе. Если есть желание расширить или, наоборот, сузить реализованный список используемых типов базовой группы данных, достаточно отредактировать поля sInc и sDescr. Если sInc=0 (по умолчанию), то данный тип исключается из системы, причем при этом можно не очищать поле sDescr. И наоборот, если нужно включить данный тип в систему, то нужно установить sInc=1, и, если есть необходимость, отредактировать поле sDescr.

Ссылочная группа данных
Ссылочная группа данных связана с требованием обязательности автоинкрементного поля в каждой таблице базы данных. Под ссылкой на таблицу можно иметь в виду поле целого типа, в котором хранится содержимое автоинкрементного поля той таблицы, на которую создается ссылка (ведущей таблицы). Для работы с ссылочными типами данных введена структура:

// Структура ссылочного типа данных
TFbReferenceType = record
  sType: TFieldType;
  sBytes,
  sSize,
  sInc: Integer;
  sDescr: ShortString;
  spTableInfo: pTTableInfo;
end;

Она отличается от ранее введенной структуры для базовой группы наличием поля spTableInfo, куда заносится ссылка на уже существующую структуру таблицы. Остальные поля этой структуры копируют информацию из структур для автоинкрементного поля, кроме sDescr и sType. В поле sDescr вписывается наименование ведущей таблицы, например, Диагнозы. Для ссылочной группы данных sType = ftInteger. Идентификаторы полей для ссылок унифицируем, назвав их T1_id, T2_id и T3_id и т.д., в какой бы новой таблице они не заводились, запретив тем самым их редактирование настройщикам и пользователям.

Следящая группа данных
Следящая группа данных особых пояснений не требует, т.к. принцип работы с ней в целом аналогичен принципу работы со ссылочной группой. Особенность состоит в том, что в структуру управления, помимо ранее рассмотренного указателя на структуру таблицы, вводится указатель на структуру того поля, на которое создается ссылка (ведущее поле), а тип sType устанавливается равным типу ведущего поля:

// Структура следящего типа данных
TFbLookupType  = record
  sType: TFieldType;
  sBytes,
  sSize,
  sInc: Integer;
  sDescr: ShortString;
  spFieldInfo: pTFieldInfo;
  spTableInfo: pTTableInfo;
end;

Приведенные три группы данных исчерпывают основные варианты. Но оказалось, что есть возможности по использованию механизма управления типами для создания полей, значения которых выбираются из определенных пользователем списков. Так появляется еще одна группа данных: списочная.

Списочная группа данных
Простейший список может состоять из значений Да, Нет. Списков аналогичного свойства, содержащих от двух до десятка и более членов, из которых пользователь может выбирать значение, любая предметная область содержит во множестве. Так, очень удобно реализовывать такими списками справочники небольшого объема. Примером может быть справочник частей света из 6 членов: Европа, Азия, Америка, Африка, Австралия и Антарктида. Другой пример - набор специальностей медицинского персонала, и т.д. Структура для работы со списочными типами, по аналогии с остальными, имеет вид:

// Структура списочного типа данных
TFbPickType = record
  sType: TFieldType;
  sBytes,
  sSize,
  sInc: Integer;
  sDescr: ShortString;
  sPickList: TStrings;
end;

Как видно, ключевым полем в этой структуре является список значений sPickList, из которого пользователь выбирает нужные ему значения во время работы приложения. Работа с базой данных таких "справочников", фактически начинающаяся со стадии конфигурирования, позволяет повысить гибкость настройки системы под конкретные требования функциональных задач. Разумеется, для создания списка и управления им придется создать менеджер списочных типов. Он должен обеспечивать создание списка, добавление в список нового члена, редактирование и удаление членов списка. Созданный список сохраняется в системной базе данных.

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

TFbTypeGroup = (FldGroup, RefGroup, PicGroup, LUpGroup, NoGroup)
соответственно идентифицирующий базовую, ссылочную, списочную и следящую группы. Для полноты в него введен тип NoGroup, не содержащий никакой группы.

Получаем структуру обобщенного (комбинированного) типа данных в виде вариантной записи:

// Структура комбинированного типа данных
TFbCommonType  = packed record
  FbTypeGroup: TFbTypeGroup;
  case TFbTypeGroup of
    FldGroup: (FbFld: pTFbBaseType);
    RefGroup: (FbRef: pTFbReferenceType);
    PicGroup: (FbPic: pTFbPickType);
    LUpGroup: (FbLUp: pTFbLookupType);
    NoGroup: ();
end;

В ней pTFbBaseType, pTFbReferenceType, pTFbPickType и pTFbLookupType - суть ссылки на структуры TFbBaseType, TFbReferenceType, TFbPickType и TFbLookupType соответственно. Итак, мы получили открытый механизм управления группами данных в конструкторе, который позволяет вводить в систему новые группы данных. Разумеется ничто не дается даром, - каждая новая группа данных потребует собственного менеджера, который бы обеспечивал интерфейс для работы с ним в конфигураторе системы. Кроме того, необходим способ связи конкретных структур полей и только что введенных структур групп данных для использования в конструкторе, а также во всех библиотечных функциях и процедурах, обрабатывающих структуры полей в пользовательском режиме.

Модернизируем структуру поля, приведенную выше (см. раздел "Архитектура платформы"):

// Структура поля
TFieldInfo  = record
  sFieldAttr: TStrings; 
    // атрибуты поля:
    { sFieldName    - Имя поля            }
    { sMTableName   - Имя ведущей таблицы }
    { sMFieldName   - Имя ведущего поля   }
    { sPicDescr     - Имя списочного типа }
    { sFieldCaption - Наименование        }
    { sFieldDescr   - Описание            }

  sFieldType: TFieldType;
  sFieldSize: Integer;
  sFieldMBytes: Integer;

  sMTTableInfo : pTTableInfo; // Ссылка на структуру главной таблицы 
  sMTFieldInfo : pTFieldInfo; // Ссылка на структуру главного поля
  sPickList    : TStrings;    // Список списочного типа
end;

Поясним смысл новых полей в структуре:
  • sMTableName - имя ведущей таблицы, на которую задается ссылка;
  • sMFieldName - имя ведущего поля, на которое задается ссылка;
  • sPicDescr - строковое наименование списка значений.
  • sMTTableInfo, sMTFieldInfo, sPickList - реальные ссылки на вудущую таблицу, ведущее поле и список значений поля соответственно, которые вводятся после загрузки приложения.
Правила использования данных полей таковы.
  • Если поле базы данных принадлежит к ссылочной группе, то sMTableName содержит наименование ведущей таблицы (sMTTableInfo <> nil). В этом случае структура поля не должна содержать атрибутов sMFieldName и sPicDescr (sMTFieldInfo = nil, sPickList = nil), а ее имя должно быть sMTableName_id.
  • Если поле базы данных принадлежит к следящей группе, то должны быть заданы оба атрибута, как sMTableName, так и sMFieldName (sMTTableInfo <> nil, sMTFieldInfo <> nil), а имя поля должно быть sMTableName_sMFieldName. Структура такого поля не должна содержать атрибута sPicDescr (sPickList = nil).
  • Если поле принадлежит к списочной группе, то должны отсутствовать атрибуты sMTableName и sMFieldName (sMTTableInfo = nil, sMTFieldInfo = nil), но должно быть задано имя списка sPicDescr (sPickList <> nil). Соответственно, все списки в системе должны быть с уникальными именами.
  • Наконец, в структуре поля базовой группы не должно быть ни одного из атрибутов sMTableName, sMFieldName и sPicDescr (sMTTableInfo = nil, sMTFieldInfo = nil, sPickList = nil).

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

После такого утомительного экскурса в дебри построения платформы, вернемся к листингу L2, чтобы пояснить как работает диалог с полями-переменными
    FpTFbCommonType : pTFbCommonType;
    FTFbTypeGroup   : TfbTypeGroup.

В момент передачи диалогу ссылки на интерфейс к базам данных в обработчике Set_FDbInterface производится заполнение списка групп данных TypeGroupCmBox.

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

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

При выходе из диалога по кнопке ОК выполняется метод Execute, в котором задаются атрибуты буферной структуры поля FDbInterface.N_pTFieldInfo, созданной до входа в данный дилог.

В заключение раздела, касающегося создания таблицы, приведем листинг диалога, в котором производится формирование списка полей для таблицы. Особенность данного диалога состоит в том, что при входе в него автоматически создается начальная структура поля для автоинкрементного поля.

Листинг L3. Модуль диалога формирования списка полей.
DFM-файл для модуля, приведенного в листинге L3.

Первые шаги в построении платформы

Итак, мы имеем некоторый задел, чтобы решить самую первую задачу настройщика - создать таблицу клиентов. Однако, прежде чем создавать таблицы, научимся сначала загружать системную базу данных и просматривать состав пользовательской базы данных, для чего создадим несложную форму для отображения информации о пользовательской базе данных. Когда я попробовал изложить идею задач загрузки и сохранения информации при работе в режиме конфигуратора, делая "вырезки" из существующей платформы, то после целого дня работы убедился, что это невозможно, - слишком велик программный код, и читатель сразу бы запутался. А если бы я попробовал при этом еще все объяснить, то никогда бы эту статью не закончил. Поэтому пришлось избрать путь создания небольших приложений сугубо для учебных целей. Первое из них мы назовем конфигуратором.

Наш конфигуратор будет делать очень простую работу: загружать урезанную информацию о таблицах пользовательской базы данных и давать возможность просматривать структуру таблиц. Тем самым не будем пока решать довольно непростую задачу обеспечения целостности базы данных, возникающую при ее реконструкциях. Тем не менее, позже мы "научим" простой конфигуратор создавать поля и таблицы, и, если хватит духу, то проводить модификацию, т.е. реконструкцию базы данных. Последнее, впрочем не обязательно делать, т.к. читатель это сам легко сможет реализовать, если поймет, как делаются первые две задачи. Далее перейдем к составлению запросов, сохраняемых в системной базе данных, что завершит основной цикл функциональности платформы.

Работоспособный проект для Delphi 7 приведен в архиве DPlatform.zip. В этом архиве, в папке DbBackup расположен архив базы данных для MSSQL Server 2000. Для запуска приложения нужно этот архив базы данных развернуть на доступном MS SQL Server 2000 и затем на главной форме приложения (файлы F_Configurator.dfm и F_Configurator.pas) компоненту Database подключить к этой базе данных, создав соответсвующий псевдоним BDE. Детали этого процесса пояснять ну буду, скажу лишь, что база данных создана на SQL Server 2000 с кодировкой 1251 и сортировкой, чувствительной к регистру, что очень важно иметь ввиду при установке базы данных. Ясно, что ваш сервер баз данных должен позволять восстанавливать базу данных из приведенного архива, т.е. иметь соответствующие кодировку и сортировку. Поясним, как работает наш конфигуратор, главная форма которого называется ConfiguratorFr.

После запуска приложение ничего не делает: оно ждет нажатия кнопки DbInterface, после чего происходят главные события, показанные в листинге 4.

Листинг 4. Создание экземпляра TDbInterface и загрузка информации в память.

procedure TConfiguratorFr.Button2Click(Sender: TObject);
Var
  k, i : Integer;
  wTabSheet : TTabSheet;
  wListBox  : TListBox;
  wpTTableInfo : pTTableInfo;
  wpTInfoCategory : pTInfoCategory;
  wTFbTypeGroup   : TFbTypeGroup;
begin
  // Создать объект FDbInterface
  FDbInterface := TDbInterface.Create(nil);
  // Загрузить информацию из системной БД
  FDbInterface.DatabaseName := Database.DatabaseName;

  // Список категорий информации
  TbDbTypeComboBox.Items.Clear;
  TbDbTypeComboBox.Items.Assign(FDbInterface.FbDbTypeList);
  TbDbTypeComboBox.Sorted := True;

  // Настройка списка групп данных
  TypeGroupCmBox.Items.Clear;
  for wTFbTypeGroup := Low(TFbTypeGroup) to High(TFbTypeGroup) do
    TypeGroupCmBox.Items.AddObject(apTypeGroupNames[wTFbTypeGroup],
      TObject(wTFbTypeGroup));

  // Показать состав загруженной информации
  for k:=0 to FDbInterface.InfoCategoryList.Count-1 do
    begin
      wpTInfoCategory := pTInfoCategory(FDbInterface.InfoCategoryList[k]);

      if wpTInfoCategory.sTFbDbType in [icAll, icNoCateg, icVirtual] then
        Continue;

      wTabSheet := TTabSheet.Create(FPageControl);
      wTabSheet.PageControl := FPageControl;
      wTabSheet.Caption := wpTInfoCategory.sInfoDescr;
      // Запомним ссылку для последующего использования
      wTabSheet.Tag := Integer(wpTInfoCategory);

      wListBox  := TListBox.Create(Self);
      wListBox.Parent  := wTabSheet;
      wListBox.Align   := alClient;
      wListBox.OnClick := ListBoxClick;

      for i:=0 to FDbInterface.TablesList.Count-1 do
        begin
          wpTTableInfo := pTTableInfo(FDbInterface.TablesList[i]);
          if wpTTableInfo.spTInfoCategory <> wpTInfoCategory then
            Continue;
          wListBox.Items.AddObject(wpTTableInfo.sTableAttr.Values['sTableCaption'],
            TObject(wpTTableInfo));
        end;
    end;
  Button2.Enabled := FDbInterface = nil;
  Button3.Enabled := FDbInterface <> nil;
end;

Рассмотрим процессы, происходящие при этом, подробнее.
Сначала создается объект FDbInterface
FDbInterface := TDbInterface.Create(nil).

В конструкторе этого объекта процедура CreateFbObjects обеспечивает создание всех необходимых списков, а также выполняется инициализация типов данных. Инициализация базовых типов проводится функцией Init_TFbFieldArray, которая заполняет массив FFbFieldArray информацией в соответствии с тем, как разработчик установил список поддерживаемых типов. Сначала производится стандартное заполнение информации в каждом элементе массива FFbFieldArray:

    for wTFieldType := Low(TFieldType) to High(TFieldType) do
      begin
        FFbFieldArray[wTFieldType].sType  := wTFieldType;
        FFbFieldArray[wTFieldType].sSize  := 0;
        FFbFieldArray[wTFieldType].sBytes := capAllTypes[wTFieldType].sBytes;
        FFbFieldArray[wTFieldType].sInc   := 0;
        FFbFieldArray[wTFieldType].sDescr := capAllTypes[wTFieldType].sDescr;

        // Эти типы включены в систему
        if wTFieldType in [ftAutoInc, ftString, ftMemo, ftBlob,
        ftInteger, ftFloat, ftDateTime, ftUnknown] then
          FFbFieldArray[wTFieldType].sInc := 1;

        // ..а для этих типов - особые условия нужны
        // apDATE_TIME - признак разделения данных типа ДАТА и ВРЕМЯ
        with FFbFieldArray[wTFieldType] do
          case wTFieldType of
            ftDate :
              begin
                if apDATE_TIME then
                  begin
                    sInc   := 1;
                    sDescr := 'Дата';
                  end;
                sBytes := SizeOf(TDateTime);
              end;

            ftTime :
              begin
                if apDATE_TIME then
                  begin
                    sInc   := 1;
                    sDescr := 'Время';
                  end;
                sBytes := SizeOf(TDateTime);
              end;
          end;
      end;

В приведенном цикле видно, что платформа поддерживает список следующих типов ftAutoInc, ftString, ftMemo, ftBlob, ftInteger, ftFloat, ftDateTime, ftDate, ftTime, ftUnknown.

Кроме того, платформа обеспечивает поддержку раздельного учета типов ftDateTime, ftDate, ftTime непосредственно в приложении, т.к. MS SQL Server такое разделение не поддерживает. Применять или нет разделение этих типов, - определяется глобальной переменной булевского типа apDATE_TIME. При желании этот параметр может быть включен в число настроек, что и сделано в штатной версии описываемой платформы. После инициализации базовых типов формируется список FFbFldGroupList, содержащий ссылки на структуры TFbCommonType, причем эти структуры создаются только для тех типов базовой группы данных, которые реально поддерживает платформа, для чего анализируется поле sInc конкретного элемента массива FFbFieldArray. Эта работа выполняется функцией Init_FbFldGroupList:

for wTFieldType := Low(TFieldType) to High(TFieldType) do
  begin
    if FFbFieldArray[wTFieldType].sInc <> 1 then
      Continue;
    New(wpTFbCommonType);
    New(wpTFbBaseType);
    wpTFbCommonType.FbTypeGroup := FldGroup;
    wpTFbBaseType^              := FFbFieldArray[wTFieldType];
    wpTFbCommonType.FbFld       := wpTFbBaseType;
    FFbFldGroupList.AddObject(wpTFbCommonType.FbFld.sDescr, TObject(wpTFbCommonType));
  end;

Обратите внимание, что к этому моменту интерфейс к базам данных FDbInterface еще не подключился к серверу, и, следовательно, могут быть созданы обобщенные структуры TFbCommonType только для базовой группы данных. В платформе для этой группы данных используется отдельный список FFbFldGroupList, хотя его наличие и не является обязательным. Именно этот список заполняется к описываемой стадии работы приложения платформы. Список обобщенных структур FFbCommonTypeList будет заполнен уже после загрузки информации из системной базы данных.

Вернемся, однако, к работе обработчика TConfiguratorFr.Button2Click.

Следующим шагом является подключение интерфейса FDbInterface к серверу базы данных, что выполняется программным кодом:
  // Загрузить информацию из системной БД
  FDbInterface.DatabaseName := Database.DatabaseName
При этом срабатывает внутренняя процедура компоненты TDbInterface

Procedure TDbInterface.Set_DatabaseName(Value : String), где через параметр Value передается имя псевдонима базы данных приложения. В этой процедуре сначала производится загрузка в память информации из системной базы данных процедурой LoadSystemDatabaseInfo, а затем завершается процесс инициализации всех типов системы последовательным выполнением процедур Get_PickTypes_From_Database, Init_FbRefGroupList, Init_FbLUpGroupList.

Обратите внимание, что во всех этих процедурах используется обращение к процедуре Update_FbCommonTypeList, реализующей обновление списка FFbCommonTypeList комбинированных типов. Надо признать, что процедуру Update_FbCommonTypeList следовало бы использовать только один раз, после завершения формирования всех частных списков для отдельных групп данных. Но так сделано для того, чтобы обеспечить целостность списков комбинированных типов при манипуляции со структурой пользовательской базы данных в конфигураторе. Вероятно, есть более изящное решение этой задачи, которое могут использовать те читатели. Описание работы процедуры LoadSystemDatabaseInfo мы пока отложим, а работа остальных процедур (Get_PickTypes_From_Database, Init_FbRefGroupList, Init_FbLUpGroupList) очень проста.

  • В процедуре Get_PickTypes_From_Database производится чтение информации из системной таблицы T_PickTypes и формирование списка FFbPicGroupList, содержащего ссылки на структуры списочного типа.
  • В процедуре Init_FbRefGroupList создается список ссылок на структуры ссылочных типов FFbRefGroupList просматривая список структур таблиц FTablesList.
  • В процедуре Init_FbLUpGroupList создается список ссылок на структуры следящих типов FFbLUpGroupList, просматривая список структур полей для всех элементов списка структур таблиц FTablesList. Как уже было замечено, процедура Update_FbCommonTypeList обеспечивает формирование списка обобщенных структур. Как может заметить внимательный читатель, тут налицо избыточность списков для частных групп данных и списка FFbCommonTypeList, хотя затраты ресурсов памяти для этого несущественны. Таких неоптимальных решений в описываемой платформе будет встречаться довольно много, за что просил бы не ругать автора, т.к. процесс программирования платформы протекал при крайне жестких сроках, что называется «с листа», и не было времени заранее обдумать решения.
Продолжим рассмотрение обработчика TConfiguratorFr.Button2Click.

Так как на главной форме нашего конфигуратора предусмотрено отображение информации из выбранных структур таблицы и поля, то производится заполнение следующих списков: выпадающего списка категорий информации TbDbTypeComboBox, заполняется на основании списка FDbInterface.FbDbTypeList, выпадающего списка групп данных TypeGroupCmBox, заполняется на основании списка apTypeGroupNames, имеющего в приложении как массив-константа.

Затем производится создание страниц объекта FPageControl. Их количество определяется количеством категорий информации платформы. В данном случае плафторма создает 6 страниц, согласно списку TFbDbType, причем для категорий icVirtual, icAll и icNoCateg страницы не создаются. На каждой странице размещается объект TListBox, в который заносится список названий таблиц соответствующей категории информации, причем в TListBox запоминаются также ссылки на соответствующие структуры TTableInfo, созданные при загрузке приложения.

Итак, наш конфигуратор готов к работе.

Рассмотрим, как он выполняет типовые операции, для которого создавался.

Создание таблицы пользовательской базы данных

Для этого служит операция по кнопке NewT.

Работа платформы состоит в следующем, определяется текущая категория информации, которая занесена в свойство Tag в виде числового выражения ссылки на структуру категории информации TInfoCategory:

  wTabSheet := FPageControl.ActivePage;
  wpTInfoCategory := pTInfoCategory(Pointer(wTabSheet.Tag));
В глобальные переменные заносятся соответствующие сведения:
  apDbType  := wpTInfoCategory.sTFbDbType;
  apDbTypeS := wpTInfoCategory.sEnumName;
Создается диалог формирования структуры таблицы и в него передается информация о категории информации, для которой нужно создать таблицу:
  if TbDlgFr = nil then
    TbDlgFr := TTbDlgFr.Create(nil);
  try
    TbDlgFr.DbInterface     := FDbInterface;
    TbDlgFr.ppTInfoCategory := wpTInfoCategory;
    TbDlgFr.ShowModal;
  finally
    FreeAndNil(TbDlgFr);
  end;

Если после выхода из этого диалога имеется ненулевая ссылка на буферную структуру FDbInterface.N_pTTableInfo, то производится как обновление отображения главной формы, так и обновление системной базы данных в процедуре Update_Server(). Разумеется, на SQL сервере создается соответствующая таблица пользователя.

Суть обновления системной базы данных состоит в том, что в таблицу T_Tables заносятся сведения о вновь созданной таблице, а в таблицу T_Fields – информация о ее полях.

Создание поля в таблице

Создание поля может выполняться двумя путями: по ходу создания новой таблицы из диалога формирования списка полей TTbDefFr по кнопке Новое поле или же непосредственно из главной формы нашей платформы по кнопке NewF. В обоих случаях создается диалог формирования новой структуры поля FldDlgFr с тем отличием, что в первом случае работа происходит с буферной структурой таблицы FDbInterface.N_pTTableInfo, а во втором – с текущей структурой таблицы FpTTableInfo главной формы конфигуратора. В обоих случаях работа начинается с создания буферной структуры поля

    FDbInterface.Init_NpTFieldInfo;

После выхода из диалога FldDlgFr ход действий несколько отличается, но суть их одна и та же. В первом случае сначала идет «набивка структуры таблицы» FDbInterface.N_pTTableInfo списком структур полей и обновление информации на сервере производится в один прием, т.е. одновременно создается как таблица, так и поля в ней. Во втором случае эта операция выполняется для одиночной структуры поля, добавляемой в структуру таблицы, что требует лишь обновления структуры таблицы на сервере.

Редактирование реквизитов таблицы

Эта операция производит в обработчике TConfiguratorFr.SpeedButton4Click главной формы конфигуратора. Действия протекают по стандартной схеме: сначала запоминаются текущие реквизиты структуры таблицы, а затем производит их корректировка с учетом информации, введенной пользователем. Допускается изменение имени таблицы, категории информации, к которой отнесена таблица (что эквивалентно изменению ее имени), а также пользовательских комментариев к ней.

Редактирование реквизитов поля

Эта операция производится в обработчике TConfiguratorFr.SpeedButton5Click главной формы конфигуратора. Так же, как в случае с таблицей, сначала запоминаются текущие реквизиты поля, а затем производится обновление информации в памяти и в системной базе данных. Допускается изменение имени поля, группы данных, типа и размера поля.

Удаление пользовательской таблицы

Реализуется по кнопке DeleteT главной формы конфигуратора. Сначала определяется ссылка на выбранную структуру таблицы через свойство Tag активной страницы объекта FPageControl, а затем вся работа выполняется в процедуре Delete_Table, в которую передается ссылка на структуру удаляемой таблицы.

В этой процедуре сначала удаляется информация о таблице из системных таблиц T_Tables и T_Fields с помощью соответствующих SQL-запросов. Затем удаляется из памяти структура поля вызовом специально для этого предназначенного метода интерфейса к базам данных:

FDbInterface.Dispose_pTTableInfo(ApTTableInfo, True, True);

Здесь второй параметр указывает на необходимость удаления списка структур полей, а третий – на необходимость обновления информации о типах. Последнее действие необходимо в связи с тем, что при удалении любой таблицы происходит удаление одной записи в списке ссылочной группы данных и удаление из списка следящей группы данных стольких записей, сколько было полей в удаляемой таблице, не считая автоинкрементного поля.

Удаление поля в пользовательской таблице

Реализуется по кнопке DeleteF главной формы конфигуратора. В данном случае поступают по следующей схеме. Сначала из системной таблицы удаляется информация о выбранном поле в процедуре

RemoveFrom_T_Fields(FDbInterface, FpTFieldInfo);

Затем удаляется информация из списка FieldsLBox на главной форме и, наконец, пользуясь методом

FDbInterface.DeleteField(FpTTableInfo.sTableAttr.Values['sTableName'],
    FpTFieldInfo.sFieldAttr.Values['sFieldName'])
удаляют структуру поля в памяти и обновляют структуру таблицы на сервере базы данных. В заключение производится обновление списков типов данных
FDbInterface.Update_FbCommonTypeList.

(Продолжение следует)



К материалу прилагаются файлы:


Смотрите также материалы по темам:
[Моделирование БД] [Постановка задачи]

 Обсуждение материала [ 18-04-2005 15:33 ] 14 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования