(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Bold for Delphi. Часть 2

Источник: RSDN Magazine #5-2003

Введение
Мы взлетаем
Сохранение структуры модели. Внутренняя структура базы данных описания модели
Генерация БД по модели Bold
Использование XML для хранения данных
Использование middleware-сервера
Механизм подписок (Subscriptions)
Язык Object Constraint Language
Некоторые особенности построения пользовательского интерфейса средствами Bold
Рендереры (Renderers)

1-я часть статьи

Введение

В первой части статьи мы рассмотрели простейшие примеры работы с Bold. В данной статье мы продолжим знакомство с Bold и рассмотрим более сложные примеры.

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

Мы взлетаем

В качестве примера рассмотрим приложение для оформления заказов на какие-либо товары. Ниже (см. рисунок 1) приведена диаграмма классов, описывающая данную задачу.


Рисунок 1.

Теперь дадим краткие пояснения к диаграмме классов:

Заказчик описывается классом Customer, который содержит поля FirstName, MiddleName и LastName (имя, фамилия и отчество заказчика).

Описание заказа отражено в классе Orders. OrderNo - номер заказа, CreateDate - дата приема заказа.

Позиции заказа описаны классом OrderPositions. ItemCount - количество единиц товара в позиции, PositionNo - порядковый номер позиции, Price - цена одной единицы товара.

Справочник номенклатуры товаров - это класс Items. Он имеет лишь одно поле Name - наименование товара.

Конечно, модель далека от совершенства и для решения реальных задач потребуются более сложные модели, но и эта модель позволит нам рассмотреть ключевые моменты работы с Bold, не отвлекаясь на излишние сложности.

Сохранение структуры модели. Внутренняя структура базы данных описания модели

В первой части статьи в качестве хранилища данных модели была выбрана СУБД Interbase. Теперь мы рассмотрим особенности использования других СУБД и типов хранилищ данных.

Как мы уже знаем, диаграмма классов через соответствующую связь (Link) импортируется во внутренний формат Bold и отображается в редакторе компонентов BoldModel. Как и в прошлый раз, диаграмма классов была создана в Model Maker. Никаких особенностей по сравнению с разобранным ранее примером нет, и поэтому мы не будем подробно останавливаться на процессе импорта модели.

За сохранение модели в БД отвечает компонент TPersistenceHandleDB (что можно перевести как «диспетчер сохранения» - persistence handler). Поскольку дословный перевод на русский язык термина «persistence handler» не отображает полностью его назначения, мы будем использовать оригинальный англоязычный термин. Базовым классом для persistence handler является TPersistenceHandle. Bold позволяет сохранять модели в следующие типы хранилищ:

  • СУБД.
  • Файл XML.
  • Сервер хранения модели.

В зависимости от выбранного типа хранилища выбирается соответствующий компонент-обработчик (persistence handler).

Так, для СУБД - это TBoldPersistenceHandleDB, а для файлов XML - BoldPersistenceHandleFileXML.

Генерация БД по модели Bold

Коротко опишем процесс превращения модели в базу данных СУБД MS SQL Server.

На главную форму приложения поместим компоненты BoldModel1 и BoldUMLMMLink1. Первый компонент, как мы уже знаем, предназначен для просмотра и редактирования модели, а второй позволяет импортировать модель из Model Maker. Свойство FileName компонента BoldUMLMMLink1 установим указывающим на файл модели Model Maker, а свойство BoldModel того же компонента сделаем равным BoldModel1. Дважды щелкнем мышкой на компоненте BoldModel1, при этом откроется редактор модели. В форме редактора модели нажмем кнопку «import via link», чтобы импортировать модель из Model Maker.

Далее на форму поместим компонент AdoConnection1, и настроим его свойство Connection string на связь с базой данных, в которую будет сохранена модель. В нашем примере база расположена на локальном сервере и называется BoldAdv. Строка соединения при этом имеет следующий вид:

Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=BOLDADV;Data Source=localhost

Затем поместим на форму компонент BoldDatabaseAdapterADO1, отвечающий за запись модели в базу через ADO. Его свойство Connection установим равным ADOConnection1, а свойство DatabaseEngine - равным dbeSQLServer. Свойство DatabaseEngine отвечает за правильную установку набора свойств SQLDatabaseConfig в соответствии с особенностями диалекта SQL сервера. В списке значений возможных свойств доступно большинство современных серверов БД. Можно выставить свойства SQLDatabaseConfig и вручную, в таблице приведен список наиболее часто используемых свойств:

Свойство Описание
AllowMetadataChangesInTransaction: Boolean Выполнять изменения метаданных внутри транзакции. Позволяет «обернуть» изменение метаданных модели в транзакцию, тем самым гарантируя согласованность метаданных модели. Если сервер не поддерживает транзакцию, то данное свойство необходимо установить в false.
ColumnTypeFor<тип данных>: string SQL-имя типа данных типа данных.
DBGenerationMode: TBoldDatabaseGenerationMode Режим генерации базы:dbgTable - генерация с использованием компонентов TTable, рекомендуется при генерации в формат локальных файл-серверных СУБД (Paradox)dbgQuery - генерация с помощью TQuery и SQL - способ выбран по умолчанию, как наиболее подходящий для серверов БД
DefaultStringLength: integer Длина строковых полей по умолчанию. Значение - 255. Необходимо, чтобы задаваемая длина поддерживалась сервером.
Drop…Template: string; Шаблон SQL-выражения для удаления таблицы, столбца или индекса (ALTER).
EmptyStringMarker: String Значение, записываемое в качестве пустой строки.
Max…IdentifierLength: integer Максимальная длина имен объектов БД.
QuoteNonStringDefaultValues: Boolean Заключать в кавычки не строковые значения по умолчанию.
ReservedWords: TStringList Список зарезервированных слов диалекта SQL.
SQLforNotNull: string SQL-выражение для выбора не-NULL значений.
StoreEmptyStringsAsNULL Сохранять пустые строки как NULL.
Supports…: Boolean; Поддержка в SQL диалекте того или иного SQL-выражений.
SystemTablePrefix: String; Префикс имен системных таблиц сервера.
UseSQL92Joins: boolean Поддержка сервером соединений таблиц в стиле SQL-92.SELECT <Column> FROM <Table> <TableAlias> LEFT JOIN <Table> <TableAlias> ON <Primary key>WHERE <Condition>
Таблица 1

Последним поместим компонент BoldPersistenceHandleDB1 и установим его свойство BoldModel равным BoldModel1, а свойство DatabaseAdapter - равным BoldDatabaseAdapterADO1.

Далее снова перейдем в редактор модели, дважды щелкнув на BoldModel1, в редакторе нажмем кнопку Generate database.

ПРИМЕЧАНИЕ

Помимо вышеописанных действий, не забудьте сгенерировать код (кнопка generate code) модулей BusinessClasses.inc, BusinessClasses.pas, BusinessClasses_Interface.inc.

Вот теперь самое время посмотреть на структуру сгенерированной базы.

Помимо таблиц, соответствующих классам нашей модели, в базе данных хранятся служебные таблицы (см. таблицу 2).

Таблица Описание
BOLD_ID Таблица содержит последнее значение идентификатора созданного экземпляра объекта.
BOLD_TYPE Содержит соответствие между строковыми именами классов и их номерами, используемыми при сохранении объектов в базу
BOLD_TABLES Содержит список таблиц, сгенерированных Bold
BOLD_TIMESTAMP Содержит количество вызовов метода UpdateDatabase, то есть номер последней сохраненной «версии» системы
BOLD_XFILES Каждый созданный объект попадает в эту таблицу и не удаляется даже при удалении объекта. Таблица используется для синхронизации с внешней БД.
BOLD_MEMBERMAPPING, BOLD_R_CLSMAP, BOLD_W_CLSMAP Используются при эволюции модели. Эволюция модели будет рассмотрена позже.
BOLD_CLOCKLOG Хранит информацию о соответствии TimeStamp транзакции и реального времени. Свойство ClockLogGranularity позволяет «проредить» записи в данной таблице при большой нагрузке.
BOLD_LASTCLOCK Используется для хранения последнего времени записи.
"BusinessClassesRoot" Хранит информацию о корневом бизнес-классе модели.
Таблица 2

Использование XML для хранения данных

Для хранения данных в XML не нужно предварительной подготовки, поскольку XML - достаточно гибкий формат, не требующий наличия строго формализованной структуры. Обычно для описания структуры XML-документа используются те или иные описания типа XSD, но разработчики Bold решили, что модели, хранящейся во внутреннем формате Bold, вполне достаточно. Подразумевается, что доступ к XML будет вестись средствами Bold. Поэтому, в отличие от БД, где необходимо предварительно сгенерировать структуру базы, в случае с XML никакой структуры не генерируется, а в XML-файл записываются непосредственно данные, формат которых определен в модели.

Таким образом, в общем случае вся метаинформация модели хранится в компоненте TBoldModel.

За хранение данных в формате XML отвечает соответствующий persistence handler - BoldPersistenceHandleFileXML.

Класс TBoldPersistenceHandleFileXML имеет следующие свойства (таблица 3):

Свойство Описание
BoldModel:TBoldModel Ссылка на сохраняемую модель
CacheData:Boolean Кэширование информации об объектах модели. Если свойство установлено в true, то данные обо всех объектах считываются за один раз и хранятся в памяти. Если же значение равно false, то считывание данных, принадлежащих экземплярам объектов, производится лишь при явной необходимости.
FileName:String Имя xml-файла, в котором хранится модель
Таблица 3.

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

Отсутствие доступного извне XSD осложняет использование получаемых XML-файлов в обход Bold.

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

- <ValueSpace xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"  
              xml:space="preserve">
- <Customer>
- <id xsi:type="BoldDefaultObjectId">
      <ClassName>Customer</ClassName>
      <DbValue>0</DbValue>
  </id>
  <persistencestate>0</persistencestate> 
  <existencestate>1</existencestate> 
  <timestamp>-1</timestamp> 
- <members>
- <firstName>
      <persistencestate>0</persistencestate>
      <content>11</content>
  </firstName>
- <lastName>
      <persistencestate>0</persistencestate>
      <content>444</content>
  </lastName>
- <middleName>
      <persistencestate>0</persistencestate>
      <content>33</content>
  </middleName>
- <owned_by_customer>
  <persistencestate>0</persistencestate> 
- <content>
      <id xsi:null="1" />
      <OrderNo>0</OrderNo>
  </content>
  </owned_by_customer>
  </members>
  </Customer>
- <Items>
- <id xsi:type="BoldDefaultObjectId">
      <ClassName>Items</ClassName>
      <DbValue>1849087502</DbValue>
  </id>
  <persistencestate>0</persistencestate> 
  <existencestate>1</existencestate> 
  <timestamp>-1</timestamp> 
- <members>
- <name>
      <persistencestate>0</persistencestate>
      <content>good</content>
  </name>
- <ordered_item>
  <persistencestate>0</persistencestate> 
- <content>
      <id xsi:null="1" />
      <OrderNo>0</OrderNo>
  </content>
  </ordered_item>
  </members>
  </Items>
  </ValueSpace>

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

Файл содержит набор тегов Customer и Item. Количество и вид тегов соответствуют количеству и типу созданных экземляров объектов того или иного класса. Внутри каждого такого тега присутствует тег id, который описывает тип уникального идентификатора объекта. Пример:

<id xsi:type="BoldDefaultObjectId">

Тег ClassName описывает тип созданного объекта

<ClassName>Items</ClassName>

Значение id, как нам кажется, хранится в теге DbValue

<DbValue>586093958</DbValue>

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

Далее, тег <members> содержит в себе описание значений полей объекта. Например, для объекта типа Customer:

- <members>
+ <firstName>
+ <lastName>
+ <middleName>
+ <owned_by_customer>
  </members>

Как видно из листинга, помимо полей простых типов, сохраняются и описанные в модели поля связи экземпляра данного объекта с экземплярами объектов других классов.

Каждое из простых полей содержит всего два тега: persistencestate и content.

- <firstName>
    <persistencestate>0</persistencestate> 
    <content>a</content> 
  </firstName>

Именно в поле content и сохраняется текущее значение поля экземпляра объекта.

Использование middleware-сервера

Bold позволяет не только сохранять данные непосредственно в БД или XML, но и использовать при этом специальный middleware-сервер. Взаимодействие между клиентом и сервером может производиться через HTTP, возможно, с использованием протокола SOAP. Посмотрим, как это делается.

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

Главными строительными блоками являются компоненты BoldHTTPServerPersistenceHandlePassthrough и BoldHTTPClientPersistenceHandle. Именно они и обеспечивают взаимодействие между клиентским приложением и middleware-сервером.

Создадим новое Web-приложение (File/New/Other -> Web Server Application). В WebModule поместим компоненты:

  • BoldModel1 - компонент, хранящий метаданные модели.
  • BoldUMLMMLink1 - связь с файлом модели, спроектированным в ModelMaker.
  • BoldPersistenceHandleDB1 - persistence handle реляционной БД.
  • BoldDatabaseAdapterADO1 - адаптер доступа к ADO.

ADOConnection1 - соединение с БД, в которую будут сохраняться данные. Сразу хочу отметить одну особенность. Так как приложение выполняется под управлением Web-сервера, то учетная запись, от имени которой будет идти обращение к базе данных, не будет совпадать с той, под которой вы работаете в Windows. Этот факт надо учитывать при установке режима аутентификации в Windows. При использовании режима аутентификации c явно заданным паролем и логином описанная проблема не возникает.

Мы не будем подробно расписывать, какие свойства нужно установить у описанных выше компонентов, так как к этому моменту, надеюсь, вы уже способны это сделать самостоятельно.

Настало время разместить в Web-модуле компонент BoldHTTPServerPersistenceHandlePassthrough и присвоить ему имя httpMapper. Свойство BoldModel установим равным BoldModel1, а свойство PersistenceHandle - равным BoldPersistenceHandleDB1. Как вы уже догадались, свойство BoldModel задает модель, а PersistenceHandle - это компонент, обеспечивающий обработку данных при сохранения.

Остались последние штрихи, а именно написать пару обработчиков событий. Первый обработчик WebModuleCreate вызывается при создании объекта Web-модуля. В нем необходимо активизировать систему поддержки сохранения, и состоит он всего из одной строки:

BoldPersistenceHandleDB1.Active:=true;

Второй обработчик - WebModule1WebActionItem1Action - является действием по умолчанию для Web-модуля. Чтобы добавить его, необходимо два раза щелкнуть мышкой на WebModule, и в появившемся окне редактора добавить новое действие (action). Свойство Default принадлежащее действию WebModule1WebActionItem1Action, установим в true, а свойство PathInfo сделаем равным "/Clients". Напомню, что PathInfo определяет подстроку URL, расположенную после имени ISAPI dll. Собственно говоря, вот и все, осталось скомпилировать приложение и поместить его на Web-сервер. Мы установили его в виртуальную папку BoldClients.

Приступаем к созданию клиента. В отличие от обычного клиента, в приложении, использующем сохранение данных через HTTP-сервер, обработчик сохранения является компонентом класса TBoldHTTPClientPersistenceHandle, что вполне логично. С одной стороны, у BoldHTTPClientPersistenceHandle есть свойство BoldModel, которое должно указывать на модель приложения. С другой стороны, у него есть свойство WebConnection, указывающее на компонент класса TBoldWebConnection. Компонент TBoldWebConnection, позволяет настроить параметры подключения к HTTP-серверу. Нам необходимо сделать свойство URL BoldWebConnection равным http://localhost/BoldClients/BoldServer.dll/Clients. Напомню, что BoldClients - это имя виртуальной папки, в которой лежит ISAPI dll сервера, Clients - это Path Info действия Web-модуля, выполняющего сохранение модели, о назначении остальных свойств можно прочитать в документации. Вот и вся специфика. Полный код приложений сервера и клиента представлен в примерах к статье.

Механизм подписок (Subscriptions)

Механизм подписки позволяет одному компоненту получать уведомления о событиях другого. Компонент, получающий сообщения, называется подписчиком (Subscriber). Он является наследником класса TBoldSubscriber. Компонент, генерирующий сообщения, называется издателем (Publisher) и должен быть наследником TBoldPublisher. Механизм подписок широко используется во всех частях Bold. Существуют две разновидности подписок:

  • Простая (Plain) - издатель извещает подписчиков о своих событиях. Подписчики получают уведомления.
  • Запрос-ответ (Query-Answer) - издатель извещает подписчиков и запрашивает разрешение на указанное в уведомлении действие. Подписчик может наложить вето, т.е. запретить издателю выполнение запрошенного действия.

События представляют собой экземпляры TBoldEvent, который является целым числом типа integer.

type TBoldEvent = Integer;

Помимо этого, события могут быть экземплярами TBoldSmallEvent, который также является целым числом, но имеет более узкий диапазон значений (0...31), чем TBoldEvent. Преимущество использования TBoldSmallEvent состоит в том, что в одной подписке можно получать уведомления сразу о нескольких «маленьких» событиях.

В модуле BoldSubscription.pas определен ряд констант, которые используются в качестве внутренних событий Bold. Все они имеют тип TBoldSmallEvent. Разработчики могут определять свои события для собственных нужд, при этом не нужно следить за тем, чтобы новые события не перекрывали номера уже используемых, так как подписчик и издатель могут «договориться» интерпретировать приходящие сообщения по собственному усмотрению.

Базовыми операциями при работе с подпиской являются оформление подписки и получение события подписки. Подписка производится с помощью вызова метода издателя TBoldPublisher.AddSubscription или AddSmallSubscription.

procedure AddSubscription(Subscriber: TBoldSubscriber; 
  OriginalEvent: TBoldEvent; RequestedEvent: TBoldRequestedEvent);

procedure AddSmallSubscription(Subscriber: TBoldSubscriber; 
  Events: TBoldSmallEventSet; RequestedEvent: TBoldRequestedEvent
);

Первый параметр, Subscriber: TBoldSubscriber, указывает на подписчика

Второй параметр, OriginalEvent, определяет событие, на которое производится подписка. В случае использования «маленьких» событий (TBoldSmallEvent) можно подписаться на несколько событий одновременно.

Третий параметр, RequestedEvent: TBoldRequestedEvent - это событие, которое используется при ответе на входящее событие в случае подписки типа запрос-ответ.

Издатель извещает подписчиков о происходящем событии с помощью методов TBoldPublisher.SendExtendedEvent или TBoldPublisher.SendQuery.

Метод SendExtendedEvent применяется для простых извещений. Он объявлен следующим образом:

procedure SendExtendedEvent(Originator: TObject; 
  OriginalEvent: TBoldEvent; const Args: array of const);

Первый параметр, Originator: TObject, это объект в котором произошло событие.

Второй параметр, OriginalEvent: TBoldEvent, содержит номер события.

Третий параметр содержит дополнительные параметры события.

Метод SendQuery предназначен для отправки извещения запрос-ответ и объявлен так:

function SendQuery(Originator: TObject; 
  OriginalEvent: TBoldEvent; 
  const Args: array of const; 
  Subscriber: TBoldSubscriber): Boolean;

Метод в цикле пересылает извещение о событии подписчикам, и если все подписчики подтверждают извещение, возвращает True. Если же хотя бы один подписчик не согласен с извещением, цикл прерывается и возвращается False. Так как опрос подписчиков ведется до первого вето, то нет гарантии, что все подписчики получат запрос на подтверждение извещения.

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

const
  breListBigger = 42
  breNonBiggerChange = 43;
 
// Код для оформления подписки, при оформлении передаются разные RequestEvent
  List.AddSmallSubscription(
    subscriber, [beItemAdded], breListBigger);
  List.AddSmallSubscription(
    subscriber, 
    [beItemDeleted, beItemReplaced, beOrderChanged],
    breNonBiggerChange);
 
// Часть метода-подписчика, обрабатывающего события, присылаемые издателем
TMySubscriber.Receive(...; RequestedEvent: TBoldRequestedEvent);
begin
  // Анализируем RequestEvent
  case RequestedEvent of
    breListBigger: ….
    ...
    breNonBiggerChange: …
  end;
end;

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

В дополнение к событиям, рассылаемым издателем с помощью явных вызовов SendExtendedEvent, перед разрушением экземпляра издателя всем подписчикам отсылается сообщение beDestroying. Это делается вызовом метода TBoldPublisher.NotifySubscribersAndClearSubscriptions.

Классы, которые хотят выступать в роли издателя, как правило, агрегируют в себе класс TBoldPublisher, определяя публичные методы AddSubscription и AddSmallSubscriptions для оформления подписки. Никто не запрещает определить и другие публичные методы для поддержки подписки. Например, класс TBoldDirectElement определяет метод DefaultSubscribe для оформления наиболее распространенных типов подписок. В иерархии классов Bold определены несколько готовых классов, агрегирующих TBoldPublisher:TBoldSubscribableObject, TBoldSubscribablePersistent и TBoldSubscribableComponent, и используемых в качестве предков для подписчиков.

При создании собственных классов издателей перед разрушением экземпляра объекта не забудьте вызвать метод TBoldPublisher.NotifySubscribersAndClearSubscriptions. Это известит подписчиков о прекращении существования издателя.

Классы, которые должны выступать в роли подписчика, могут быть унаследованы от класса TBoldSubscriber. При этом должны быть переопределены методы Receive и Answer. Однако более удобным и часто применяемым способом является использование класса - адаптера TBoldPassthroughSubscriber. Данный класс просто направляет полученные извещения указанному обработчику, при этом компонент, реально обрабатывающий извещения, может не являться наследником TBoldSubscriber.

Язык Object Constraint Language

Язык OCL является языком описания ограничений объектов и широко используется в Bold для работы с экземплярами объектов модели.

Синтаксис языков ОСL и Object Pascal очень похож, поэтому в данной статье мы не будем останавливаться на этом вопросе. Подробнее об OCL вы сможете узнать на сайте rsdn.ru.

Некоторые особенности построения пользовательского интерфейса средствами Bold

В данном разделе мы собрали описание некоторых типовых приемов построения GUI с использованием Bold.

Использование стандартных действий (Actions) Bold

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

Действие Описание
TBoldActivateSystemAction Активизация системы.
TBoldUpdateAction Сохранение сделанных изменений и внесенных данных.
TBoldGenerateSchemaAction Генерация пустой базы данных, соответствующей модели.
TBoldUndoAction Отменяет изменения, произведённые после установки последней контрольной точки (checkpoint).

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

Рендереры (Renderers)

Многие визуальные компоненты для построения GUI имеют возможность изменения своего внешнего вида в зависимости от некоторых условий. Одним из примеров таких компонентов является компонент TBoldGrid. Стандартным приемом изменения внешнего вида ячеек в зависимости от представленных в них данных являетcя написание обработчиков событий OnDraw. Например, для «расцвечивания» ячеек стандартного компонента TDBGrid используется обработчик сообщения OnDrawColumnCell. Помимо этого способа Bold предоставляет гораздо более удобный и гибкий механизм, получивший название rendering. Далее мы будем использовать для его обозначения русифицированный термин - рендеринг. Главная идея рендеринга заключается в том, что визуальный компонент передает функции формирования параметров отрисовки своих данных специальному невизуальному компоненту - рендереру(Renderer). Базовым классом для компонентов-рендереров является TBoldRenderer, наследники которого позволяют решить типовые задачи отрисовки данных. Применение рендереров имеет ряд преимуществ:

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

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

Один и тот же рендерер может реагировать на изменения данных, производимые в разных control-ах.

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

В качестве примера рассмотрим следующую задачу: Класс Customer нашей модели содержит атрибуты FirstName, MiddleName и LastName (имя, отчество и фамилия заказчика). Необходимо при отображении и редактировании данных атрибутов в сетке TBoldGrid выводить в отдельном столбце строчку с полными данными о заказчике (объединение строк) и обеспечить контроль полноты ввода данных. Мы не будем описывать подробно создание заготовки приложения (так как делали это уже ранее), отметим лишь, что помимо остальных компонентов поместили на форму компонент-рендерер BoldAsStringRenderer1 класса TBoldAsStringRenderer, т.е строковый рендерер. Ниже приведен внешний вид полученной формы (см. рисунок :


Рисунок 2а.

При использовании рендерера необходимо написать код обработчиков его событий. Первым возникает вопрос: каким образом рендерер будет узнавать о том, что пользователь вводит или изменяет данные? Для этого используются подписки. В обработчике события OnSubscribe нашего рендерера мы должны подписаться на изменения полей FirstName, MiddleName и LastName объектов Customer, код обработчика выглядит следующим образом:

procedure TForm1.BoldAsStringRenderer1Subscribe(Element: TBoldElement;
                                 Representation: Integer; Expression: String;
                                 Subscriber: TBoldSubscriber);
  begin
    with Element as TCustomer do
      begin
        SubscribeToExpression('firstName', Subscriber);
        SubscribeToExpression('middleName', Subscriber);
        SubscribeToExpression('lastName', Subscriber);
      end;
  end;

Событие OnSubscribe возникает при инициализации системы подписок. Параметр Element содержит класс владельца подписки. В первой строке обработчика мы проверяем, что данный класс является классом TCustomer, и если это так, то подписываемся на получение уведомлений об изменении полей FirstName, MiddleName и LastName. Подписка осуществляется вызовом метода SubscribeToExpression. Первый параметр - имя OCL-выражения, на изменения, которого мы подписываемся, второй - компонент-подписчик. Параметр Subscriber указывает на внутренний объект-подписчик рендерера. Теперь наш рендерер будет реагировать на изменения данных о заказчике.

Далее нужно обеспечить формирования значения ФИО заказчика. Это делается в обработчике события OnGetAsString рендерера.

function TForm1.BoldAsStringRenderer1GetAsString(Element: TBoldElement;
                        Representation: Integer; Expression: String): String;
begin
  Result := '';
  if Assigned(Element) then
    begin
      with Element as TCustomer
      do
        begin
          if FirstName <> '' then
            Result := FirstName
          else
            Result := '<Имя>';
          if MiddleName <> '' then
            Result := Result + ' ' + MiddleName
          else
            Result := Result + ' <Отчество>';
          if LastName <> '' then
            Result := Result + ' ' + LastName
          else
            Result := Result + ' <Фамилия>';
        end;
    end;
end;

Обработчик возвращает строку ФИО, «склеивая» атрибуты FirstName, MiddleName и LastName в одну строку. Если какой-либо из атрибутов пустой, то в качестве его значения вставляется строка <…>.

Следующий обработчик рендерера OnSetColor отвечает за вычисление цвета строки, формируемой рендерером:

procedure TForm1.BoldAsStringRenderer1SetColor(Element: TBoldElement;
            var AColor: TColor; Representation: Integer; Expression: String);
begin
  with Element as TCustomer do
  begin
    if (FirstName = '') or (MiddleName = '') or (LastName = '') then
      AColor:=clRed
    else
      AColor:=clSilver;
  end;
end;

Если не введены имя, фамилия или отчество, цвет будет красным, если же все заполнено - серым.

Таким образом, мы обеспечили отображение нужной нам информации. Но самое интересное то, что мы можем довольно легко запрограммировать возможность изменения поля ФИО таким образом, чтобы при вводе в него данных изменялись значения отдельных полей имени, фамилии и отчества. Для этого нам надо написать два обработчика событий рендерера: OnMayModify и OnSetAsString.

Обработчик OnMayModify позволяет проверить возможность редактирования поле ФИО:

function TForm1.BoldAsStringRenderer1MayModify(Element: TBoldElement;
                                 Representation: Integer; Expression: String;
                                 Subscriber: TBoldSubscriber): Boolean;
begin
  with Element as TCustomer do
  begin
    result := EvaluateExpressionAsDirectElement(
      'firstName').ObserverMayModify(Subscriber) 
      and EvaluateExpressionAsDirectElement(
      'middleName').ObserverMayModify( Subscriber ) 
      and EvaluateExpressionAsDirectElement(
      'lastName').ObserverMayModify( Subscriber );
  end;
end;

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

Второй обработчик OnSetAsString вызывается при попытке редактирования поля ФИО.

procedure TForm1.BoldAsStringRenderer1SetAsString(Element: TBoldElement;
                                                  NewValue: String;
                                                  Representation: Integer;
                                                  Expression: String);
begin
  with Element as TCustomer do
  begin
    FirstName := Copy(NewValue, 0, (Pos(' ', NewValue) -1));
    NewValue:=Copy(NewValue,
                   (Pos(FirstName, NewValue) + Length(FirstName)+1),
                   Length(NewValue) - Length(FirstName));
    MiddleName:=Copy(NewValue, 0, (Pos(' ', NewValue) -1));
    NewValue:=Copy(NewValue,
                   (Pos(MiddleName, NewValue) + Length(MiddleName)+1),
                   Length(NewValue) - Length(MiddleName));
    LastName:=NewValue;
  end;
end;

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

Последнее, что необходимо сделать - это создать в сетке TBoldGrid новый столбец для отображения/редактирования ФИО, и его свойству Renderer установить значение BoldAsStringRenderer1. Результат наших трудов представлен на рисунке 2.


Рисунок 2б.

Вычисляемые переменные в OCL выражениях (Variables)

Реализация OCL в Bold позволяет использовать вычисляемые переменные внутри OCL-выражений. Вычисление значений переменных производится в коде приложения. Использование вычисляемых переменных придает OCL-выражениям дополнительную гибкость.

На главную форму приложения поместим строку ввода для указания условий поиска и кнопку для начала поиска. Там же разместим компонент-грид (TBoldGrid) для отображения результатов поиска. Внешний вид части формы приведен на рисунке 3.


Рисунок 3.

Далее на форму поместим компонент bhlFind:TBoldListHandle, в свойстве Expression которого напишем OCL-выражение «Customer.allInstances->select(lastName.regExpMatch(var_string))». Данное выражение выбирает все экземпляры заказчиков, имеющих в фамилии строку, совпадающую со значением переменной var_string. Чтобы найденные значения отображались в гриде, свойство BoldHandle компонента BoldGrid2 установим равным bhlFind.

Теперь нам предстоит главное - устанавливать значение переменной var_string в соответствии со строкой введенной пользователем. Первым шагом к решению этой задачи является определение OCL-переменной var_string. В самом деле, откуда система узнает, что такая переменная есть? Список пользовательских переменных хранится в компоненте BoldOclVariables1:TBoldOclVariables. Этот компонент с закладки BoldHandles необходимо поместить на форму. В свойстве-коллекции Variables необходимо определить переменную var_string.

Как же организовать вычисление значения переменной? Для организации связи OCL-переменная - код существует специальный компонент TBoldVariableHandle. Данный компонент находится на закладке BoldHandle. Поместим данный компонент на форму и назовем его varSearchString. Свойство BoldHandle класса-переменной var_string установим равным varSearchString. Далее настроим свойства varSearchString, как показано в таблице (см. таблицу 4):

Свойство Значение
StaticSystemHandle BoldSystemHandle1
ValueTypeName String
Таблица 4

Таким образом, используя компонент varSearchString: TBoldVariableHandle мы можем в коде устанавливать значение OCL-переменной var_string. В нашем случае мы делаем это в обработчике события нажатия на кнопку поиска.

procedure TForm1.Button3Click(Sender: TObject);
begin
  varSearchString.Value.AsString:=edSearchString.Text;
end;

Осталось только скомпилировать и запустить приложение.

Заключение

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

Мы рассмотрели решение нескольких достаточно непростых задач, а также рассмотрели соответствующие VCL-компоненты. И, хотя эту статью нельзя назвать руководством по Bold, она может служить опорной точкой при изучении этой очень мощной технологии от фирмы Borland.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 22.02.2007 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
SAP Crystal Reports XI R2 Dev 2006 INTL WIN NUL License (Version 11)
SAP® Crystal Reports 2016 WIN INTL NUL
EMS Data Export for PostgreSQL (Business) + 1 Year Maintenance
WinRAR 5.x 1 лицензия
Nero 2018 Platinum ESD
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Программирование на Visual Basic/Visual Studio и ASP/ASP.NET
Проект mic-hard - все об XP - новости, статьи, советы
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100