Продолжается подписка на наши издания! Вы не забыли подписаться?

Визуальные компоненты ascDb для работы с данными. Концепция и реализация

Михайлов С., Яныхбаш Михаил

Обычно элемент управления, используемый для работы с данными (Grid, поле ввода, и т.п.), сам занимается их отображением и редактированием. Большинство элементов управления либо отображает данные в строковом виде, либо способно отображать ограниченное количество жестко заданных типов. Если возникают новые требования к отображению данных, приходится создавать новый компонент или приобретать его у сторонних разработчиков.

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

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

При разработке ascDB мы (разработчики компании «Оптим.ру») попытались решить описанные выше проблемы. То, что у нас получилось, описано в этой статье.

Подробнее же о самой ascDb можно узнать из статьи «ascDb-курсоры в многоуровневых приложениях», опубликованной в 4-м номере журнала «Технология клиент-сервер» за 1999 г. или на сайте www.k-press.ru.

Концепция «редакторов и отрисовщиков»

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

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

Сменные редакторы и отрисовщики являются отдельными (по отношению к элементам управления) COM-объектами.

Для реализации вышесказанного была создана компонентная модель, которая дает возможность абстрагироваться от особенностей реализации механизма работы с данными, и оперировать понятием «домен». Домен в ascDb является описателем типа данных (аналогом понятия «домен» в БД или typedef в C++) и используется для задания пользовательского типа данных, определяющего внешний вид (отрисовку) и способ редактирования данных.

Домен назначается колонке курсора – и с этого момента определяет ее тип с прикладной точки зрения. Например, колонке с типом данных int может быть назначен домен «Boolean», после чего данные из нее будут отображаться и редактироваться как данные логического типа. Это примитивный случай. Более сложным является пример, когда колонка с типом данных int содержит идентификаторы товара. Реализовав домен «Product» и подключив его к колонке, можно определить ее тип с прикладной точки зрения, и в дальнейшем работать с колонкой в соответствии с этим типом (например, отображать название или внешний вид товара в элементе управления, а при редактировании – выдавать список доступных товаров).

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

Гибкость и настраиваемость ascDb вообще во многом связана с разнесением функциональности на разные компоненты там, где ее вообще можно разделить. Это иногда может сбивать с толку пользователей, привыкших к другим системам, но, наверное, стереотипы – это не самое ценное из приобретений программиста.

Чтобы дальнейший текст не вводил в заблуждение, а, напротив, выводил из оного, имеет смысл коротко остановиться на некоторых особенностях ascDb, напрямую не связанных с концепцией редакторов-отрисовщиков. Для простоты понимания всю цепочку работы с данными можно представить двумя элементами: курсор (позволяющий хранить, перемещать между процессами (компьютерами), редактировать и читать данные) и элемент управления ActiveX, реализующий пользовательский интерфейс для работы с данными (grid или field).

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

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

Когда ascDataGrid или ascDataField обнаруживает попытку начать изменение данных, создается редактор (элемент управления ActiveX). Задача редактора считать данные из редактируемой ячейки, предоставить пользовательский интерфейс для редактирования данных и поместить данные обратно в курсор.

Расширяемость системы – вот одна из основных причин, по которым в ascDb используются отдельные объекты для отрисовки и редактирования данных. В случаях, когда возникает потребность в нестандартном отображении или редактировании данных, есть возможность создания и подключения внешних (созданных сторонними программистами) отрисовщиков или редакторов.

Чтобы такой отрисовщик или редактор мог быть использован в ascDb, он должен соответствовать некоторым условиям (о деталях – чуть позже), быть зарегистрированным в системе (как COM-объект) и входить в список редакторов и отрисовщиков ascDb. После выполнения этих условий он может быть подключен к колонке.

Каждому домену соответствует своя пара «редактор-отрисовщик». Например, для работы с данными типа Boolean можно воспользоваться доменом ascBoolean, для которого назначены редактор BoolEditor и отрисовщик BoolPainter. Редакторы и отрисовщики можно задавать не опосредованно через домен, а напрямую для колонки. В принципе, комбинация в паре редактор-отрисовщик может быть любой. Так, очень часто со специализированными редакторами используется универсальный отрисовщик BasePainter.

Домен, редактор или отрисовщик могут быть заданы непосредственно во время выполнения программы. Например, если часть данных должна редактироваться как текст, а другая часть – как число, можно на время редактирования подменять редактор колонки. То же самое можно делать, если нужно отрисовать данные, например, в одной из ячеек не так, как в остальных ячейках той же колонки (реализовав обработчик события AfterDataRead и подменив в нем отрисовщик).

Использование редакторов и отрисовщиков в ascDb

Имена редакторов и отрисовщиков являются атрибутами колонок. Колонки имеются у курсора (ascCachedCursor, ascVisualCursor), а также элементов управления ascDataGrid и ascDataField. По умолчанию элементы управления наследуют информацию о колонках от курсора. Курсор может наследовать информацию о колонках от базового курсора, присылаемого с сервера. Изменения состава или атрибутов колонок базового объекта автоматически отражаются на колонках текущего объекта. Определить, что колонки работают в default-режиме, можно с помощью свойства колонок Default:

If ascCursor.Columns.Default Then ...

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

Как и любые другие атрибуты, редакторы и отрисовщики могут быть назначены колонке любого уровня иерархии. Если колонке не назначен отрисовщик или редактор, будет использоваться отрисовщик или редактор по умолчанию (в зависимости от типа данных).

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

Рисунок 1.  Дизайнер хранимых команд (список доменов, редакторов и отрисовщиков, подключенных к колонкам).

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

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

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

Кроме того, существует возможность использования функциональности ascDb без привязки к базе данных. Для этих целей существует компонент ascMemoryCursor, который не подключается к БД (в остальном он является аналогом компонента ascVisualCursor). Настройка редакторов и отрисовщиков на клиентской стороне может оказаться более выгодной, если используется сеть с малой пропускной способностью, или если настройки редакторов и/или отрисовщиков имеют слишком большой объем. Дело в том, что при настройке редакторов и отрисовщиков на сервере вся необходимая для их работы информация получается прямо там, а затем закладывается в курсор и передается по сети. Это может привести к замедлению работы программы, например, при большом количестве редакторов типа LookupEditor, настроенных в хранимой команде (на сервере).

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

Визуально колонки можно настроить в дизайнере хранимых команд на закладке «Колонки» или в странице свойств объекта ascVisualCursor и ascMemoryCursor (та же закладка). Для назначения колонке домена, редактора или отрисовщика достаточно выбрать их имена из выпадающего списка (например, на рисунке 1 выбран домен Lookup).

Возможна и настройка свойств колонок из кода программы. В ascDb колонки курсора представлены коллекцией ascColumns, содержащей объекты ascColumn. Каждая колонка имеет набор визуальных свойств, к которым можно получить доступ через страницу свойств колонок или программно – через свойство VisualProp интерфейса IascColumn (объект ascColumn). Например, ниже приведен код, назначающий колонке «Column1» домен ascBoolean:

Curs1(1).VisualProp.DomainName = "ascBoolean"

Так, свойство DomainName определяет имя ascDb-домена, а свойства EditorID и PainterID – имена редактора и отрисовщика. Список имен зарегистрированных доменов, редакторов и отрисовщиков хранится в служебной БД ascDb (файл default.mdb). Этот список можно просмотреть и изменить через ascDb-консоль (MMC-консоль ascDb представлена на рисунке 2). Доступ к списку доменов, редакторов и отрисовщиков можно получить программным путем через объект ascControlsManager (из библиотеки ascDbControlsLib).

Рисунок 2. ascDb-консоль (открыт менеджер доменов, редакторов и отрисовщиков)

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

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

‘ Заменить редактор у колонки Column1
ascVisualCursor1(“Column1”).VisualProp.EditorID = "TextEditor"

Подмена редактора обычно осуществляется в обработчике события курсора BeforeEdit, что позволяет отреагировать на попытку изменить значение в конкретной ячейке. В этом же событии можно отменить показ редактора, вернув True в параметре bCancel (например, если не выполнилось какое-то условие).

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

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

