- •Основы офисного программирования и язык vba
- •Офисное программирование
- •Состав Office 2000
- •Инсталляция Office 2000
- •Содержимое диска "Resource Kit"
- •Документ Issues
- •Документ ork9
- •Папка Tools
- •Содержимое диска "Language Pack"
- •Об этой книге и ...
- •Моя благодарность
- •Vba и объекты. Обзор
- •Библиотека объектов Office 2000 - каркас приложений
- •Встраивание вместо наследования
- •Построение каркаса документа
- •Библиотека объектов Office 2000 и Object Browser
- •Объекты Application
- •Свойства-участники
- •Вторая группа терминальных свойств
- •Методы объектов Application
- •События объектов Application
- •Совместная работа приложений. Создание объектов Application. Раннее и позднее связывание
- •Коллекции в Office 2000
- •Коллекции объектов Office 2000
- •Коллекции Excel: Workbooks, Sheets
- •Коллекция Workbooks
- •Коллекция Sheets
- •Коллекции Word: Documents, Paragraphs и другие
- •Коллекция Documents
- •Коллекция Paragraphs
- •Коллекция Sections
- •Коллекции Characters, Words, Sentences
- •Коллекции и конструкция For Each … Next
- •Объекты Range
- •Что определяет объект Range?
- •Метод Range
- •Объекты Selection
- •Метод Select и свойство Selection
- •Активные объекты и метод Activate
- •Проектирование документов
- •Документ и его программный проект
- •Модули - обработчики событий
- •Стандартные модули
- •Модули классов
- •Модуль макросов
- •Структура модуля. Окно проекта и Окно кода
- •Окно проекта
- •Свойства проекта
- •Имя проекта
- •Защита проекта
- •Окно кода
- •Еще раз о "переиспользовании" модулей
- •Проект и область видимости
- •Система документов и ее проект
- •Организация системы документов
- •Как организуются ссылки между проектами
- •Обмен информацией между документами
- •Система документов One - Two - Three
- •Типы данных
- •Простые типы данных.
- •Объявление переменных и констант простых типов
- •Синтаксис объявления простых переменных
- •Объявления по умолчанию
- •Константы
- •Массивы
- •Динамические массивы
- •Записи и тип, определенный программистом
- •Что можно делать с записями?
- •Раздел объявлений
- •Раздел опций
- •Разделы констант, типов и переменных
- •Раздел Declare
- •Правила именования
- •Типы и классы Еще раз о понятии "класс"
- •Что нового в классах "Office 2000"
- •Создание класса "Личность"
- •Объекты и переменные
- •Объекты, класс которых определен пользователем.
- •Объекты "родного" приложения
- •ActiveX-объекты
- •Модуль класса Свойства
- •Сокрытие свойств
- •Конструкторы и деструкторы. Стандартные события
- •Стандартные события Initialize и Terminate
- •Два конструктора класса Rational
- •Процедуры - свойства
- •Как создаются процедуры- свойства
- •Синтаксис Let, Get и Set
- •Классы, как упаковка
- •Семейство классов и процедуры - свойства
- •Friend методы
- •События
- •Классы, объекты With Events и обработчики событий
- •Модуль класса с объектом WithEvents
- •Объект WithEvents
- •События собственных классов
- •Как создать класс с событиями
- •Как зажигаются события
- •Где и как следует создавать обработчики событий для экземпляров класса
- •Связывание объектов
- •Реальные объекты и инициирование событий
- •Семейство классов и реализация интерфейсов
- •Наследование
- •Виртуальные методы и полиморфизм
- •Абстрактные классы
- •Наследование и полиморфизм в Office 2000
- •Наследование интерфейсов
- •Полиморфизм семейства классов
- •Проект "Люди и Машины"
Еще раз о "переиспользовании" модулей
Пора, пожалуй, прервать плавный ход повествования и привести хоть небольшой фрагмент кода, иллюстрирующий высказанные в этой лекции утверждения. Мы отмечали роль стандартных модулей, одно из достоинств которых состоит в возможности их многократного использования. Рассмотрим следующую, часто возникающую на практике задачу. Пусть есть форма, содержащая два списка - элементы управления типа 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 мы указали разработчикам на это рассогласование, оно должно быть учтено в последующих версиях. Будем надеяться, что в будущем мы сможем в процедурах при объявлении формальных параметров, являющихся объектами, указывать их настоящий тип.