Visual2000 · Архив статей А.Колесова & О.Павловой

Элементы управления VB 6.0 для работы с ADO. Часть 2

Андрей Колесов, Ольга Павлова

© 1999, Андрей Колесов, Ольга Павлова
Авторский вариант. Статья была опубликована c незначительной литературной правкой в журнале "КомпьютерПресс" № 08/99, c. 161-168.

VBP-проекты примеров, приведенных в данной статье, вы можете найти на Web-узле по адресу: www.visual.2000.ru/develop/vb/source/.


Элементы управления DataCombo, DataList и DataGrid

Эти элементы управления являются модифицированными вариантами DBCombo, DBList и DBGrid, которые поставлялись в более ранних версиях VB. Как и ADO Data, они реализованы в виде ActiveX-компонентов, входящих в состав всех редакций VB 6.0. Поэтому для их размещения в окне инструментов необходимо воспользоваться командой Project|Components и в списке компонентов диалогового окна Components установить флажки Microsoft DataGrid Control 6.0 и Microsoft DataList Controls 6.0.

Элемент управления DataGrid - это связанная с данными сетка, набор строк и столбцов которой соответствует записям и полям объекта Recordset. С его помощью конечный пользователь может читать и корректировать информацию баз данных. Установка ссылки в свойстве DataSource обеспечивает автоматическое заполнение сетки, в том числе и заголовков ее столбцов, после чего можно работать с этими данными в режиме обычной электронной таблицы.

DBGrid из VB 5.0 программно совместим с элементом управления DataGrid, но обладает одним важным отличием: он имеет свойство DataMode, поддерживающее два режима - "связанный" (bound mode) и "несвязанный" (unbound mode), в то время как DataGrid не поддерживает второй режим.

Связанные с данными окно списка DataList и комбинированное окно Data Combo очень похожи на стандартные элементы управления ListBox и ComboBox, однако в них есть специальные функции для работы с базами данных. Отличительная черта элементов управления DataCombo и DataList - это их способность получать доступ к двум разным таблицам и связывать данные из первой таблицы с полем во второй. Это становится возможным благодаря использованию двух источников данных, например элемента управления ADO Data или Data Environment. Такие возможности делают их незаменимыми при разработке приложений типа "таблиц поиска".

В начало статьи

Связывание двух таблиц базы данных

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

Например, база данных Northwind, поставляемая вместе с VB, хранит координаты поставщиков в таблице под именем Suppliers. Данные о поставщике хранятся в отдельной записи, состоящей из фиксированного числа полей, в которых размещается название фирмы (CompanyName), ее адрес (Address) и пр. Кроме того, каждая фирма (каждая запись) имеет уникальный цифровой код, который хранится в поле SupplierID.

Вторая таблица носит имя Products, в которой хранится описание продуктов: название (ProductName), стоимость (UnitPrice) и т.д. Поставщик же продуктов в этой таблице представлен кодом в поле SupplierID. То есть если необходимо узнать подробную информацию о поставщике, вы должны найти соответствующую запись в таблице Suppliers с таким же кодом. Таким образом, параметр SupplierID связывает информацию в двух таблицах, при этом в таблице Suppliers он выступает в качестве идентификатора, а в таблице Products - в качестве ключа.

Вопросы связывания двух таблиц и создания законченного приложения, работающего с такой базой данных, приведены в статье "Создание персональной адресной книги на VB 5.0". Сейчас же мы рассмотрим только небольшой пример приложения, который покажет возможности совместного использования DataGrid и DataCombo при работе с базой данных Northwind.

В начало статьи

Пример 2. Создание приложения с использованием DataGrid и DataCombo

Шаг 1. В VB создайте новый проект Standard EXE. Поместите на форму элемент управления ADO Data и установите его свойства следующим образом:

Name AdoDataSource
ConnectionString Nwind.udl (указав также путь к источнику данных)
RecordSource Select * From Products
Caption Таблица Products

Шаг 2. Разместите на форме элемент управления DataGrid и установите его свойства:

Name

grdProducts
DataSourceAdoDataSource
CaptionПродукты

Таким образом, мы связали сетку с элементом управления ADO Data, который, в свою очередь, связан с таблицей Products из базы данных Northwind (через созданный ранее источник данных Nwind.udl).

Шаг 3. Запустим приложение и мы увидим в сетке содержимое таблицы Products (рис. 8). Щелкая стрелки элемента управления adoDataSource, мы можем перемещаться по строкам таблицы, изменяя позицию текущего курсора.

Рис. 8

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

Шаг 4. Поместите на форму еще один элемент управления ADO Data и установите его свойства:

Name

adoRowSource
ConnectionString Nwind.udl
RecordSource Select CompanyName, SupplierID From Suppliers
Caption Suppliers
Visible False