‘ В этом примере подменяется отрисовщик для тех ячеек колонки ColumnInt1,
‘ в которых содержатся значения больше 100 (они будут отрисовываться 
‘ в шестнадцатеричном формате отрисовщиком HexPainter).
Private Sub ascVisualCursor1_AfterDataRead()
    If ascVisualCursor1("ColumnInt1").Value.AsLong > 100 Then
        ascVisualCursor1("ColumnInt1").VisualProp.PainterID = “HexPainter”
    Else
        ascVisualCursor1("ColumnInt1").VisualProp.PainterID = “BasePainter”
    End If
End Sub

Аналогичным образом можно подменять, например, цвет фона ячейки:

‘ Если значение больше 100, фон будет бледно-желтым (cLiteYellowColor)
‘ Если значение меньше 100, но больше 50 – бледно-зеленым (cLiteGreenColor)
‘ Иначе – стандартным цветом фона окна Windows, 
‘ обычно белым (vbWindowBackground)
Const cLiteYellowColor = &H80000018
Const cLiteGreenColor = &HC0F0C0
Private Sub ascVisualCursor1_AfterDataRead()
    Dim iColValue as Long ‘ Чтобы не дергать лишний раз метод AsLong
    iColValue = ascVisualCursor1("ColumnInt1").Value.AsLong
    If iColValue > 100 Then
      ascVisualCursor1("ColumnInt1").VisualProp.BackColor = cLiteYellowColor
    ElseIf iColValue > 50 Then
      ascVisualCursor1("ColumnInt1").VisualProp.BackColor = cLiteGreenColor
    Else
      ascVisualCursor1("ColumnInt1").VisualProp.BackColor = vbWindowBackground
    End If
End Sub

Настройка редакторов и отрисовщиков

Некоторые редакторы и отрисовщики ascDb не нуждаются в настройке: их функциональность жестко запрограммирована (примером может послужить отрисовщик шестнадцатеричных чисел HexPainter). Другие нуждаются в обязательной настройке, уникальной для каждой колонки курсора (редактор LookupEditor), или могут работать с установками по умолчанию, но способны изменять режим своей работы в зависимости от настройки (например, PicPainter).

Каждый редактор или отрисовщик может использовать данные из так называемых «дополнительных» или «расширенных» свойств списка колонок (объект ascColumns) или конкретной колонки (ascColumn) ascDb-курсора. Дополнительные свойства колонок (колонки) реализованы в COM-объекте ascExtProperties и представляют собой коллекцию объектов ascExtProp. Каждый объект ascExtProp обеспечивает доступ к значению конкретного дополнительного свойства.

Дополнительное свойство – это некоторое свойство, имеющее имя и значение типа Variant. Для каждой колонки в курсоре можно назначить неограниченное (вернее, ограниченное только здравым смыслом) количество дополнительных свойств.

Дополнительные свойства доступны через свойство ExtProp объекта ascColumn. Доступ к дополнительным свойствам осуществляется по их именам. Попытка получить значение незаданного свойства приведет к возврату значения Empty (данные типа вариант с типом VT_EMPTY). Очистить ранее заданное свойство можно, установив ему значение Empty.

Дополнительные свойства можно задавать на любой стадии жизни курсора (на сервере, в кэширующем и визуальном курсоре, в любом визуальном компоненте). Если свойство задано на сервере, то очистить его уже не удастся. Можно только перебить его значение в дочерних компонентах (ascCachedCursor, ascVisualCursor, ascDataGrid, ascDataField). Если значение перебито на некотором уровне, то его очистка приведет к тому, что значение будет браться с предыдущего уровня.

Если редактор или отрисовщик нуждаются в специфичной настройке для отображения или редактирования данных конкретной колонки курсора, то они хранят эту настройку в дополнительных свойствах колонки. Если эта информация распространяется на весь курсор, то имеет смысл сохранять ее в дополнительных свойствах списка колонок. Доступ к расширенным свойствам объектов ascColumns и ascColumn осуществляется через свойства ExtProperties и ExtProp этих объектов соответственно.

В ascDb существует специальный механизм для настройки редакторов и отрисовщиков в визуальном режиме. Каждый редактор (отрисовщик) может быть ассоциирован с дизайнером (особым элементом управления ActiveX, предназначенным для настройки редактора или отрисовщика). Список стандартных дизайнеров ascDb, ассоциированных с отрисовщиками, можно увидеть на закладке «Отрисовщики» ascDb-консоли (рисунок 3).

Дизайнер редактора или отрисовщика загружается на закладке «Колонки» (страница свойств курсора). Тип дизайнера определяется типом редактора или отрисовщика, установленного для выделенной колонки. Для примера на рисунке 1 показан дизайнер редактора LookupEditor, реализованный в объекте ascVisualControlsLib.ascEnumColDesigner.

Рисунок 3. Закладка «Отрисовщики» ascDb-консоли.

Подробнее о конкретных дизайнерах сказано ниже, в описании стандартных редакторов и отрисовщиков ascDb.

Описание стандартных редакторов и отрисовщиков (входящих в поставку ascDb)

В составе ascDb определено 7 доменов. Также в поставку ascDb входят 8 отрисовщиков и 6 редакторов. В качестве еще одного редактора используется “Microsoft Date and Time Picker” (объект MsComCtl2.DTPicker).

Домены:

ascText – работа с текстом.

Отрисовщик TextPainter, редактор TextEditor.

Этот домен предназначен для отображения и редактирования текста. На рисунке 4 показан момент редактирования одной из ячеек колонки «ContactName» с помощью редактора TextEditor. Кроме режима редактирования текста в отдельном окне, показанного на этом рисунке, TextEditor обеспечивает и редактирование текста по месту.

Рисунок 4. Редактор и отрисовщик домена ascText.

ascLookup – выпадающий список

Отрисовщик BasePainter, редактор LookupEditor.

По своей сути редактор LookupEditor – это многоколоночный выпадающий список. Вместо обычного ListBox’а в нем открывается грид (компонент ascDataGrid), который отображает данные из так называемого lookup-курсора. В качестве lookup-курсора обычно выступает курсор, связанный с БД (ascVisualCursor), но может применяться и курсор, не связанный с БД (ascMemoryCursor).

Как правило, lookup-курсор делается редактируемым, что позволяет добавлять, удалять и изменять записи в нем. При закрытии редактора значения из колонок выделенной строки выпадающего списка попадают в соответствующие им колонки основного курсора (это соответствие устанавливается при настройке редактора LookupEditor).

Lookup-курсор содержит список значений, пригодных для установки в подключенные к нему колонки основного курсора. Например: основной курсор (данные которого отображаются к компоненте ascDataGrid) содержит список автомобилей, отданных в ремонт. В колонке «Марка» показывается название марки автомобиля. Можно настроить lookup-курсор так, чтобы он содержал список марок автомобилей (например, взяв их из другой таблицы БД). Тогда, при необходимости выбрать новое значение в ячейке колонки «Марка» основного курсора, достаточно будет просто открыть выпадающий список редактора LookupEditor и выделить в нем соответствующее наименование марки автомобиля. Там же можно будет ввести новую марку автомобиля, удалить или изменить старую. Если с маркой автомобиля связаны какие-то дополнительные параметры (например, модель автомобиля), то для колонки «Модель» (в выпадающем списке) можно настроить свой LookupEditor, и уже в нем редактировать номера моделей автомобиля.

Как уже говорилось выше, редактор ascLookupEditor реализован на базе компонента ascDataGrid, который появляется в качестве выпадающего списка (причем к каждой колонке этого списка может быть подключен свой LookupEditor или любой другой редактор). Это самый сложный, но, в то же время, самый многофункциональный из редакторов в ascDb (на рисунке 5 показан LookupEditor, открытый над колонкой «Марка»). Он позволяет существенно упростить пользовательский интерфейс и сэкономить немало места на формах приложения - ведь редактирование и выбор значений для всех колонок основного курсора оказываются вынесенными в выпадающие списки, которые, в свою очередь, являются редактируемыми и могут иметь значительный уровень вложенности.

Довольно сложное приложение, связанное с БД, может быть собрано с использованием только редактора LookupEditor, и при этом потребуется минимум программирования, поскольку вся основная работа выполняется в визуальном режиме в дизайнере хранимых команд (на сервере) или на страницах свойств компонентов ascVisualCursor, ascMemoryCursor, ascDataGrid.

Рисунок 5. Редактор и отрисовщик домена ascLookup (двойная вложенность Lookup-редакторов).

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

Настройка редактора LookupEditor выполняется в визуальном режиме на странице свойств колонок курсора или программно (через управление значениями расширенных свойств колонок). На рисунке 1 показана страница свойств «Колонки» с дизайнером редактора LookupEditor (объект ascExtPropDesigners.ExtPropLookup), открытым на странице «динамические курсоры».

Последовательность подключения редактора LookupEditor к какой-либо колонке в визуальном режиме выглядит следующим образом. В дизайнере хранимых команд или на странице свойств объекта ascVisualCursor, ascMemoryCursor или ascDataGrid, на закладке «Колонки» открывается закладка «Домены, редакторы и отрисовщики». Там, в таблице со списком колонок курсора, выбирается имя домена «Lookup» для выделенной колонки (рисунок 6).

Как только домен будет установлен, ascDb найдет и подгрузит соответствующие дизайнеры для редактора и отрисовщика. Домен Lookup использует в качестве отрисовщика BasePainter, поэтому закладка «Отрисовщик» останется пустой (BasePainter является не настраиваемым отрисовщиком). На закладке же «Редактор» появится дизайнер редактора LookupEditor.

Рисунок 6. Выбор домена Lookup для колонки «CompanyName»

На первой закладке этого редактора (она называется «Основная») указывается имя lookup-курсора и имя его колонки-источника, которая будет связана с колонкой-приемником основного курсора (в данном случае, с колонкой «CompanyName»). Эта закладка выглядит по-разному на сервере (при настройке команды, рисунок 7) и на странице свойств локального компонента (при настройке ascVisualCursor, ascMemoryCursor или ascDataGrid, рисунки 8, 9).

Рисунок 7. Закладка «Основная» дизайнера для редактора EditLookup при настройке на сервере (дизайнер хранимых команд).

При настройке в дизайнере команд невозможно использовать локальный курсор в качестве lookup-курсора. Список выбора «тип источника» скрыт за ненадобностью, а закладка «Динамические курсоры» – доступна.

При настройке в странице свойств визуального курсора или элемента управления ascDataGrid предоставляется возможность выбрать локальный курсор (из числа объектов ascVisualCursor и ascMemoryCursor, лежащих на той же форме). Если выбирается локальный курсор, то закладка «Динамические курсоры» скрыта (если же выбирается динамический курсор, то эта закладка становится доступной, как это показано на рисунке 9).

Рисунок 8. Закладка «Основная» дизайнера для редактора EditLookup при настройке на клиенте (страница свойств ascVisualCursor, ascMemoryCursor или ascDataGrid). Выбран список локальных курсоров.

Рисунок 9. Закладка «Основная» дизайнера для редактора EditLookup при настройке на клиенте (страница свойств ascVisualCursor, ascMemoryCursor или ascDataGrid). Выбран список динамических курсоров.

Если выбран режим работы с динамическим курсором, то прежде, чем настраивать колонки, нужно создать и настроить сам курсор. Это делается на закладке «Динамические курсоры» (рисунок 10). В поле «Динамический курсор» вводится имя создаваемого курсора, в выпадающем списке «Загружаемый объект» выбирается middleware-объект (подробнее о middleware-объектах в ascDb см. статью «ascDb-курсоры в многоуровневых приложениях» в 4-м номере журнала «Технология клиент-сервер» за 1999 г или на сайте www.k-press.ru).

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

Включение режима «Показывать фильтр» дает возможность увидеть и редактировать в выпадающем списке не только данные, но и строку с параметрами команды. Таким образом можно фильтровать данные через пользовательский интерфейс прямо в редакторе LookupEditor (рисунок 11). Когда же этот режим выключен, то список параметров скрыт, и доступ к ним может быть получен только программным путем.

Рисунок 10. Закладка «Динамические курсоры» дизайнера для редактора EditLookup при настройке на клиенте (страница свойств ascVisualCursor, ascMemoryCursor или ascDataGrid).

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

Рисунок 11. Редактор EditLookup в режиме показа фильтра (фильтр – это список парамтеров команды, представленный одной строкой в верхней таблице). Данные отфильтрованы по столбцу «CompanyName» в соответствии с фильтром «%u%»

Список динамических lookup-курсоров является общим для всех колонок основного курсора и хранится в дополнительных (расширенных) свойствах коллекции колонок (объект ascColumns) в свойстве «ascDinCursors». Список динамических курсоров реализован в виде массива вариантов (variant safe array), где каждым элементом является массив настроек отдельного динамического курсора. В массиве настроек динамического курсора хранятся (в порядке возрастания индекса):

Конкретная колонка основного курсора, к которой подключен LookupEditor (далее: колонка-приемник), в списке своих дополнительных (расширенных) свойств содержит свойство «ascDinCurColInfo», в котором хранится массив с настройками. В этом массиве (variant safe array) хранятся (в порядке возрастания индекса):

Подмена lookup-курсора колонкам в ходе выполнения программы

Можно непосредственно во время выполнения программы подменять lookup-курсор редактору LookupEditor, просто меняя значения в его дополнительном свойстве ascDinCurColInfo.

Ниже приведен пример (на VB). В этом примере в обработчике события AfterOpen визуального курсора ascVisualCursor1 происходит подмена lookup-курсора для редактора LookupEditor, подключенного к колонкам ID и Name основного курсора. В зависимости от значения переменной m_bCondition, редактор оказывается подключен то к динамическому курсору dcDinCursorFirst (с колонками-источниками ID_First и FirstName), то к динамическому курсору dcDinCursorSecond (с колонками-источниками ID_Second и SecondName).

К сожалению, при доступе к массиву, заложенному в значение типа Variant, VB создает временную копию этого массива. Это не очень страшно при чтении данных (происходит всего одно лишнее копирование памяти), но зато критично при их записи в этот массив. Все изменения, сделанные в этом временном массиве, будут потеряны и не попадут по назначению.

Эта особенность VB вынуждает создавать собственный временный массив, изменять значения в нем, а затем присваивать массив в свойство, то есть использовать код:

Dim aryTmp() As Variant
aryTmp = avc.Columns(ColumnName).ExtProp(ExtPropName)
aryTmp(ParamIndex) = ParamValue
avc.Columns(ColumnName).ExtProp(ExtPropName) = aryTmp

вместо кода:

avc.Columns(ColumnName).ExtProp(ExtPropName)(ParamIndex) = ParamValue

Чтобы избежать дублирования кода, эта заплатка вынесена в функцию SetCursorExtPropParamByNumber:

Private Sub SetCursorExtPropParamByNumber( _
avc As ascVisualCursor, _
ColumnName As String, _
ExtPropName As String, _
ParamIndex As Long, _
ParamValue As Variant _
)
Dim aryTmp() As Variant
aryTmp = avc.Columns(ColumnName).ExtProp(ExtPropName)
aryTmp(ParamIndex) = ParamValue
avc.Columns.Default = False
avc.Columns(ColumnName).ExtProp(ExtPropName) = aryTmp
End Sub

Сам же обработчик события AfterOpen, в котором выполняется подмена lookup-курсора, выглядит так:

Private Sub ascVisualCursor1_AfterOpen()
  ‘ Здесь подменяется lookup-курсор для колонок-приемников ID и Name 
  ‘ в зависимости от некоторого внешнего условия m_bCondition.
  Const iDinCurInfoParamIndex As Long = 1
  Const iColNameParamIndex As Long = 2
  If m_bCondition Then
    ascVisualCursor1.Columns("Name").Caption = "Первый случай"
    SetCursorExtPropParamByNumber ascVisualCursor1, "ID", _
      "ascDinCurColInfo", iDinCurInfoParamIndex, " dcDinCursorFirst"
    SetCursorExtPropParamByNumber ascVisualCursor1, "Name", _
      "ascDinCurColInfo", iDinCurInfoParamIndex, " dcDinCursorFirst"
    SetCursorExtPropParamByNumber ascVisualCursor1, "ID", _
      "ascDinCurColInfo", iColNameParamIndex, "ID_First"
    SetCursorExtPropParamByNumber ascVisualCursor1, "Name", _
      "ascDinCurColInfo", iColNameParamIndex, "FirstName"
  Else
    avcGetOrderForRepairFL.Columns("Name").Caption = " Второй случай"
    SetCursorExtPropParamByNumber ascVisualCursor1, "ID", _
      "ascDinCurColInfo", iDinCurInfoParamIndex, " dcDinCursorSecond"
    SetCursorExtPropParamByNumber ascVisualCursor1, "Name", _
      "ascDinCurColInfo", iDinCurInfoParamIndex, " dcDinCursorSecond"
    SetCursorExtPropParamByNumber ascVisualCursor1, "ID", _
      "ascDinCurColInfo", iColNameParamIndex, "ID_Second"
    SetCursorExtPropParamByNumber ascVisualCursor1, "Name", _
      "ascDinCurColInfo", iColNameParamIndex, "SecondName"
  End If 
End Sub
Получение доступа к вложенным lookup-курсорам

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

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

Ниже приведен пример кода по подключению к событиям вложенного lookup-курсора:

' Объявление переменной для получения в нее lookup-курсора 
‘ и подключения к его событиям
Dim WithEvents m_LookUpCurs As ascDBSharedLib.ascVisualCursor
' Позволяет получить lookup-курсор по его имени
' Список динамических курсоров хранится в объекте,
' реализующем коллекцию колонок (ascColumns)
Private Function GetLookupCursor( _
    ParentCurs As ascDBSharedLib.ascVisualCursor, _
    sCursName As String _
) As ascDBSharedLib.ascVisualCursor
  Dim CursInfo As Variant, col As ascColumn
  If Not IsEmpty(ParentCurs.Columns.ExtProp("ascDinCursors")) Then
    For Each CursInfo In ParentCurs.Columns.ExtProp("ascDinCursors")
      If CursInfo(2) = sCursName Then
        Set GetLookupCursor = CursInfo(1)
        Exit Function
      End If
    Next
    Exit Function
  End If
End Function

' В обработчике события AfterOpen главного курсора 
' выполняется подключение к событиям его lookup-курсора
Private Sub ascVisualCursor1_AfterOpen()
  Set m_LookUpCurs = GetLookupCursor(ascVisualCursor1.Object, "Suppliers")
End Sub

' Этот обработчик события AfterOpen вызовется после открытия 
' первого по вложенности lookup-курсора
Private Sub m_LookUpCurs_AfterOpen()
  ‘ В этом месте можно поместить, например, код подмены lookup-курсора 
  ‘ уже для колонок-приемников lookup-курсора m_LookUpCurs 
  ‘ (второй уровень вложенности)
  ‘ Аналог этого кода есть в обработчике ascVisualCursor1_AfterOpen 
  ‘ предыдущего примера, поэтому сам код здесь не приводится.
  ‘…
End Sub
ascPicture – работа с изображениями

Отрисовщик PicPainter, редактор PicEditor.

Этот отрисовщик предназначен для работы с изображениями. PicEditor позволяет только выбирать и загружать уже существующие графические файлы (рисунок 12), без возможности их редактирования. Если колонка, к которой подключен PicEditor, содержит данные бинарного типа, то картинка из выбранного файла копируется в БД в виде BLOB’а. Если колонка имеет строковый тип данных, в ней сохраняется ссылка на выбранный графический файл (полный путь до этого файла или URL). Сохраненные таким образом изображения считываются из БД и отображаются отрисовщиком PicPainter в соответствующих ячейках таблицы (ascDataGrid) или в поле данных (ascDataField).

Рисунок 12. Редактор и отрисовщик домена ascPicture.

Отрисовщик PicPainter можно настраивать с помощью объекта ascPntAndEdtDesigners.PicDesigner. Внешний вид дизайнера для этого отрисовщика представлен на рисунке 13.

Рисунок 13. Дизайнер отрисовщика PicPainter.

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

Список перечислений, используемых в настройках отрисовщика PicPainter (язык описания: C++):

//  Используется для PicAlignMode отрисовщика PicPainter
enum ASC_ALIGNMENT_PIC
{
  aapLeft     = 0x01,
  aapRight    = 0x02,
  aapHCenter  = 0x04,
  aapTop      = 0x10,
  aapBottom   = 0x20,
  aapVCenter  = 0x40,
  aapCenter   = aapHCenter | aapVCenter,
  aapLeftTop  = aapLeft | aapTop, 
  aapRightTop = aapRight | aapTop
};

//  Используется для PicScaleMode отрисовщика PicPainter
enum ASC_SCALE_MODE_INTERNAL
{
  asmiFit         = 0, // растягивать на всю область отрисовки
  asmiActualSize  = 1, // отрисовка изображения «как есть» (в масштабе 1:1)
  asmiBestFit     = 2  // растягивать, сохраняя пропорции
};

//  Используется для PicRenderMode отрисовщика PicPainter
enum ASC_RENDER_TYPE_INTERNAL
{
  // отрисовка средствами интерфейса IPicture (метод Render)
  artiIPicture   = 0x01, 
  // отрисовка средствами GDI (функция StrechBlt)
  artiGDI        = 0x02, 
  // возможности GDI + смешение с указанным цветом
  artiColorBlend = 0x04, 
  // возможности GDI + прозрачность по указанному цвету
  artiGDITransparent           = 0x07, 
  artiGDIColorBlend            = artiGDI | artiColorBlend,
  artiIPictureColorBlend       = artiIPicture | artiColorBlend,
  artiGDITransparentColorBlend = artiGDITransparent | artiColorBlend
};

Доступ к настройкам отрисовщика из кода программы осуществляется стандартным путем через расширенные свойства колонки (с помощью свойств ExtProperties и ExtProp объекта ascColumn).

ascDateTime – работа с датой и временем.

Отрисовщик BasePainter, редактор DTPicker (из стандартных ActiveX-компонентов поставки Microsoft).

Предназначен для показа и редактирования даты и (или) времени (рисунок 14).

Рисунок 14. Редактор и отрисовщик домена ascDateTime (над колонкой TestDateTime).

ascEnum – целочисленное перечисление (enum of integers)

Отрисовщик EnumPainter, редактор EnumEditor.

Редактор и отрисовщик содержат в себе настраиваемый список значений перечисления с соответствующими им строками (display name) для отображения этих значений. Редактор является выпадающим списком. В качестве примера на рисунке 15 показан редактор ascEnum, в котором значению «0» соответствует название «Не определено», значению «1» – «Первый элемент», значению «2» – «Второй элемент», значению «3» – «Третий элемент».

Рисунок 15. Редактор и отрисовщик домена ascEnum
(над колонкой TestInt, содержащей данные типа int)

Рисунок 16. Дизайнер отрисовщика EnumPainter и редактора EnumEditor.

Редактор и отрисовщик домена ascEnum можно настраивать. В визуальном режиме они настраиваются с помощью одного и того же объекта ascVisualControlsLib.ascEnumColDesigner. Внешний вид этого дизайнера представлен на рисунке 16.

Отрисовщик EnumPainter и редактор EnumEditor могут также быть настроены программно. Список дополнительных (расширенных) свойств колонки, к которой подключен любой из них, содержит свойство EnumValues. В этом свойстве хранится массив (variant safe array) со значениями и соответствующими им строками (display name) для элементов перечисления. Если в ячейке колонки оказывается значение, не описанное в этом массиве, то оно отображается в виде строки.

Чтобы изменить массив соответствий значений строкам, хранящимся в EnumValues, нужно получить доступ к нему. Чтобы не попасться в ловушку особенностей реализации VB (при работе с массивами, заложенными в Variant), надо обойти ее, как это уже делалось в примере к редактору LookupEditor, за счет присвоения через собственный временный массив:

' Пример изменения значения и строки (display name) первого элемента 
' перечисления (настройка EnumPainter или EnumEditor)
Dim ary As Variant
ary = ascVisualCursor1(“Column1”).ExtProp("EnumValues")
ary (0, 0) = 1 ‘ Значение первого элемента
ary (1, 0) = “Новое название первого элемента”
ascVisualCursor1(“Column1”).ExtProp("EnumValues") = ary
ascStringEnum – строковое перечисление (enum of strings)

Отрисовщик EnumPainter, редактор EnumEditor.

Этот домен аналогичен домену ascEnum, за исключением того, что в качестве значений перечисления выступают строки («короткие имена» элементов перечисления), а отображаются соответствующие им строки (display name). В примере на рисунке 17 показан редактор ascEnum, в котором значению «ааа» соответствует название «Значение aaa», значению «ббб» – «Значение ббб», значению «ввв» – «Значение ввв».

Рисунок 17. Редактор и отрисовщик домена ascEnumStrings (над колонкой TestStr10, содержащей данные строкового типа varchar(10))

Поскольку отрисовщик и редактор этого домена те же, что и домена ascEnum, то все, сказанное выше о домене ascEnum, применимо и здесь.

ascBoolean – работа с данными логического типа (Boolean)

Отрисовщик BoolPainter, редактор BoolEditor (рисунок 19).

Редактор BoolEditor и отрисовщик BoolPainter реализованы в виде переключателя (checkbox). Отмеченное состояние означает «True», иначе «False».

Рисунок 18. Дизайнер отрисовщика EnumPainter и редактора EnumEditor.

Однако их внешний вид является настраиваемым, хотя и без помощи дизайнера. Установив в визуальное свойство Format колонки курсора значение «acfCustom» и значение свойства «FormatString» на "True";"False";"True";"False", можно добиться показа строк «True» и «False» вместо переключателя (разумется, вместо «True» и «False» можно использовать любые другие строки, например «мама мыла Раму» и «мама НЕ мыла Раму»).

Рисунок 19. Редактор и отрисовщик домена ascBoolean (над колонкой TestBoolBit, содержащей данные логического типа bit)

Отрисовщики и редакторы, пока не включенные ни в один домен (могут использоваться самостоятельно или в составе нового домена):

HexPainter – отрисовщик целых чисел в шестнадцатеричном формате (представлен на рисунке 20).

Рисунок 20. Отрисовщик HexPainter (над колонкой TestIntNotNull, содержащей целочисленные данные
типа int)

FontPainter – отрисовщик для показа шрифтов, FontEditor – редактор для выбора шрифта.

В примере на рисунке 21 показано всплывающее меню редактора FontEditor. При выборе «Choose» или при нажатии на кнопку «…», выдается стандартный диалог выбора шрифта. При выборе «Preview», в отдельном диалоге показывается пример текста, набранного этим шрифтом.

Рисунок 21. Отрисовщик FontPainter и редактор FontEditor, примененные на закладке «Колонки» (страница свойств колонок курсора).

Особенно хочется отметить, что редактор FontEditor написан на VB6. Этот редактор не реализует интерфейса IascEditor и работает только через свойство Value (свойство по умолчанию).

OleColorPainter – отрисовщик для показа цвета (воспринимает целочисленные значения, которые интерпретирует как данные типа OLE_COLOR), рисунок 22.

Рисунок 22. Отрисовщик OleColorPainter (над колонкой TestOleColor, целочисленные данные типа int).

Создание новых дизайнеров для редакторов и отрисовщиков

Итак, дизайнер – это элемент управления ActiveX, динамически загружаемый для настройки редакторов или отрисовщиков. Чтобы дизайнер мог быть загружен на странице свойств «Колонки», он должен реализовывать метод ColumnsCursor в основном Disp-интерфейсе. Ниже приведено описание метода ColumnsCursor на VB:

Public Sub ColumnsCursor(ByVal CurColumns As Object, _
ByVal WorkingMode As Variant, ByVal SrvName As Variant)
End Sub

Метод ColumnsCursor вызывается из дизайнера колонок. На вход в параметре CurColumns передается ascDb-курсор, содержащий настройки редактируемой колонки.

Все настройки редакторов и отрисовщиков хранятся в расширенных свойствах колонки и/или курсора. Расширенные свойства – это ассоциативный массив, содержащий пары «название свойства»/«значение в виде Variant». Имя расширенного свойства должно быть уникальным для своей области видимости (колонки или курсора). Расширенные свойства колонки можно получить через ее основные свойства ExtProperties и ExtProp. Доступ к списку колонок можно получить через свойство Columns.

Вернемся к описанию метода ColumnsCursor. В параметре WorkingMode передается идентификатор режима, в котором открыт дизайнер. Список режимов описан перечислением ASC_COLUMNS_DESIGNER_MODE (библиотека ascVisualControlsLib):

Public Enum ASC_COLUMNS_DESIGNER_MODE
  acdmNormal
  acdmServer
End Enum

Если указан режим acdmServer, то дизайнер открыт в серверном режиме, в дизайнере хранимых команд (в этом случае, например, дизайнер редактора LookupEditor не дает возможность подключиться к локальным курсора). Иначе дизайнер открыт для редактирования колонок на клиенте (это означает, что ему могут быть доступны визуальные курсоры, расположенные на той же форме).

В параметре SrvName передается имя сервера (удаленного компьютера, где установлена серверная часть ascDb).

Дизайнер должен быть зарегистрирован в системе и связан с редактором или отрисовщиком. Последнее можно сделать с помощью ascDb-консоли. Если эти условия выполнены, то при подключении к колонке соответствующего отрисовщика (в дизайнере хранимых команд или странице свойств объектов ascVisualCursor, ascMemoryCursor и ascDataGrid) дизайнер появится на закладке «Колонки\Домены, редакторы и отрисовщики\Отрисовщик». Дизайнер, зарегистрированный для редактора, появится не на закладке «Отрисовщик», а, соответственно, на закладке «Редактор».

Создание новых отрисовщиков в ascDb

Самый простой способ создать свой объект-отрисовщик – это воспользоваться соответствующим ascDb-визардом для VC6 (мастер «Painter» из категории ascDb мастера ATL Object Wizard). Этот визард входит в поставку ascDb. Для VC7 такой визард появится в ближайшее время. Однако, прежде чем воспользоваться этим мастером, имеет смысл ознакомиться с тем, что же он делает.

Чтобы COM-объект стал отрисовщиком ascDb, нужно реализовать в нем COM-интерфейс IascPainter, через который в ascDb осуществляется взаимодействие отрисовщика с курсором. Описание интерфейса IascPainter (IDL):


typedef enum DISPID_PAINTER 
{
  DISPID_PAINTER_ASSIGNCOLUMN = 1,
  DISPID_PAINTER_DRAWCELL,
  DISPID_PAINTER_CALCMAXDRAWSIZE
} DISPID_PAINTER;

struct ASC_DRAW_CELL_STRUCT
{
  [ helpstring("Контекст устройства вывода.") ]
  long hDC;

  [ helpstring("Прямоугольник с координатами ячейки внутри объекта.") ]
  RECT rcCell;

  [ helpstring(
    "Прямоугольник с координатами области, в которой нужно рисовать.") 
  ]

  RECT rcBound;
  [ helpstring("Комбинация флагов из перечисления ASC_DRAW_CELL_FLAGS.") ]

  long Flags;
} ASC_DRAW_CELL_STRUCT;

[
  object,
  uuid(2DF1A5F5-CF34-11D3-AEAC-004095E1F072),
  dual,
  helpstring("IascPainter Interface"),
  pointer_default(unique)
]
interface IascPainter : IDispatch
{
  [
   id(DISPID_PAINTER_ASSIGNCOLUMN), 
   helpstring("Назначить отображаемую колонку.")
  ] 
  HRESULT AssignColumn([in] IascColumn * Column);

  [id(DISPID_PAINTER_DRAWCELL), helpstring("Отобразить ячейку.")]
  HRESULT DrawCell([in] ASC_DRAW_CELL_STRUCT * ascDrawCellStruct);

  [id(DISPID_PAINTER_CALCMAXDRAWSIZE), helpstring(
   "Используется для расчета оптимального размера области.")
  ]
  HRESULT CalcMaxDrawSize(
    [in] long hdc, [in]long CellWidth, 
    [in]long  CellHeight, [out]long *MaxWidth, 
    [out]long *MaxHeight);
};

В момент подключения отрисовщика к колонке у него вызывается метод AssignColumn. В первом параметре этого метода передается указатель на объект колонки (ascColumn, интерфейс IascColumn), содержащий всю необходимую информацию о ней. При отрисовке элемента управления, отображающего данные (например, ascDataGrid), у отрисовщика вызывается метод DrawCell, в параметре которого передаются все данные, необходимые для отрисовки. Как уже говорилось выше, в задачи отрисовщика входит кэширование необходимой информации о колонке с целью ускорения отрисовки. Обновлять содержимое кэша можно, подключившись к событиям колонки (интерфейс _IascColumnEvents) в методе AssignColumn. Метод CalcMaxDrawSize вызвается для расчета оптимального размера области при отображении данных.

Приведенное ниже описание подразумевает использование Visual C++ 6.0 и библиотек шаблонов ATL и ascLib.

Чтобы не мучить себя каждый раз при создании нового отрисовщика, можно унаследовать всю его основную функциональность от шаблона IascPainterWithBasePropImpl<...> (файл «ascPainterWithBasePropImpl.h»).

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

Чтобы созданный класс был распознан средствами регистрации ascDb как объект-отрисовщик, он должен входить в компонентную категорию ascPainters. Для этого достаточно добавить в описание класса следующий код (здесь «CPntExmpl» – имя класса нового объекта-отрисовщика):

BEGIN_CATEGORY_MAP(CPntExmpl)
  IMPLEMENTED_CATEGORY(ASC_CATEGORY_ID(ascPainters))
END_CATEGORY_MAP()

В случае создания простого отрисовщика нет нужды в реализации методов событийного интерфейса _IascColumnEvents (заглушки для этих методов наследуются автоматически от IascPainterWithBasePropImpl). Однако, если отрисовщику необходимо реагировать на какие-то из событий, происходящих в колонке, эти события должны быть в нем переопределены. Например, если отрисовщик должен реагировать на изменение шрифта в колонке, то в нем следует переопределить метод AfterFontChanged. Для того, чтобы отрисовщик мог реагировать на изменение цвета фона, нужно переопределить метод AfterBackColorChanged, а для реакции на установку нового цвета текста – метод AfterForeColorChanged.

Отрисовщик может пользоваться дополнительной информацией, которую можно настраивать программно или в дизайнерах. Например, отрисовщик EnumPainter должен знать, какой текст показывать над ячейкой, содержащей некоторое значение. Список соответствий значений и отображаемых строк (display name) при настройке этого отрисовщика записывается в массив, который сохраняется в дополнительных (расширенных) свойствах колонки. Если список соответствий будет изменен во время выполнения программы, отрисовщик сможет узнать об этом, перебив реализацию метода AfterColumnsExtPropChanged.

Чтобы создавать отрисовщик без помощи мастера ascDb, нужно последовательно пройти все нижеописанные этапы.

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

Для подключения ascLib к проекту нужно модифицировать файлы stdafx.h и stdafx.cpp.

В файле «stdafx.h» должно быть указано:

#define _NO_ASCDEBUGBREAKONFAILURE
#include <atlbase.h>
#include <AtlApp.h>
using namespace WTL;
#include "asclibinit.h"
extern CComModule _Module; 
#include <atlcom.h>
#include "ascLib.h" 

Из файла «stdafx.cpp» нужно удалить строку:

#include «atlimpl.cpp»

и добавить строку:

#include "ascLib.cpp"

Вместо объявления NO_ASCDEBUGBREAKONFAILURE можно добавить ссылку на ascDebug.lib в настройках проекта (в списке «Objects/library modules» на закладке «Link») и прописать путь к библиотеке ascDebug.dll в настройках среды VC.

После подключения ascLib, надо создать новый COM-объект. Для автоматизации данной задачи в среде Visual C++ 6.0 имеется специальное средство – мастер ATL Object Wizard. Чтобы компонент получился «потоньше», лучше всего выбрать Simple Object из категории Objects.

Назовем новый объект PntExmpl. Его класс при этом получит название СPntExmpl, а файлы – PntExmpl.h и PntExmpl.cpp.

В IDL-файл проекта нужно внести информацию об обязательном для отрисовщиков интерфейсе (IascPainter), включить файлы «ascDBShared.tlb» и «ascDBControls.tlb», а также удалить создаваемый по умолчанию интерфейс IPntExmpl. В конечном итоге IDL-файл должен приобрести примерно такой вид (естественно, UUID’ы будут другими):

import "oaidl.idl";
import "ocidl.idl";
[
  uuid(C1C0899D-2365-11D5-A8AA-0050BAC0EF0F),
  version(1.0),
  helpstring("PainterExample 1.0 Type Library")
]
library PAINTEREXAMPLELib
{
  importlib("stdole32.tlb");
  importlib("stdole2.tlb");
  importlib("ascDBShared.tlb");
  importlib("ascDBControls.tlb");
  [
    uuid(C1C089AA-2365-11D5-A8AA-0050BAC0EF0F),
    helpstring("PntExmpl Class")
  ]
  coclass PntExmpl
  {
    [default] interface IascPainter;
  };
};

В заголовочный файл COM-класса (в данном случае «PntExmpl.h») надо добавить включения заголовочных файлов с описанием всех необходимых для работы отрисовщика шаблонов и описанием компонентной категории отрисовщика. Кроме этого нужно модифицировать описание класса таким образом, чтобы он наследовался от шаблона IascPainterWithBasePropImpl, включался в компонентную категорию ascPainters, содержал в COM_MAP интерфейс IascPainter и реализовал метод этого интерфейса DrawCell.

В конечном итоге заголовочный файл COM-класса отрисовщика должен выглядеть примерно так:

#include "resource.h"       // main symbols
#include "ascPainterWithBasePropImpl.h"
#include "ascCategories_i.h"
#include "ascEditorsAndPainters.h"
class ATL_NO_VTABLE CPntExmpl : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CPntExmpl, &CLSID_PntExmpl>,
  public IascPainterWithBasePropImpl<CPntExmpl>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_PNTEXMPL)
DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CPntExmpl)
  COM_INTERFACE_ENTRY(IascPainter)
  COM_INTERFACE_ENTRY2(IDispatch, IascPainter)
END_COM_MAP()

BEGIN_CATEGORY_MAP(CPntExmpl)
  IMPLEMENTED_CATEGORY(ASC_CATEGORY_ID(ascPainters))
END_CATEGORY_MAP()

HRESULT FinalConstruct(){  return S_OK;}
STDMETHOD(DrawCell)(/*[in]*/ ASC_DRAW_CELL_STRUCT * ascDrawCellStruct)
{
  // Здесь нужно будет написать код отрисовки ячейки
  // ...
  return S_OK;
}
public: // IPntExmpl
};

Единственное, что хорошо бы не забыть сделать в cpp-файле COM-класса отрисовщика – это добавить включение файла "ascDBControls_i.c", в котором описаны необходимые ему константы (GUID).

В конечном итоге cpp-файл COM-класса отрисовщика должен выглядеть примерно так:

#include "stdafx.h"
#include "PainterExample.h"
#include "PntExmpl.h"
#include "ascDBControls_i.c"

Остается только зарегистрировать и подключить наше творение. После компиляции и линковки проекта VC зарегистрирует библиотеку (dll) автоматически.

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

