Мир объектов Excel 2000


Построение обработчиков событий и обработка ошибок


Давайте разберемся с деталями построения обработчиков событий объектов ADO. Прежде всего, рассмотрим, как они создаются, в какой модуль проекта их следует поместить. Хотя объекты Recordset и Connection обладают событиями, но появляются они как объекты без событий. Необходимо предпринять обычные для VBA в таких ситуациях действия, чтобы создать объекты с событиями и написать процедуры, обрабатывающие события, - обработчики событий. В первом томе и других книгах серии "Офисное программирование" я подробно рассказывал о том, как это делается. Напомню, что для решения этой задачи нужно выполнить три шага:

  1. Создать на VBA собственный класс с именем, например MyEventsADO, в котором описать вложенные объекты - Connection WithEvents и Recordset WithEvents.
  2. Создать обработчики нужных событий для данных объектов в классе MyEventsADO. Заметьте, после объявления переменных With Events в этом классе появится возможность написания обработчиков событий, следуя обычной технологии.
  3. В подходящем месте, например в уже существующем модуле TestingADO, создать экземпляры класса MyEventsADO и связать их с глобальными или локальными объектами Rst1 и Con1, задающими набор записей и соединение.

Чтобы сделать дальнейшее описание конкретным, давайте рассмотрим создание обработчика события WillChangeField объекта Recordset, которое, напомню, появляется при попытках изменить значение поля в записи набора, но перед тем, как это изменение реально произойдет. Вот как выглядит модуль класса MyEventsADO, содержащий обработчик этого события:

'Класс MyEventsADO, определяющий события ADO Public WithEvents EvRst As ADODB.Recordset Public WithEvents EvCon As ADODB.Connection

Private Sub EvRst_WillChangeField(ByVal cFields As Long, _ ByVal Fields As Variant, adStatus As ADODB.EventStatusEnum, _ ByVal pRecordset As ADODB.Recordset) 'Печать параметров обработчика события MsgBox "Номер поля = " & cFields & vbCrLf & _ "Значение поля = " & Fields(cField) & vbCrLf & _ "Позиция записи в наборе = " & _ pRecordset.AbsolutePosition & vbCrLf & _ "Статус выполняемой операции = " & adStatus 'Изменение статуса приведет к появлению ошибки 'при попытке изменить значение поля, обработка которой 'позволит отменить операцию. If IsNumeric(Fields(cField)) Then If Fields(cField) > 75 Then adStatus = adStatusCancel End If End Sub


Первым делом я включил в обработчике события WillChangeField печать всех его параметров на входе. Обратите внимание, обработчику события передается указатель на сам объект Recordset, поэтому доступна практически любая информация, связанная с этим объектом. Я, например, использовал его, чтобы вывести на печать позицию записи в наборе, поля которой обновляются. Во второй части обработчика я изменяю значение статуса при выполнении некоторого условия на обновляемое поле. Делаю это для того чтобы реализовать возможность отмены изменения значения, когда в результате анализа, проведенного в обработчике, принимается решение о нежелательности выполнения изменений. Заметьте, для этого свойству статус, которое имело значение adStatusOk, уведомляющее о том, что изменения возможны, я присваиваю значение adStausCancel. В этом случае при выходе из обработчика возникнет ошибка в операторе, производящем изменения. Обработав соответствующим образом эту ошибку, я смогу выполнить поставленную задачу.

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

Public Sub CreateEvents() 'Объект с событиями Dim imEvRst As New MyEventsADO 'добавление и изменение записей базы данных Dim recExist As Boolean ' Связывание объекта с событиями Set imEvRst.EvRst = Rst1 'Создать соединение CreateConnection 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic 'Изменение записей recExist = False .MoveFirst Do While Not .EOF 'Обработка текущей записи On Error Resume Next If !Название = "Офисное программирование" Then recExist = True If ![Год издания] < 2000 And !Цена < 100 Then MsgBox "Старая цена =" & Str(!Цена) !Цена = !Цена * 2 .Update End If .MoveNext Loop If Not recExist Then .AddNew !Автор = "Владимир Биллиг" !Название = "Офисное программмирование" ![Год издания] = 2001 ![Число страниц] = 599 !Цена = 150 .Update End If End With End Sub



