Основы офисного программирования и язык VBA

         

Еще раз о "переиспользовании" модулей


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

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


Рис. 2.3.  Форма TwoListsForm документа Excel

В форме есть два списка и 4 командные кнопки. Перенос элементов из одного списка в другой можно осуществлять тремя разными способами:

  • Двойным щелчком по элементу.
  • Выделить предварительно один или несколько элементов списка и затем щелкнуть командную кнопку со значком ">" ("<").
  • Для переноса всех элементов списка нет необходимости в их выделении, - достаточно щелкнуть командную кнопку со значком ">>" ("<<").


Направление переноса задают командные кнопки. Знак на кнопке меняется в зависимости от того, какой из списков выбран - левый или правый. Кнопка "OK" на форме переносит данные из списка на лист Excel, кнопка "Cancel" завершает работу с формой без переноса данных.

Работу обработчиков событий, возникающих при работе с объектами формы, можно было бы без сомнения полностью описать в модуле, связанном с формой. Но мы поступили по-иному и создали стандартный модуль с именем ToolsMod, в который и вынесли содержательную часть обработки. Так что у нас появилось два модуля, - в одном находятся обработчики события, в другом - стандартном сосредоточен содержательный код, выполняющий обработку. Вот код модуля TwoLists, связанного с нашей формой, и содержащий обработчики событий:

Пример 2.1.

(html, txt)

Модуль TwoListsForm, связанный с формой, содержит 9 обработчиков событий, возникающих при работе с самой формой - ее инициализации, так и при работе пользователя с объектами, населяющими форму. Каждый из обработчиков выполняет свои специфические задачи. Когда пользователь переключается на работу с элементами левого или правого списка, то в тот момент, когда список получает фокус, возникает событие Enter. Обработчик события Enter изменяет заголовок у соответствующих командных кнопок, подготавливая, тем самым, передачу данных в нужном направлении. Такое автоматическое изменение направления передачи позволяет уберечь пользователя от ошибочных действий. Но, обратите внимание на главное в этом примере. Для удобства пользователя ему предоставлены три разных способа передачи данных из одного списка в другой. Поскольку обмен данными может вестись в двух направлениях, то при лобовом программировании нужно было бы написать 6 различных макросов. Мы же свели задачу к двум процедурам, вызываемых с разными значениями параметров. Тексты этих процедур мы поместили в стандартный модуль с именем ToolsMod, предполагая возможность его дальнейшего использования. Вот эти тексты:

Пример 2.2.



(html, txt)

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

Завершая разговор о переиспользовании стандартных модулей, предлагаем взглянуть на форму, взятую из совсем другого документа - документа Word.


Рис. 2.4.  Форма TwoLists, взятая из документа Word

Хотя форма и похожа на форму, связанную с рабочей книгой Excel, но, обратите внимание, списки в ней другие, они состоят из одного столбца, а не из двух, как в прошлом случае. Тем не менее, мы спокойно выполнили операции экспорта - импорта стандартного модуля из Excel в Word и без всяких изменений использовали его процедуры MoveSelectedItems и MoveAllItems. Конечно, думая о возможности многократного использования этих процедур, мы заранее побеспокоились о возможности работы с произвольным числом столбцов в списках.

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

Замечание:

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

Public Sub MoveAllItems(ByVal n As Byte, ByVal ListBox1 As Object, _

ByVal ListBox2 As Object)

Вы видите, что тип формальных параметров для ListBox1 и ListBox2 задан как Object и, следовательно, является нетипизированным указателем. Это не очень удобно, так как лишает нас подсказки в момент написания процедуры. Возникает вопрос, можно ли указывать точный тип для подобных формальных параметров. Ответ зависит от приложения. При работе в приложении Word разрешается указывать настоящий, правильный тип ListBox и при этом передача параметров производится корректно. В приложении Excel при написании процедуры также можно указать тип ListBox, но возникнет ошибка в момент передачи параметров.

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





Но, обратите внимание на главное в этом примере. Для удобства пользователя ему предоставлены три разных способа передачи данных из одного списка в другой. Поскольку обмен данными может вестись в двух направлениях, то при лобовом программировании нужно было бы написать 6 различных макросов. Мы же свели задачу к двум процедурам, вызываемых с разными значениями параметров. Тексты этих процедур мы поместили в стандартный модуль с именем ToolsMod, предполагая возможность его дальнейшего использования. Вот эти тексты:

Option Explicit Public Sub MoveSelectedItems(ByVal n As Byte, ByVal ListBox1 As Object, _ ByVal ListBox2 As Object) 'Перемещает выделенные элементы первого списка в конец второго 'с одновременным удалением данных из первого списка. 'Оба списка имеют n столбцов.

Dim RowIndex1 As Byte, RowIndex2 As Byte, i As Byte, j As Byte

'Выборочный обмен данными между списками: ListBox1 -> ListBox2 With ListBox1 RowIndex2 = ListBox2.ListCount RowIndex1 = 0 For i = 0 To .ListCount - 1 If .Selected(RowIndex1) Then 'Создается элемент нового списка и заполняется его первый столбец ListBox2.AddItem .List(RowIndex1) 'Заполняются остальные столбцы элемента списка For j = 1 To n - 1 ListBox2.Column(j, RowIndex2) = .Column(j, RowIndex1) Next j 'Перемещенный элемент удаляется из списка .RemoveItem RowIndex1 RowIndex2 = RowIndex2 + 1 Else RowIndex1 = RowIndex1 + 1 End If Next i End With End Sub

Public Sub MoveAllItems(ByVal n As Byte, ByVal ListBox1 As Object, _ ByVal ListBox2 As Object) ' Перемещает все элементы первого списка в конец второго, ' возможно, не пустого списка с одновременным удалением данных из ' первого списка. ListBox1 -> ListBox2

Dim RowIndex1 As Integer, RowIndex2 As Integer, i As Byte RowIndex2 = ListBox2.ListCount For RowIndex1 = 0 To ListBox1.ListCount - 1 With ListBox1 ListBox2.AddItem .List(0) For i = 1 To n - 1 ListBox2.Column(i, RowIndex2) = .Column(i, 0) Next i RowIndex2 = RowIndex2 + 1 'Перемещенный,- это всегда первый элемент,удаляется из списка .RemoveItem 0 End With Next RowIndex1 End Sub



Public Sub MoveListToRange(ByVal n As Byte, List1 As Object, Dom As String) 'List1 - объект типа ListBox, состоящий из n столбцов. ' Его элементы переносятся в прямоугольную область активного листа, ' Dom - задает имя ячейки, расположенной в левом верхнем углу этой ' области.

Dim myr As Range Dim i As Byte, j As Byte

Set myr = Range(Dom) 'Цикл по числу элементов списка. For i = 0 To List1.ListCount - 1 'Цикл по числу столбцов списка. For j = 0 To n - 1 myr.Offset(j, i) = List1.Column(j, i) Next j Next i End Sub

Public Sub ClearRange(Dom As String) 'Эта процедура очищает содержимое области листа рабочей книги, 'заданной ячейкой с именем Dom

Dim myr As Range, Row As Byte, Col As Byte

Set myr = Range(Dom) Col = 0: Row = 0 While myr.Offset(Row, Col) <> "" While myr.Offset(Row, Col) <> "" 'Чистка содержимого myr.Offset(Row, Col).ClearContents Col = Col + 1 Wend Row = Row + 1 Col = 0 Wend End Sub

Пример 2.2.

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

Завершая разговор о переиспользовании стандартных модулей, предлагаем взглянуть на форму, взятую из совсем другого документа - документа Word.


Рис. 2.4.  Форма TwoLists, взятая из документа Word

Хотя форма и похожа на форму, связанную с рабочей книгой Excel, но, обратите внимание, списки в ней другие, они состоят из одного столбца, а не из двух, как в прошлом случае. Тем не менее, мы спокойно выполнили операции экспорта - импорта стандартного модуля из Excel в Word и без всяких изменений использовали его процедуры MoveSelectedItems и MoveAllItems. Конечно, думая о возможности многократного использования этих процедур, мы заранее побеспокоились о возможности работы с произвольным числом столбцов в списках.



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

Замечание:

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

Public Sub MoveAllItems(ByVal n As Byte, ByVal ListBox1 As Object, _

ByVal ListBox2 As Object)

Вы видите, что тип формальных параметров для ListBox1 и ListBox2 задан как Object и, следовательно, является нетипизированным указателем. Это не очень удобно, так как лишает нас подсказки в момент написания процедуры. Возникает вопрос, можно ли указывать точный тип для подобных формальных параметров. Ответ зависит от приложения. При работе в приложении Word разрешается указывать настоящий, правильный тип ListBox и при этом передача параметров производится корректно. В приложении Excel при написании процедуры также можно указать тип ListBox, но возникнет ошибка в момент передачи параметров.

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


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