Шаг 5. Разместите на форме элемент управления DataCombo и установите его свойства:

Name

dcbSuppliers
DataSourceadoDataSource
DataFieldSupplierID
RowSourceadoRowSource
ListFieldCompanyName
BoundColumnSupplierID

Шаг 6. Запустим приложение. Теперь, передвигаясь по таблице Products, мы видим в окне элемента dcbSuppliers название компании-поставщика для текущего продукта (рис. 9).

Рис. 9

Более того, чтобы изменить имя поставщика для какого-либо продукта, нужно просто раскрыть список и выбрать другую фирму. При этом автоматически изменяется значение, записанное в поле SupplierID (рис. 10). Обратите внимание, что раньше для продукта Aniseed Syrup был указан поставщик Exotic Liquids (код 1), а теперь мы установили для него другую фирму - Pavlova Ltd. (код 7).

Рис. 10

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

Private Sub Form_Load() 
  ' Сделать поле SupplierID невидимым в
  ' элементе управления DataGrid
  grdProducts.Columns("SupplierID").Visible = False
End Sub

Теперь столбец с кодом поставщика будет невидимым и недоступным для прямого исправления (да он теперь и не нужен), рис. 11.

Рис. 11

Обратите внимание, что аналогичную операцию можно было бы выполнить для поля CategoryID для вывода более подробной информации о категории продукта (для определения, например, таможенных пошлин). А вот смысл поля ProductID в таблице Products совсем иной. Если SupplierID и CategoryID являются здесь ключами, то ProductID - уникальный идентификатор, который может использоваться для получения информации о продукте, например, из таблицы сведений о сделках. Это, в частности, означает, что мы можем в таблице Products менять значения ключей, но корректировать идентификатор никто не имеет права (он формируется автоматически). Поэтому лучше в процедуре Form_Load добавить еще одну строку, чтобы сделать поле ProductID недоступным:

grdProducts.Columns("ProductID").Visible = False

В начало статьи

Как мы использовали DataCombo

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

adoDataSource --     Свойства dbcSuppiers     --- adoRowSource
(Products)     |                              |   (Suppliers)
==============================================================
               |                              |
               --- DataSource: adoDataSource  |
SupplierID ------- DataField: SupplierID      |   ContactName
ProductName        RowSource: adoRowSource  ---   Address
ProductID          BoundColumn: SupplierID ------ SupplierID
UnitPrice          ListField: CompanyName  ------ CompanyName
UnitsInStock

Таким образом свойство ListField определяет, какое поле реально выводится элементом управления на экран (в нашем случае это название поставщика). А свойство BoundColumn определяет, какое поле в таблице Suppliers предоставляет фактическое значение таблице Products. Помните при этом, что поле SupplierID в таблице Suppliers ни в коем случае не следует редактировать - его значение записывается в поле, указанное свойством DataField (в нашем случае это поле SupplierID в таблице Products).

Следующая таблица содержит упомянутые здесь свойства и способы их использования:

DataSource Элемент управления данными, связанный с DataCombo или DataList
DataField Поле в наборе записей элемента управления, заданного в свойстве DataSource. Определяет, какой элемент списка будет выделен. Служит для обновления списка
RowSource Элемент управления данными, который будет использоваться для заполнения списка
BoundColumn Поле в наборе записей элемента управления, заданного в свойстве RowSource. Должно быть того же типа, что и DataField
ListField Поле в наборе записей элемента управления, заданного в свойстве RowSource. Будет использоваться при заполнении списка

Примечание. DataCombo и DataList можно использовать и с одним элементом управления данными. Для этого необходимо установить оба свойства - DataSource и RowSource - как один и тот же элемент управления данными, а свойства DataField и BoundColumn - как одно и то же поле в наборе записей этого элемента управления. Тогда список будет заполнен значениями ListField из обновленного набора записей. В случае же если определено свойство ListField, но не указано свойство BoundColumn, то последнее будет установлено как поле ListField.

В начало статьи

Пример 3. Доступ к данным с помощью модуля класса

Для доступа к данным, формат которых не поддерживается драйвером ODBC, можно создать собственный источник данных с помощью модуля класса. Мы продемонстрируем это на примере элемента управления DataGrid, который будет связываться с этим модулем при помощи свойства DataSource.

В начало статьи

Создание модуля класса

Шаг 1. Создайте новый проект Standard EXE. Поместите на форму элемент управления DataGrid. Установите ссылку на библиотеку Microsoft ActiveX Data Objects 2.0 Library командой Project|References. Измените названия проекта и созданной формы на Example3.