Для регистрации в базе данных ascDb используется snap-in MMC, (см. рисунок 2). Этот snap-in доступен в ascDb Console (Programs\ascDb\Tools). Регистрация отрисовщиков выполняется на закладке "Отрисовщики". Колонка "Имя" позволяет задать отрисовщику короткое пользовательское имя (псевдоним). Колонка "Объект" позволяет выбрать ProgID СОМ-объекта отрисовщика из числа зарегистрированных в системе в компонентной категории "ascPainters". В колонке "Способ хранения" можно выбрать способ хранения информации о СОМ-объекте: по ProgID или по CLSID.

В реальных условиях, конечно, вручную ничего делать не придется. Обычно для создания нового отрисовщика применяется визард «Painter» из категории ascDb мастера ATL Object Wizard. Этот визард создает код-заглушку, отрисовщик, который умеет отображать значения колонок типа long и unsigned long в шестнадцатеричном формате (аналог отрисовщика HexPainter из стандартной поставки ascDb). Этот код можно изменять как заблагорассудится. В расширенную поставку ascDb входят исходные коды всех отрисовщиков и редакторов, входящих в стандартную поставку.

Поскольку ascDb-визард «Painter» почти полностью повторяет стандартный ATL Object Wizard для Simple Object (из категории Objects), подробное его описание здесь излишне.

Создание новых редакторов

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

Редактор не обязан реализовывать специальный COM-интерфейс ascDb-редакторов IascEditor, и создается как обыкновенный элемент управления ActiveX. Реализация специальных COM-интерфейсов IascEditor и IascEditorInputControl всего лишь желательна, так как позволяет существенно расширить функциональность редактора (например, позволяет ему обрабатывать сообщения от клавиатуры и мыши в неактивном состоянии). Описания интерфейсов IascEditor и IascEditorInputControl, а также используемых в их методах перечислений, приведены ниже:

[
  uuid(77D3E341-7600-49B6-AE8C-1F2223914451), 
  version(1.0)
]
typedef enum ASC_EDITOR_ACTIVATE_TYPE {
  [helpstring("Обычная активация( по f2 или программно)")]
  aeatNormal,
  [helpstring("активация от клика мыши")]
  aeatMouse,
  [helpstring(
    "активация с клавиатуры (при попытке пользователя ввести некие данные)")
  ]
  aeatKeyboard
} ASC_EDITOR_ACTIVATE_TYPE;

[
  uuid(2E688198-F0F7-471E-8303-1DF2DF328BF9), 
  version(1.0)
]
typedef enum ASC_STEAL_ACTION {
  asaCancel,
  asaOK,
  asaCreateCopy
} ASC_STEAL_ACTION;
[
  uuid(9D1CA45E-8DAB-492C-AF51-C16BBEA9FF87), 
  version(1.0)
]
typedef enum ASC_CHANGE_VALUE_ACTION {
  acvaCancel,
  acvaOK,
  acvaOK_CloseEditor
} ASC_CHANGE_VALUE_ACTION;

typedef enum DISPID_EDITOR {
  DISPID_EDITOR_EDITBEGIN = 1,
  DISPID_EDITOR_EDITEND,
  DISPID_EDITOR_EDITCANCEL,
  DISPID_EDITOR_BEFORECHANGEVALUE,
  DISPID_EDITOR_AFTERCHANGEVALUE,
  DISPID_EDITOR_PRETRANSLATEACCELERATOR,
  DISPID_EDITOR_PRETRANSLATEMOUSE,
  DISPID_EDITOR_BEFORESTEAL,
  DISPID_EDITOR_GETCURSOR
} DISPID_EDITOR;

[
  object,
  uuid(24FE6130-D819-11d3-AEBC-004095E1F072),
  dual,
  helpstring("IascEditor интерфейс редактора ascDB."),
  pointer_default(unique)
]
interface IascEditor : IDispatch
{
  [
   id(DISPID_EDITOR_EDITBEGIN), 
   helpstring("Вызывается визуальным элементом "
  "управления (таким как ascDataGrid или ascDataField) в начале "
  "редактирования.")
  ] 
  HRESULT EditBegin(
    IascColumn * Column, 
    ASC_EDITOR_ACTIVATE_TYPE ActivateType);

  [
    id(DISPID_EDITOR_EDITEND), helpstring("Вызывается визуальным элементом "
    "управления (таким как ascDataGrid или ascDataField) в случае завершения" 
    "редактирования.")
  ] 
  HRESULT EditEnd();

  [
    id(DISPID_EDITOR_EDITCANCEL), helpstring("Вызывается визуальным элементом"
    " управления (таким как ascDataGrid или ascDataField), если требуется"
    " прервать редактирование.")
  ] 
  HRESULT EditCancel();

  [
    id(DISPID_EDITOR_BEFORESTEAL), helpstring("Вызывается перед тем, как "
    "редактор будет отобран для передачи другому элементу управления. "
    "Если вернуть asaOK - редактор будет отобран; если asaCreateCopy – будет "
    "создана временная копия редактора (необходимо при открытии вложенного "
    "редактора (Lookup))")
  ]
  HRESULT BeforeSteal([out, retval] ASC_STEAL_ACTION * StealAction);

  [
    id(DISPID_EDITOR_BEFORECHANGEVALUE), helpstring("Вызывается, когда в "
    "колонке, над которой открыт редактор, изменяется значение.")
  ] 
  HRESULT BeforeChangeValue(ASC_CHANGE_VALUE_ACTION *Action);

  [
    id(DISPID_EDITOR_AFTERCHANGEVALUE), helpstring("Вызывается, когда значение"
    "в колонке было успешно изменено.")
  ] 
  HRESULT AfterChangeValue();
};

typedef
[
  uuid(8A7C81D9-4D6A-4C8F-A1E8-0A19AC891E8C),
  helpstring("Структура метода PreTranslateMouse.")
]
struct ASC_DB_MOUSE_INFO
{
  long CtrlWidth;
  long CtrlHeight;
  long MouseLeft;
  long MouseTop;
  short Shift; 
  [ helpstring("Колонка, над которой производится действие мышью.") ]
  IascColumn * ascColumn;
} ASC_DB_MOUSE_INFO;

[uuid(A9458DC5-4D72-4BCC-8B54-BE5358637919), version(1.0)]
typedef enum ASC_MOUSE_POINTER_CONSTAS 
{
  [helpstring("Определяется родителем.")]
  ampcDefault                =                0,
  ampcArrow                  =                1,                //                IDC_ARROW
  ampcCrosshair              =                2,                //                IDC_CROSS
  ampcIbeam                  =                3,                //                IDC_IBEAM
  ampcIconPointer            =                4,                //                OBSOLETE = IDC_ARROW
  ampcSizePointer            =                5,                //                OBSOLETE = IDC_SIZEALL
  ampcSizeNESW               =                6,                //                IDC_SIZENESW
  ampcSizeNS                 =                7,                //                IDC_SIZENS
  ampcSizeNWSE               =                8,                //                IDC_SIZENWSE
  ampcSizeWE                 =                9,                //                IDC_SIZEWE
  ampcUpArrow                =                10,               //                IDC_UPARROW
  ampcHourglass              =                11,               //                IDC_WAIT
  ampcNoDrop                 =                12,               //                IDC_NO
  ampcArrowHourglass         =                13,               //                IDC_APPSTARTING
  ampcArrowQuestion          =                14,               //                IDC_HELP
  ampcSizeAll                =                15,               //                IDC_SIZEALL
  ampcHand                   =                50,               //                IDC_HAND (WINVER >= 0x0500)
  [
    helpstring("Иконка курсора мыши определяется пользователем. Курсор "
    "устанавливается вручную через SetCursor API")
  ]
  ampcCustomOnAPI          =                98,
  [
    helpstring("Иконка курсора мыши определяется пользователем. "
    "Курсор передается в качестве параметра в виде IPicture.")
  ]
  ampcCustom                =                99
} ASC_MOUSE_POINTER_CONSTAS;

[
  object,
  uuid(F288D834-CA62-4135-90CA-B7A817DC8A71),
  oleautomation,
  helpstring("Запрашивается у кэшированной копии редактора для обработки "
   "ввода пользователя (клавиатуры и мыши) без активизации редактора. "
   "Методы этого интерфейса не должны использовать хранящиеся в "
   "редакторе данные."),
  pointer_default(unique)
]
interface IascEditorInputControl : IUnknown
{
  [
    id(DISPID_EDITOR_PRETRANSLATEACCELERATOR), 
    helpstring("Позволяет определить, будет ли элемент управления этого "
    "класса обрабатывать нажатие на клавишу или комбинацию клавиш.")
  ] 
  HRESULT PreTranslateAccelerator([in] short KeyCode, [in] short Shift, 
    [in] IascColumn * Column, [out, retval] VARIANT_BOOL * ProcessEditing);

  [
    id(DISPID_EDITOR_PRETRANSLATEMOUSE), helpstring("Позволяет определить, "
    "будет ли элемент управления обрабатывать события мыши и, если будет, "
    "то каким образом")
  ] 
  HRESULT PreTranslateMouse(
    [in] ASC_DB_MOUSE_INFO * admi,
    [out, retval] ASC_PROCESS_PRETRANSLATE_MOUSE * ProcessEditing);

  [
    id(DISPID_EDITOR_GETCURSOR), 
    helpstring("Возвращает значение определяющее, какой курсор надо "
    "показывать над областью ячейки, над которой находится курсор.")
  ] 
  HRESULT GetCursor([in] ASC_DB_MOUSE_INFO * admi, 
    [out] ASC_MOUSE_POINTER_CONSTAS * MousePointer, 
    [out] IPictureDisp ** MouseIcon);
};

Реализовав в классе редактора методы EditBegin, EditEnd и EditCancel интерфейса IascEditor, можно получить контроль над процессом редактирования данных текущей ячейки. Метод BeforeChangeValue вызывается у редактора перед тем, как будет изменено значение в ячейке, на которой открыт редактор. Метод AfterChangeValue вызывается после того, как значение в ячейке было успешно изменено.

Если интерфейс IascEditor реализован в классе редактора, то в паре с ним желательно реализовать интерфейс IascEditorInputControl, с помощью которого можно обеспечить обработку пользовательского ввода (с клавиатуры и мыши) без активизации самого редактора. Этот интерфейс должен быть реализован как stateless, то есть не зависеть от состояния объекта, в котором он реализуется. Дело в том, что этот интерфейс запрашивается у первой попавшейся копии редактора, которая, в свою очередь, может быть открыта для редактирования другой колонки. Реализовав метод GetCursor этого интерфейса, можно изменить форму курсора мыши над ячейкой без активации редактора. Метод PreTranslateAccelerator вызывается, когда пользователь нажимает некоторую комбинацию клавиш. Активная колонка передается в качестве параметра этого метода. Задача этого метода – определить, нужно ли начинать редактирование. Если нужно начать редактирование, нужно установить в True параметр ProcessEditing (если редактор создается на VB, возвратить True в качестве возвращаемого значения функции). Аналогичный метод для мыши называется PreTranslateMouse.

Чтобы удовлетворять минимальным требованиям редактора ascDb, элементу управления AсtiveX не обязательно реализовывать интерфейс IascEditor. Он должен всего лишь предоставлять пользовательский интерфейс для редактирования данных и иметь свойство по умолчанию (в IDL оно определяется как id(DISPID_VALUE)), через которое можно было бы получить доступ к редактируемому значению. Таким требованиям удовлетворяют многие уже существующие элементы управлений ActiveX (например, среди редакторов, зарегистрированных в ascDb, можно найти Microsoft Date and Time Picker).

Как и в случае с созданием нового отрисовщика, свой редактор можно произвести на свет с помощью соответствующего ascDb-визарда (рисунок 23). Однако, целесообразно сначала выполнить все необходимые действия вручную, чтобы избежать неясностей, которые могут возникнуть при его применении.

Рисунок 23. ATL Object Wizard с запущенным мастером ascDb (ascDb Editor wizard).

Для примера будет построен простейший редактор, содержащий кнопку (Button).

Если ascDb-редактор создается в уже существующем проекте, нужно убедиться, что к этому проекту подключена библиотека ascLib (как это уже было описано при создании отрисовщика).

Затем в “stdafx.h” нужно добавить (после “ascLib.h)”:

#include "ascDBShared.h"
#include "ascDBControls.h"

и убедиться, что после включения “atlbase.h” добавлено:

#include “atlapp.h”
using namespace WTL;

В файл “stdafx.cpp” нужно добавить (после “ascLib.h)”:

#include "ascDBControls_i.c"

Учитывая, что редактор – это элемент управления ActiveX, надо сначала создать такой компонент с помощью мастера ATL Object Wizard. В отличие от создания отрисовщика, в этот раз проще создать стандартный класс типа Full Control из категории COM-объектов Controls. Причем, раз уж создаваемый пример редактора было решено сделать кнопкой, то и создаваемый компонент лучше унаследовать от базового элемента «Button» (в ATL-визарде).

Назовем новый редактор «EdtExmpl». Первым шагом будет добавление свойства по умолчанию Value в интерфейс IEdtExmpl (это делается в idl-файле проекта). В результате описание интерфейса будет выглядеть так:

[
  object,
  uuid(7FF4C1A7-2434-11D5-A8AB-0050BAC0EF0F),
  dual,
  helpstring("IEdtExmpl Interface"),
  pointer_default(unique)
]
interface IEdtExmpl : IDispatch
{
  [propget, id(DISPID_VALUE), helpstring("property Value")] 
  HRESULT Value([out, retval] BSTR *pVal);

  [propput, id(DISPID_VALUE), helpstring("property Value")] 
  HRESULT Value([in,optional] BSTR newVal);
};

Следующим шагом должно идти включение в заголовочный файл создаваемого редактора заголовочного файла с описанием компонентной категории редактора добавлением следующей строки:

#include "ascCategories_i.h".

Затем нужно модифицировать описание COM-класса таким образом, чтобы он включался в компонентную категорию ascEditors, а также реализовывал методы get/put для свойства по умолчанию Value. Для этого к описанию класса нужно добавить CATEGORY_MAP и внести в него соответствующую строчку. Это средствами ATL делается так:

BEGIN_CATEGORY_MAP(CEdtExmpl)
  IMPLEMENTED_CATEGORY(ASC_CATEGORY_ID(ascEditors))
END_CATEGORY_MAP()

И, наконец, остается реализовать метод Value интерфейса IEdtExmpl (основной интерфейс создаваемого редактора), который в дальнейшем можно заполнить требуемой функциональностью:

STDMETHOD(get_Value)(/*[out, retval]*/ BSTR *pVal)
{
// Здесь нужно возвращать редактируемое значение...
return S_OK;
}
STDMETHOD(put_Value)(/*[in]*/ BSTR newVal)
{
// Здесь нужно кэшировать редактируемое значение
//...
return S_OK;
}

После компиляции проекта надо зарегистрировать библиотеку в системе, после чего с помощью ascDb-консоли включить новый редактор в список редакторов ascDb (аналогично тому, как регистрировался отрисовщик в вышеописанном примере, но на закладке «Редакторы»).

Процесс создания редактора можно значительно упростить за счет использования мастера «ascDb Editor Wizard». При запуске этого мастера появляется диалог, представленный на рисунке 23. В выпадающем списке можно выбрать тип базового COM-класса. Всего предлагаются три таких типа:

  1. Базовый класс («Пустой каркас»). Генерируется пустой элемент управления ActiveX (без поддержки пользовательского интерфейса). Это самый «легкий» из классов, пригодных для создания редактора, требующий, однако, большего вклада программиста в разработку.
  2. «На основе стандартных элементов управления» – генерируется код элемента управления ActiveX на базе одного из стандартных элементов управления Windows.

  3. «Пример редактора на основе ascEditBase». Генерируется COM-класс для элемента управления ActiveX (со встроенным полем редактирования текста). Аналогичен стандартному acsDb-редактору «EditBase».

Заключение

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

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

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


Copyright © 1994-2016 ООО "К-Пресс"