|
|
|||||||||||||||||||||||||||||
|
Bold for Delphi. Часть 2Источник: RSDN Magazine #5-2003
ВведениеВ первой части статьи мы рассмотрели простейшие примеры работы с Bold. В данной статье мы продолжим знакомство с Bold и рассмотрим более сложные примеры. Изучение чего-либо носит циклический характер и продвигается от простого к сложному, данное утверждение справедливо и для программирования. Поэтому мы решили написать вторую часть статьи в этом же ключе, построив ее в виде более углубленного изучения составляющих Bold. Напомним, что приложение Bold состоит из трех основных частей: пользовательского интерфейса, набора бизнес-правил (реализованного в виде модели) и слоя, отвечающего за сохранение модели в каком-либо хранилище данных. Последовательно углубим наши знания по каждой составляющей и начнем с работы с моделью. Мы взлетаемВ качестве примера рассмотрим приложение для оформления заказов на какие-либо товары. Ниже (см. рисунок 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 позволяет сохранять модели в следующие типы хранилищ:
В зависимости от выбранного типа хранилища выбирается соответствующий компонент-обработчик (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. Строка соединения при этом имеет следующий вид:
Затем поместим на форму компонент BoldDatabaseAdapterADO1, отвечающий за запись модели в базу через ADO. Его свойство Connection установим равным ADOConnection1, а свойство DatabaseEngine - равным dbeSQLServer. Свойство DatabaseEngine отвечает за правильную установку набора свойств SQLDatabaseConfig в соответствии с особенностями диалекта SQL сервера. В списке значений возможных свойств доступно большинство современных серверов БД. Можно выставить свойства SQLDatabaseConfig и вручную, в таблице приведен список наиболее часто используемых свойств:
Последним поместим компонент BoldPersistenceHandleDB1 и установим его свойство BoldModel равным BoldModel1, а свойство DatabaseAdapter - равным BoldDatabaseAdapterADO1. Далее снова перейдем в редактор модели, дважды щелкнув на BoldModel1, в редакторе нажмем кнопку Generate database.
Вот теперь самое время посмотреть на структуру сгенерированной базы. Помимо таблиц, соответствующих классам нашей модели, в базе данных хранятся служебные таблицы (см. таблицу 2).
Использование XML для хранения данныхДля хранения данных в XML не нужно предварительной подготовки, поскольку XML - достаточно гибкий формат, не требующий наличия строго формализованной структуры. Обычно для описания структуры XML-документа используются те или иные описания типа XSD, но разработчики Bold решили, что модели, хранящейся во внутреннем формате Bold, вполне достаточно. Подразумевается, что доступ к XML будет вестись средствами Bold. Поэтому, в отличие от БД, где необходимо предварительно сгенерировать структуру базы, в случае с XML никакой структуры не генерируется, а в XML-файл записываются непосредственно данные, формат которых определен в модели. Таким образом, в общем случае вся метаинформация модели хранится в компоненте TBoldModel. За хранение данных в формате XML отвечает соответствующий persistence handler - BoldPersistenceHandleFileXML. Класс TBoldPersistenceHandleFileXML имеет следующие свойства (таблица 3):
Первый сюрприз ожидал нас при попытке задания свойства FileName. Дело в том, что диалог выбора имени файла не позволяет создать новый xml-файл. Попытка установить вручную данное свойство таким образом, чтобы оно указывало на несуществующий файл, тем не менее, оказывается успешной. Отсутствие доступного извне XSD осложняет использование получаемых XML-файлов в обход Bold. По каким причинам реализован именно такой алгоритм работы с XML-файлом, пока так и осталось для нас загадкой. Ниже приведен пример XML-файла, полученного после создания в системе экземпляров классов Customer и Items.
К сожалению, никакой документации о формате файла мы найти не смогли и опять же базировались на собственном опыте. Файл содержит набор тегов Customer и Item. Количество и вид тегов соответствуют количеству и типу созданных экземляров объектов того или иного класса. Внутри каждого такого тега присутствует тег id, который описывает тип уникального идентификатора объекта. Пример:
Тег ClassName описывает тип созданного объекта
Значение id, как нам кажется, хранится в теге DbValue
Наличие и назначение тегов persistencestate, existencestate и timestamp, повторяющихся для каждого объекта с завидной постоянностью, осталось для нас загадкой. Мы, конечно, догадываемся, что тег persistencestate указывает на особенности сохранения объекта, а existencestate - на его существование, но выявить ситуации, при которых значения этих тегов изменялись, нам так и не удалось. Далее, тег <members> содержит в себе описание значений полей объекта. Например, для объекта типа Customer:
Как видно из листинга, помимо полей простых типов, сохраняются и описанные в модели поля связи экземпляра данного объекта с экземплярами объектов других классов. Каждое из простых полей содержит всего два тега: persistencestate и content.
Именно в поле content и сохраняется текущее значение поля экземпляра объекта. Использование middleware-сервераBold позволяет не только сохранять данные непосредственно в БД или XML, но и использовать при этом специальный middleware-сервер. Взаимодействие между клиентом и сервером может производиться через HTTP, возможно, с использованием протокола SOAP. Посмотрим, как это делается. Наш сервер будет представлять собой ISAPI-приложение, написанное с использованием архитектуры WebBroker. Использование WebBroker и формата ISAPI не являются жесткими требованиями и определяются вашими предпочтениями. Главными строительными блоками являются компоненты BoldHTTPServerPersistenceHandlePassthrough и BoldHTTPClientPersistenceHandle. Именно они и обеспечивают взаимодействие между клиентским приложением и middleware-сервером. Создадим новое Web-приложение (File/New/Other -> Web Server Application). В WebModule поместим компоненты:
ADOConnection1 - соединение с БД, в которую будут сохраняться данные. Сразу хочу отметить одну особенность. Так как приложение выполняется под управлением Web-сервера, то учетная запись, от имени которой будет идти обращение к базе данных, не будет совпадать с той, под которой вы работаете в Windows. Этот факт надо учитывать при установке режима аутентификации в Windows. При использовании режима аутентификации c явно заданным паролем и логином описанная проблема не возникает. Мы не будем подробно расписывать, какие свойства нужно установить у описанных выше компонентов, так как к этому моменту, надеюсь, вы уже способны это сделать самостоятельно. Настало время разместить в Web-модуле компонент BoldHTTPServerPersistenceHandlePassthrough и присвоить ему имя httpMapper. Свойство BoldModel установим равным BoldModel1, а свойство PersistenceHandle - равным BoldPersistenceHandleDB1. Как вы уже догадались, свойство BoldModel задает модель, а PersistenceHandle - это компонент, обеспечивающий обработку данных при сохранения. Остались последние штрихи, а именно написать пару обработчиков событий. Первый обработчик WebModuleCreate вызывается при создании объекта Web-модуля. В нем необходимо активизировать систему поддержки сохранения, и состоит он всего из одной строки:
Второй обработчик - 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. Существуют две разновидности подписок:
События представляют собой экземпляры TBoldEvent, который является целым числом типа integer.
Помимо этого, события могут быть экземплярами TBoldSmallEvent, который также является целым числом, но имеет более узкий диапазон значений (0...31), чем TBoldEvent. Преимущество использования TBoldSmallEvent состоит в том, что в одной подписке можно получать уведомления сразу о нескольких «маленьких» событиях. В модуле BoldSubscription.pas определен ряд констант, которые используются в качестве внутренних событий Bold. Все они имеют тип TBoldSmallEvent. Разработчики могут определять свои события для собственных нужд, при этом не нужно следить за тем, чтобы новые события не перекрывали номера уже используемых, так как подписчик и издатель могут «договориться» интерпретировать приходящие сообщения по собственному усмотрению. Базовыми операциями при работе с подпиской являются оформление подписки и получение события подписки. Подписка производится с помощью вызова метода издателя TBoldPublisher.AddSubscription или AddSmallSubscription.
Первый параметр, Subscriber: TBoldSubscriber, указывает на подписчика Второй параметр, OriginalEvent, определяет событие, на которое производится подписка. В случае использования «маленьких» событий (TBoldSmallEvent) можно подписаться на несколько событий одновременно. Третий параметр, RequestedEvent: TBoldRequestedEvent - это событие, которое используется при ответе на входящее событие в случае подписки типа запрос-ответ. Издатель извещает подписчиков о происходящем событии с помощью методов TBoldPublisher.SendExtendedEvent или TBoldPublisher.SendQuery. Метод SendExtendedEvent применяется для простых извещений. Он объявлен следующим образом:
Первый параметр, Originator: TObject, это объект в котором произошло событие. Второй параметр, OriginalEvent: TBoldEvent, содержит номер события. Третий параметр содержит дополнительные параметры события. Метод SendQuery предназначен для отправки извещения запрос-ответ и объявлен так:
Метод в цикле пересылает извещение о событии подписчикам, и если все подписчики подтверждают извещение, возвращает True. Если же хотя бы один подписчик не согласен с извещением, цикл прерывается и возвращается False. Так как опрос подписчиков ведется до первого вето, то нет гарантии, что все подписчики получат запрос на подтверждение извещения. При оформлении подписки подписчик, как вы заметили, передает издателю параметр RequestEvent. Необходимость в этом параметре станет понятна, если мы вспомним, что номера событий не уникальны, и подписчик может оформить подписку у нескольких различных издателей. Таким образом, нам необходимо иметь какой-то признак, определяющий контекст номера события. Ниже приведен пример кода, использующего описанную концепцию.
Непосредственно механизм подписки не использует 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 с кратким описанием.
Теперь вместо написания кода обработчиков событий элементов управления, отвечающих за создание, открытие базы, откат и сохранение введенных данных, мы можем присвоить элементам управления стандартные действия Bold, тем самым еще более облегчив наш труд. Пример использования действий Bold можно увидеть в примере, прилагаемом к статье. Рендереры (Renderers)Многие визуальные компоненты для построения GUI имеют возможность изменения своего внешнего вида в зависимости от некоторых условий. Одним из примеров таких компонентов является компонент TBoldGrid. Стандартным приемом изменения внешнего вида ячеек в зависимости от представленных в них данных являетcя написание обработчиков событий OnDraw. Например, для «расцвечивания» ячеек стандартного компонента TDBGrid используется обработчик сообщения OnDrawColumnCell. Помимо этого способа Bold предоставляет гораздо более удобный и гибкий механизм, получивший название rendering. Далее мы будем использовать для его обозначения русифицированный термин - рендеринг. Главная идея рендеринга заключается в том, что визуальный компонент передает функции формирования параметров отрисовки своих данных специальному невизуальному компоненту - рендереру(Renderer). Базовым классом для компонентов-рендереров является TBoldRenderer, наследники которого позволяют решить типовые задачи отрисовки данных. Применение рендереров имеет ряд преимуществ: Различные визуальные компоненты могут использовать один рендерер для отрисовки своих данных. Это избавляет от необходимости согласования набора параметров обработчиков событий для разных контролов, возникающей при использовании стандартного механизма. В наследнике TBoldRenderer можно сосредоточить код, вычисляющий параметры отрисовки (например, установить жирный шрифт в зависимости от значения строки). Один и тот же рендерер может реагировать на изменения данных, производимые в разных control-ах. Надеюсь, что мы убедили вас в преимуществах использования рендеринга, и теперь перейдем к непосредственному рассмотрению этого механизма. В качестве примера рассмотрим следующую задачу: Класс Customer нашей модели содержит атрибуты FirstName, MiddleName и LastName (имя, отчество и фамилия заказчика). Необходимо при отображении и редактировании данных атрибутов в сетке TBoldGrid выводить в отдельном столбце строчку с полными данными о заказчике (объединение строк) и обеспечить контроль полноты ввода данных. Мы не будем описывать подробно создание заготовки приложения (так как делали это уже ранее), отметим лишь, что помимо остальных компонентов поместили на форму компонент-рендерер BoldAsStringRenderer1 класса TBoldAsStringRenderer, т.е строковый рендерер. Ниже приведен внешний вид полученной формы (см. рисунок :
При использовании рендерера необходимо написать код обработчиков его событий. Первым возникает вопрос: каким образом рендерер будет узнавать о том, что пользователь вводит или изменяет данные? Для этого используются подписки. В обработчике события OnSubscribe нашего рендерера мы должны подписаться на изменения полей FirstName, MiddleName и LastName объектов Customer, код обработчика выглядит следующим образом:
Событие OnSubscribe возникает при инициализации системы подписок. Параметр Element содержит класс владельца подписки. В первой строке обработчика мы проверяем, что данный класс является классом TCustomer, и если это так, то подписываемся на получение уведомлений об изменении полей FirstName, MiddleName и LastName. Подписка осуществляется вызовом метода SubscribeToExpression. Первый параметр - имя OCL-выражения, на изменения, которого мы подписываемся, второй - компонент-подписчик. Параметр Subscriber указывает на внутренний объект-подписчик рендерера. Теперь наш рендерер будет реагировать на изменения данных о заказчике. Далее нужно обеспечить формирования значения ФИО заказчика. Это делается в обработчике события OnGetAsString рендерера.
Обработчик возвращает строку ФИО, «склеивая» атрибуты FirstName, MiddleName и LastName в одну строку. Если какой-либо из атрибутов пустой, то в качестве его значения вставляется строка <…>. Следующий обработчик рендерера OnSetColor отвечает за вычисление цвета строки, формируемой рендерером:
Если не введены имя, фамилия или отчество, цвет будет красным, если же все заполнено - серым. Таким образом, мы обеспечили отображение нужной нам информации. Но самое интересное то, что мы можем довольно легко запрограммировать возможность изменения поля ФИО таким образом, чтобы при вводе в него данных изменялись значения отдельных полей имени, фамилии и отчества. Для этого нам надо написать два обработчика событий рендерера: OnMayModify и OnSetAsString. Обработчик OnMayModify позволяет проверить возможность редактирования поле ФИО:
Код обработчика понятен, и мы не будем его комментировать, чтобы не отвлекать внимание на пока неважные детали. Второй обработчик OnSetAsString вызывается при попытке редактирования поля ФИО.
Данный обработчик разбирает введенную пользователем строку с ФИО на отдельные составляющие - фамилию, имя и отчество. Мы считаем, что отдельные составляющие ФИО разделяются пробелами. По результатам разбора мы вычисляем значения фамилии, имени и отчества. Последнее, что необходимо сделать - это создать в сетке TBoldGrid новый столбец для отображения/редактирования ФИО, и его свойству Renderer установить значение BoldAsStringRenderer1. Результат наших трудов представлен на рисунке 2.
Вычисляемые переменные в OCL выражениях (Variables)Реализация OCL в Bold позволяет использовать вычисляемые переменные внутри OCL-выражений. Вычисление значений переменных производится в коде приложения. Использование вычисляемых переменных придает OCL-выражениям дополнительную гибкость. На главную форму приложения поместим строку ввода для указания условий поиска и кнопку для начала поиска. Там же разместим компонент-грид (TBoldGrid) для отображения результатов поиска. Внешний вид части формы приведен на рисунке 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):
Таким образом, используя компонент varSearchString: TBoldVariableHandle мы можем в коде устанавливать значение OCL-переменной var_string. В нашем случае мы делаем это в обработчике события нажатия на кнопку поиска.
Осталось только скомпилировать и запустить приложение. ЗаключениеМы надеемся, что прочтение статьи убедит вас в том, что технология Bold для Delphi выводит разработчиков на совершенно новый уровень построения информационных систем. Мы рассмотрели решение нескольких достаточно непростых задач, а также рассмотрели соответствующие VCL-компоненты. И, хотя эту статью нельзя назвать руководством по Bold, она может служить опорной точкой при изучении этой очень мощной технологии от фирмы Borland. Ссылки по теме
|
|