Обратите внимание, первым делом я объявляю объект imEvRst созданного класса MyEventsADO и связываю его с глобальным объектом Rst1, после чего последний начнет реагировать на события. Сколько раз будет появляться событие WillChangeField? Очевидно, при каждом выполнении оператора !Цена = !Цена * 2, изменяющего значение соответствующего поля записи. Вот как выглядят окошки функции MsgBox, одно из которых открывается перед выполнением этого оператора, а второе в обработчике события в момент выполнения оператора:


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

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

Важную роль в этой процедуре играет и оператор On Error Resume Next. Всякий раз, когда в обработчике события изменяется значение переменной adStatus, возникнет ошибка при выполнении оператора, изменяющего значение поля и послужившего причиной возникновения события. Оператор OnError позволяет в этом случае обойти выполнение оператора и, тем самым, избежать изменения значения, что и хотелось. Взгляните, как выглядит окно, уведомляющее об ошибке, появляющееся, если в процедуре закомментировать оператор On Error:


Рис. 6.2.  Окно, уведомляющее о возникновении ошибки при изменении значения

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



Первым делом я включил в обработчике события WillChangeField печать всех его параметров на входе. Обратите внимание, обработчику события передается указатель на сам объект Recordset, поэтому доступна практически любая информация, связанная с этим объектом. Я, например, использовал его, чтобы вывести на печать позицию записи в наборе, поля которой обновляются. Во второй части обработчика я изменяю значение статуса при выполнении некоторого условия на обновляемое поле. Делаю это для того чтобы реализовать возможность отмены изменения значения, когда в результате анализа, проведенного в обработчике, принимается решение о нежелательности выполнения изменений. Заметьте, для этого свойству статус, которое имело значение adStatusOk, уведомляющее о том, что изменения возможны, я присваиваю значение adStausCancel. В этом случае при выходе из обработчика возникнет ошибка в операторе, производящем изменения. Обработав соответствующим образом эту ошибку, я смогу выполнить поставленную задачу.

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

Public Sub CreateEvents() 'Объект с событиями Dim imEvRst As New MyEventsADO 'добавление и изменение записей базы данных Dim recExist As Boolean ' Связывание объекта с событиями Set imEvRst.EvRst = Rst1 'Создать соединение CreateConnection 'Создать команду 'задание свойств объекта Command Cmd1.ActiveConnection = Con1 Cmd1.CommandText = "Select * From [Книги]" Cmd1.CommandType = adCmdText 'Открытие обновляемого объекта Recordset With Rst1 .Open Source:=Cmd1, CursorType:=adOpenDynamic, _ LockType:=adLockOptimistic 'Изменение записей recExist = False .MoveFirst Do While Not .EOF 'Обработка текущей записи On Error Resume Next If !Название = "Офисное программирование" Then recExist = True If ![Год издания] < 2000 And !Цена < 100 Then MsgBox "Старая цена =" & Str(!Цена) !Цена = !Цена * 2 .Update End If .MoveNext Loop If Not recExist Then .AddNew !Автор = "Владимир Биллиг" !Название = "Офисное программмирование" ![Год издания] = 2001 ![Число страниц] = 599 !Цена = 150 .Update End If End With End Sub



Обратите внимание, первым делом я объявляю объект imEvRst созданного класса MyEventsADO и связываю его с глобальным объектом Rst1, после чего последний начнет реагировать на события. Сколько раз будет появляться событие WillChangeField? Очевидно, при каждом выполнении оператора !Цена = !Цена * 2, изменяющего значение соответствующего поля записи. Вот как выглядят окошки функции MsgBox, одно из которых открывается перед выполнением этого оператора, а второе в обработчике события в момент выполнения оператора:


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

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

Важную роль в этой процедуре играет и оператор On Error Resume Next. Всякий раз, когда в обработчике события изменяется значение переменной adStatus, возникнет ошибка при выполнении оператора, изменяющего значение поля и послужившего причиной возникновения события. Оператор OnError позволяет в этом случае обойти выполнение оператора и, тем самым, избежать изменения значения, что и хотелось. Взгляните, как выглядит окно, уведомляющее об ошибке, появляющееся, если в процедуре закомментировать оператор On Error:


Рис. 6.2.  Окно, уведомляющее о возникновении ошибки при изменении значения

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


Содержание раздела