Тверской государственный университет
Опубликован: 13.09.2006 | Доступ: свободный | Студентов: 3491 / 369 | Оценка: 4.65 / 4.29 | Длительность: 30:37:00
Специальности: Программист, Менеджер
Лекция 3:

Документы и проекты

< Лекция 2 || Лекция 3: 123456 || Лекция 4 >

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

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

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

Форма TwoListsForm документа 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.

Модуль 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.

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

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

Форма TwoLists, взятая из документа 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 мы указали разработчикам на это рассогласование, оно должно быть учтено в последующих версиях. Будем надеяться, что в будущем мы сможем в процедурах при объявлении формальных параметров, являющихся объектами, указывать их настоящий тип.

Проект и область видимости

Напомним, проект документа мы рассматриваем, как совокупность модулей разного типа. Компонентами каждого модуля являются переменные уровня модуля, описанные в разделе объявления модуля, и методы модуля.

Первое правило видимости гласит:

"Все компоненты модуля видимы в пределах этого модуля".

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

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

Второе правило видимости гласит:

"Область видимости компонент модуля расширяется на весь проект, если компонент объявлен со спецификатором Public ".

Каждый компонент модуля - переменная или метод может быть снабжен спецификатором области видимости, который имеет два возможных значения - Public и Private. Если задан спецификатор Public, то это означает, что компонент общедоступен в пределах всего проекта. Спецификатор Private делает компонент закрытым для других модулей проекта. Он видим только в своем родном модуле.

Если при объявлении переменных модуля спецификатор области видимости опущен и указано только ключевое слово Dim, то такие переменные считаются закрытыми, - действует спецификатор Private. Для методов спецификатор области видимости можно опускать. В этом случае действует следующее правило. Все методы стандартных модулей имеют по умолчанию спецификатор Public и являются доступными во всем проекте. Методы модулей - классов и модулей, связанных с объектами, по умолчанию являются закрытыми и имеют статус Private.

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

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

< Лекция 2 || Лекция 3: 123456 || Лекция 4 >
полина есенкова
полина есенкова
Дмитрий Вологжин
Дмитрий Вологжин
Добрый день, прошел тесты с 1 по 9, 10 не сдал, стал читать лекцию и всё пройденные тесты с 1 по 9 сбросились, когда захотел пересдать 10 тест.