Шаг 2. Добавьте в проект модуль класса командой Project|Add Class Module. В окне Properties созданного модуля класса измените его название на NamesData, а затем щелкните свойство DataSourceBehavior и измените его на vbDataSource. Последняя установка нужна, чтобы в модуле класса появилась дополнительная событийная процедура GetDataMember, через которую и будут обрабатываться запросы потребителя данных.

Шаг 3. В разделе Declarations модуля класса создайте переменную ADODB.Recordset:

Option Explicit 
Private WithEvents rsNames As ADODB.Recordset

Здесь ключевое слово WithEvents позволяет запрограммировать события объекта Recordset.

Шаг 4. А теперь для события Initialize модуля класса введем код, приведенный в листинге 1. С его помощью создается набор записей Recordset, в котором определяются два поля, а затем формируются десять записей. Для события GetDataMember введите следующий код:

Private Sub Class_GetDataMember(DataMember _ 
  As String, Data As Object)
  Set Data = rsNames
End Sub

Этот код возвращает объект Recordset каждый раз при вызове события.

В начало статьи

Связывание DataGrid с источником данных

Шаг 5. Теперь нам нужно связать DataGrid с созданным источником данных. Обратите внимание, что мы не можем указать источник, установив свойство DataSource в окне Properties. Такую установку можно произвести только программным образом. Для этого в модуле кода формы объявите переменную объекта класса:

Option Explicit 
Private datNames As NamesData

Затем в событии Load формы введите следующий код, чтобы установить свойство DataSource элемента управления DataGrid1 как объект класса:

Private Sub Form_Load() 
  ' Создает новый объект NamesData
  Set datNames = New NamesData
  ' Связывает элемент управления DataGrid1
  ' с новым источником данных
  Set DataGrid1.DataSource = datNames
End Sub

Шаг 6. Запустите проект на выполнение, и вы получите форму наподобие той, что приведена на рис.12.

Рис. 12

В начало статьи

Модификация приложения

Шаг 7. В событии GetDataMember есть аргумент DataMember, позволяющий выбирать нужный набор записей из некоторого списка, определенного в модуле класса. Например, это можно сделать таким образом:

Private Sub Class_GetDataMember(DataMember _ 
  As String, Data As Object)
  Select Case DataMember
    Case "Names"
      Set Data = rsNames
    Case "Dates"
      Set Data = rsDates
    Case Else ' Набор данных по умолчанию
      Set Data = rsYears
  End Select
End Sub

Теперь по умолчанию будет выбираться набор записей rsYears (его, конечно, нужно создать и определить в модуле класса), а не rsNames. Чтобы обращение все же выполнялось к набору rsNames, нужно сначала задать свойство DataMember элемента управления DataGrid1, а уже потом установить DataSource:

DataGrid1.DataMember = "Names" 
Set DataGrid1.DataSource = datNames

Шаг 8. У вас есть также возможность программировать события объекта Recordset. Чтобы увидеть список возможных событий, выберите rsNames в списке Object окна кода модуля класса, а потом откройте список Procedures/Events.

Шаг 9. Вы можете добавить классу свойства. Например, приведенный здесь код обеспечивает создание свойства RecordCount - числа записей в наборе:

Public Property Get RecordCount() As Long 
  RecordCount = rsNames.RecordCount
End Property

Чтобы проверить его работоспособность, разместите на форме элемент управления Label и введите в код процедуры Form_Load еще одну строку:

Label.Caption = "Общее число записей = " & _
  datNames.RecordCount

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

Public Sub Cycle() 
  ' Циклическое перемещение по записям
  ' набора данных
  rsNames.MoveNext
  If rsNames.EOF Then rsNames.MoveFirst
End Sub

Шаг 11. Затем поместите на форму командную кнопку Command1 (назовите ее "Перемещение в цикле") и введите для нее такой код:

Private Sub Command1_Click()
  datNames.Cycle
End Sub

Запустите приложение. Теперь, щелкая кнопку, вы увидите, что курсор текущей записи в DataGrid1 перемещается по сетке.

В начало статьи

Связывание других элементов управления

Шаг 12. Совершенно аналогичным способом можно связать набор записей, определенный в модуле класса, с другими элементами управления, например текстовым окном. Разместите на форме элемент управления Text1 и введите в процедуру Form_Load еще три строки кода:

Text1.DataMember = "Names" 
Set Text1.DataSource = datNames 
Text1.DataField = "Номер"

Запустите приложение. Теперь щелкая кнопку "Перемещение в цикле", вы увидите, что в поле Text1 будет выдаваться поле "Номер" текущей записи (рис. 13).

Рис. 13

В начало статьи

Использование объекта BindingCollection

Шаг 13. Однако более гибким механизмом связывания данных является использование объекта BindingCollection из коллекции Data Binding Collection. (Для этого нужно сначала установить ссылку на библиотеку Microsoft Data Binding Collection). Для работы с ним добавьте в раздел Declarations модуля класса строку:

Private objBindingCollection as BindingCollection

Чтобы протестировать данный режим, разместите на форме еще одно текстовое поле - Text2. Далее в процедуру Form_Load добавьте такой код:

' Связывание данных с помощью объекта 
' BindingCollection 
Set objBindingCollection = New BindingCollection 
objBindingCollection.DataMember = "Names" 
Set objBindingCollection.DataSource = datNames 
'
' Далее выполняется добавление связей для 
' объекта BindingCollection 
' 
' Установка свойства Text 
objBindingCollection.Add Text2, "Text", "Название" 
' Установка свойств элементов управления 
' из столбца "Код" набора записей 
objBindingCollection.Add Text2, "Appearance", "Код" 
objBindingCollection.Add Text1, "Visible", "Код"

Здесь создается экземпляр объекта BindingCollection и устанавливается его свойство DataSource. Затем с помощью метода Add определяются отношения, связывающие свойства разных объектов. Методу Add передаются три обязательных параметра: имя объекта потребителя, свойство этого объекта, которое будет привязано к источнику, и поле источника, которое будет привязано к свойству. Казалось бы, этот механизм похож на тот, что мы уже применяли ранее. Однако в шаге 11 мы только устанавливали связь между объектом и источником данных. Заполнение же свойств элемента управления выполнялось автоматически по некоторому предопределенному алгоритму (в данном случае для Text1 производилась установка его свойства Text).

В случае же с BindingCollection выполняется прямая связь поля набора записей с любым свойством элемента управления (а не только свойством Text). В нашем примере мы производим соответствующую установку свойств Visible для Text1 и Appearance для Text2. В результате после запуска примера, щелкая кнопку "Перемещение в цикле", мы видим не только коррекцию содержимого поля Text2, но также изменение формы данного окна (плоское или объемное изображение) и появление/исчезновение изображения окна Text1 (рис. 14).

Рис. 14

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

Рис. 15

В начало статьи

Создание потребителя данных

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

Шаг 14. Создайте еще один модуль класса и установите его свойства Name как MyTarget и DataBindingBehavior как vbSimpleBound. В его секции Declarations определите переменную

Private NameField$

Добавьте в этот модуль класса пару процедур Property Get/Property Let для общего свойства MyName:

Public Property Get MyName() As String 
  MyName = NameField$
End Property

Public Property Let MyName(MyNameField As String) 
  NameField$ = MyNameField$
  ' Вывод полученного значения в окно Immediate
  Debug.Print NameField$
End Property

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

Шаг 15. Теперь выберите форму и введите в ее раздел Declarations:

Private objConsumer as MyConsumer

а в код процедуры Form_Load следующий код:

Set objConsumer = New MyConsumer 
' Связываем поле "Название" со свойством "MyName" 
' объекта MyConsumer 
objBindingCollection.Add objConsumer, "MyName", "Название"

Шаг 16. Запустите проект и сделайте так, чтобы было видно окно Immediate. Теперь, нажимая кнопку "Перемещение в цикле", убедитесь, что одновременно с обновлением информации в окнах формы будет выдаваться значение обновляемого свойства MyName класса MyConsumer (рис. 16).

Рис. 16

В начало статьи

Листинг 1. Инициализация класса — создание набора данных

Private Sub Class_Initialize() 
  ' Добавляет название нового элемента к
  ' коллекции DataMembers
  DataMembers.Add "Names"
  ' Устанавливает переменную объекта
  Set rsNames = New ADODB.Recordset
  With rsNames
       ' Создает набор записей с тремя полями.
       ' Первая запись и третья записи -
       ' целочисленные данные,
       ' а вторая - строка, имеющая
       ' максимальную длину 256 символов
    .Fields.Append "Номер", adInteger
    .Fields.Append "Название", adBSTR, 255
    .Fields.Append "Код", adInteger
       ' CursorType установлен как
       ' adOpenStatic - обновляемый
       ' мгновенный снимок набора записей
    .CursorType = adOpenStatic
       ' LockType установлен как
       ' adLockOptimistic, чтобы иметь
       ' возможность обновлять набор записей
    .LockType = adLockOptimistic
    .Open ' Открывает набор записей
  End With
  '
  Dim i As Integer
  For i = 1 To 10  ' Добавляет 10 записей
    rsNames.AddNew
    rsNames!Номер = i
    rsNames!Название = "Запись " & i
    rsNames!Код = i Mod 2
    rsNames.Update
  Next i
  ' Переходит к началу набора данных
  rsNames.MoveFirst
End Sub

В начало статьи