Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ОСНОВЫ ОФИСНОГО ПРОГРАММИРОВАНИЯ И ЯЗЫК VBA - 1....doc
Скачиваний:
59
Добавлен:
17.12.2018
Размер:
1.88 Mб
Скачать

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

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

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

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

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

  • Двойным щелчком по элементу.

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

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

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

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

Option Explicit

Private Sub CommandButton1_Click()

'Обработчик события Click кнопки "> <"

'Выборочный обмен данными между n- колоночными списками:

'ListBox1 <--> ListBox2

If CommandButton1.Caption = ">" Then

Call MoveSelectedItems(ListBox1.ColumnCount, ListBox1, ListBox2)

Else

Call MoveSelectedItems(ListBox2.ColumnCount, ListBox2, ListBox1)

End If

End Sub

Private Sub CommandButton2_Click()

'Обработчик события Click кнопки ">> <<"

'Перенос всех данных из одного n-колоночного списка

'в конец другого, возможно, не пустого списка: ListBox1 <--> ListBox2

If CommandButton2.Caption = ">>" Then

Call MoveAllItems(ListBox1.ColumnCount, ListBox1, ListBox2)

Else

Call MoveAllItems(ListBox2.ColumnCount, ListBox2, ListBox1)

End If

End Sub

Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)

'Обработчик события DblClick левого списка ListBox1 (имеет параметры!)

'При двойном щелчке выбранный элемент одного n-колоночного списка

'переносится в конец другого списка

Call MoveSelectedItems(ListBox1.ColumnCount, ListBox1, ListBox2)

End Sub

Private Sub ListBox2_DblClick(ByVal Cancel As MSForms.ReturnBoolean)

'Обработчик события DblClick правого списка ListBox2

'При двойном щелчке выбранный элемент одного n-колоночного списка

'переносится в конец другого списка

Call MoveSelectedItems(ListBox2.ColumnCount, ListBox2, ListBox1)

End Sub

Private Sub CommandButton5_Click()

'Обработчик события Click кнопки "OK"

'Перенос данных из списка на лист Excel

'В область, заданную ячейкой с именем "Dom"

Call MoveListToRange(ListBox2.ColumnCount, ListBox2, "Dom")

Me.Hide

End Sub

Private Sub CommandButton6_Click()

'Обработчик события Click кнопки "Cancel"

Me.Hide

End Sub

Private Sub ListBox1_Enter()

'Обработчик события Enter, возникающего при получении фокуса

CommandButton1.Caption = ">"

CommandButton2.Caption = ">>"

End Sub

Private Sub ListBox2_Enter()

'Обработчик события Enter, возникающего при получении фокуса

CommandButton1.Caption = "<"

CommandButton2.Caption = "<<"

End Sub

Private Sub UserForm_Initialize()

'Обработчик события Initialize формы TwoListsForm

'Заполнение списка ListBox1

Dim MyArray(1 To 5, 1 To 2) As String

MyArray(1, 1) = "Петров" : MyArray(1, 2) = "Музыкант"

MyArray(2, 1) = "Сергеев" : MyArray(2, 2) = "Учитель"

MyArray(3, 1) = "Гурина" : MyArray(3, 2) = "Актриса"

MyArray(4, 1) = "Водкин" : MyArray(4, 2) = "Художник"

MyArray(5, 1) = "Козина" : MyArray(5, 2) = "Геолог"

ListBox1.ColumnCount = 2 : ListBox2.ColumnCount = 2

ListBox1.List() = MyArray

End Sub

Пример 2.1. (html, txt)

Модуль TwoListsForm, связанный с формой, содержит 9 обработчиков событий, возникающих при работе с самой формой - ее инициализации, так и при работе пользователя с объектами, населяющими форму. Каждый из обработчиков выполняет свои специфические задачи. Когда пользователь переключается на работу с элементами левого или правого списка, то в тот момент, когда список получает фокус, возникает событие Enter. Обработчик события Enter изменяет заголовок у соответствующих командных кнопок, подготавливая, тем самым, передачу данных в нужном направлении. Такое автоматическое изменение направления передачи позволяет уберечь пользователя от ошибочных действий. Но, обратите внимание на главное в этом примере. Для удобства пользователя ему предоставлены три разных способа передачи данных из одного списка в другой. Поскольку обмен данными может вестись в двух направлениях, то при лобовом программировании нужно было бы написать 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. (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 мы указали разработчикам на это рассогласование, оно должно быть учтено в последующих версиях. Будем надеяться, что в будущем мы сможем в процедурах при объявлении формальных параметров, являющихся объектами, указывать их настоящий тип.