Многие наверняка слышали про модули классов, но не все их используют. На самом деле довольно многие программирующие на VBA за все время программирования прекрасно обходятся без применения модулей классов. Т.к. VBA не является языком объектно-ориентированного программирования(ООП) в строгом смысле слова, то пользовательские классы здесь не обязательны и как следствие не так уж и часто используются при разработке. Это не значит, что VBA не содержит модулей классов: модули книги, листов, пользовательские формы - все это модули классов. Многие, кстати, используют их даже не зная того, что используют именно модули классов. Т.к. модуль листа, книги и формы - это модуль класса, то почти каждый, кто работал с формой работал с модулем класса. В чем их большая польза - с их помощью можно отслеживать различные события объектов. Для форм это события самой формы или любого её элемента - например CommandButton_Click или TextBox_Change. Но мы сейчас рассмотрим лишь тот тип модулей, который в VBA обычно называют модулем класса - Class Module.
Но прежде чем создать модуль, необходимо понять, что мы будем в нем хранить и для чего он нам. Возьмем для примера самую распространенную проблему: на форме создано несколько ТекстБоксов и необходимо отследить событие ввода данных в эти ТекстБоксы. Обычно делается все просто - для каждого ТекстБокса прописывается отслеживание события:
Private Sub TextBox1_Change() MsgBox "Изменено значение TextBox1" End Sub Private Sub TextBox2_Change() MsgBox "Изменено значение TextBox2" End Sub Private Sub TextBox3_Change() MsgBox "Изменено значение TextBox3" End Sub 'и т.д. |
С одной стороны - все верно. А с другой: что если таких текстбоксов у нас не 3, а 43? Не очень удобно для каждого событие прописывать. Да и читабельность такой "портянки" кода тоже значительно падает.
Или другая ситуация - необходимо "на ходу" создать ТекстБоксы на форме и в дальнейшем отслеживать их события. Как тут быть? Ведь раз ТексБоксов еще нет - то и события в форме для них не создать. Создание для них кодов обработки событий заранее ничего не даст - они не будут связаны с самими объектами, поэтому и пытаться даже не стоит. Почему так - при создании элемента вручную VBE делает за нас всю грязную работу - он сам ассоциирует созданный объект с событиями, предназначенные для него заранее. Если же создать объект программно - то часть грязной работы придется делать самим. И создание модуля класса, с описанием в нем объекта ТекстБокс и его событий, как раз очень даже подойдет.
Рассмотрим сразу оба случая. Что нам для этого потребуется:
- для начала создать модуль класса с именем clsmTxtBxes(Insert-Class Module)
- создать стандартный модуль с именем mMain(Insert-Module)
- ну и сама форма тоже не лишняя(Insert-UserForm). У меня форма называется frmTest.
- очень желательно наличие у вас опыта написания хотя бы простейших процедур. Иначе может показаться все очень сложным и непонятным.
Чтобы было проще вникать советую скачать файл с готовыми кодами:
Tips_Macro_UseClassModules.xls (63,5 КиБ, 6 393 скачиваний)
Для начала создадим на нашей форме frmTest 4 ТекстБокса, не меняя их имена(по умолчанию они будут TextBox1, TextBox2, TextBox3, TextBox4). Это для того, чтобы понять как применить модули класса к уже созданным ранее на форме элементам.
Далее в стандартный модуль mMain поместим следующий код:
Option Explicit Public aoTxtBxes(1 To 8) As New clsmTxtBxes Sub Show_Form() frmTest.Show End Sub |
aoTxtBxes - массив, который будет содержать до 8 ТекстБоксов. Объявляется как Public (чтобы был доступен из любого модуля проекта. Подробнее в статье: Что такое переменная и как правильно её объявить?). Обращаю внимание, что данный массив объявлен как созданный нами модуль класса - As clsmTxtBxes. Это обязательное условие. Если у вас модуль класса называется ClassModule1, то и объявлять aoTxtBxes следует соответственно:
Public aoTxtBxes(1 To 8) As New ClassModule1 |
Теперь в созданный модуль класса clsmTxtBxes запишем создание объекта и код, который хотим применить для всех наших ТекстБоксов:
Option Explicit Public WithEvents oTxtBx As MSForms.TextBox 'событие изменения текста в TextBox-ах Private Sub oTxtBx_Change() MsgBox "Вы изменили значение " & oTxtBx.Name, vbInformation, "Информационное окно" End Sub |
Сами события для контролов не берутся из головы и не пишутся вручную - они уже есть и следует использовать именно те, которые доступны. Чтобы для конкретного элемента создать событие, необходимо перейти в модуль класса, вверху в левой части выбрать из списка нужный объект(в нашем случае это oTxtBx) и после этого в правом списке выбрать событие(в этом списке перечисляются все процедуры, доступные для выбранного объекта):
Для выбранного события в модуле будет автоматически создана новая процедура.
Процедуры, события для которых уже созданы, выделяются в списке жирным шрифтом и при выборе их из списка происходит переход в процедуру выбранного события.
Завершающий этап - создаем код в модуле формы
Option Explicit Private Sub UserForm_Initialize() Dim i As Integer 'Присваиваем последовательно значениям массива aoTxtBxes значения объектов, существующих на форме For i = 1 To 4 Set aoTxtBxes(i).oTxtBx = Me.Controls("TextBox" & i) Next i 'создаем 4 своих TrxtBox-а помимо имеющихся на форме и так же заносим в массив aoTxtBxes For i = 5 To 8 Set aoTxtBxes(i).oTxtBx = Me.Controls.Add("Forms.TextBox.1", "TextBox" & i) 'задаем позицию нового TextBox aoTxtBxes(i).oTxtBx.Left = 100 aoTxtBxes(i).oTxtBx.Top = Me.Controls("TextBox" & i - 4).Top Next i End Sub |
Кратко описать, что делает эта процедура, можно так:
- при запуске формы в массив aoTxtBxes запоминаются сначала те ТекстБоксы, которые мы предусмотрительно заранее создали на форме
- затем создаются еще 4 новых ТекстБокса, которые также записываются в массив
aoTxtBxes - Т.к. массив
aoTxtBxes у нас является новым экземпляром класса, то обращаться к его содержимому мы можем только по законам работы с классами, т.е. только к тем объектам и методам, которые в классе прописаны. А у нас там пока только один объект прописан - oTxtBx(Public WithEvents oTxtBx As MSForms.TextBox ). Его и используем. Ничего другого использовать VBE нам и не позволит - т.к. класс мы создали, событие объекта прописали, объектам значения ТекстБоксов присвоили - остается только наслаждаться. Теперь любое изменение в любом из ТекстБоксов будет обработано и появится сообщение -
"Вы изменили значение " + имя ТекстБокса
Если необходимо больше ТекстБоксов обработать - увеличиваем верхнюю границу массива aoTxtBxes(если хотим вместить 20 текстбоксов -
Конечно, здесь я привел лишь маленький пример показа сообщения при изменении ТекстБокса. Но ведь можно таким образом отследить практически любое доступное событие. И не просто сообщение показывать, а запретить ввод букв, делать проверку введенного значения на соответствие шаблону и пр. Все зависит от конкретной задачи.
Так же дополню, что подобным образом можно создавать и отслеживать и иные элементы форм. Для этого необходимо лишь изменить тип элемента здесь:
Me.Controls.Add("Forms.TextBox.1", "TextBox" & i) |
и соответственно изменить/добавить тип переменной в модуле класса:
Public WithEvents oCmbBx As MSForms.ComboBox |
Всего для создания доступно 11 встроенных типов контролов:
CheckBox - MSForms.CheckBox
CommandButton - MSForms.CommandButton
Frame - MSForms.Frame
Image - MSForms.Image
Label - MSForms.Label
ListBox - MSForms.ListBox
MultiPage - MSForms.MultiPage
SpinButton - MSForms.SpinButton
TabStrip - MSForms.TabStrip
ToggleButton - MSForms.ToggleButton
И небольшая ложка дегтя: из модулей классов доступны не все события. Например, для TextBox-ов нет события Exit. Это порой расстраивает. И никак это не исправить - нет его и все...
Tips_Macro_UseClassModules.xls (63,5 КиБ, 6 393 скачиваний)
Также см.:
Что такое модуль? Какие бывают модули?
Что такое переменная и как правильно её объявить?
Variable not defined или что такое Option Explicit и зачем оно нужно?
Большое спасибо. Долго пытался найти, как отследить действия над программно созданными объектами. Нашел пример кода. Написал аналог для своей проги, но вылетала ошибка Object does not source automation events. Так как найденный мной пример был без описания, я решил разобраться в принципах работы модуля классов и наткнулся на эту статью. Решение проблемы оказалось проще простого. Нужно было просто добавить MSForms. при описании.
Еще раз огромное спасибо.
Спасибо, очень красивый и понятный пример. Есть только один вопрос: как связаны кнопки с макросами? Я обычно использую простые CommandButton, но иногда хочется для разнообразия вставить что-то другое для запуска макроса.
Поясните, пожалуйста или подскажите направление в котором покопать...
Спасибо очень хорошая статья, именно то что искал.
Подскажите,пожалуйста, тоже самое можно сделать, если textbox-сы находятся на листе?
Александр, можно. Только на листе разные типы текстбоксов. Элементы управления форм и ActiveX.
Для ActiveX почти так же все выглядит. Только заносить в массив, скорее всего надо примерно таким циклом:
Дмитрий. очень понятный пример и объяснения! большое спасибо за сатью! Мне необходимо создать контрлс не на самой форме а на MultiPage формы. Подскажите, пожауйста, как это сделать? заранее благодарен!
MultiPage1.Controls.Add
Дмитрий, добрый день!
не могу передать параметр внутрь Класса
я объявил переменную внутри класса
Public Property Let iIndexTXT(iNewIndex As Integer)
iIndexTXT = iNewIndex
End Property
и попробовал передать данные
With aoTxtBxes(i, 2).oTxtBx
.iIndexTXT = i
.Left = 160
но выдает ошибку Объект не поддерживает метод или проперти
Видимо, потому что процедура iIndexTXT относится к самому классу, а не к отдельному объявленному там объекту? В данном случае эта процедура не нужна - достаточно использовать свойство Tag самого TextBox.
Спасибо, получилось!
У меня получилось передать данные в класс, созданный с формой.
В самом модуле класса через процедуру setTextBox отразил привязку Set tBox = objTbx, и через эту же процедуру передал координаты и данные для расчетов.
в итоге создание новой формы ТекстБокса модуле формы frmTest получилось одной строке:
clsTxtBoxes(i).setTextBox Me.Controls.Add("Forms.TextBox.1", "TextBox" & i), 10, iTop, 18, 100, arrMain(i, 1), MyOptionTmp
Спасибо за статью! Пример помог разобраться в проблеме отслеживания событий программно созданных объектов
Дмитрий, большое спасибо за статью! Долгое время не знал как прикрутить какое-нибудь действие на определенное событие множества элементов формы. С какой стороны вообще подойти к этому вопросу. И тут такое! То, что искал! Спасибо!
Егор, спасибо. Рад, что статьи помогают освоить новые границы и упростить работу с VBA и Excel.
в примере отслеживается только 4 чекбокса, с 5 го по 8ой у меня не отслеживаются. а у кого как?
Просто пропущена строка с msgbox после else.
Вася, не то чтобы она пропущена - она намеренно отсутствует :) просто с 5-го по 8-ой идет закраска текстбоксов при длине символов более 3-х, о чем честно указано в комментариях: