Многие наверняка слышали про модули классов, но не все их используют. На самом деле довольно многие программирующие на VBA за все время программирования прекрасно обходятся без применения модулей классов. Т.к. VBA не является языком объектно-ориентированного программирования(ООП) в строгом смысле слова, то пользовательские классы здесь не обязательны и как следствие не так уж и часто используются при разработке. Это не значит, что VBA не содержит модулей классов: модули книги, листов, пользовательские формы - все это модули классов. Многие, кстати, используют их даже не зная того, что используют именно модули классов. Т.к. модуль листа, книги и формы - это модуль класса, то почти каждый, кто работал с формой работал с модулем класса. В чем их большая польза - с их помощью можно отслеживать различные события объектов. Для форм это события самой формы или любого её элемента - например CommandButton_Click или TextBox_Change. Но мы сейчас рассмотрим лишь тот тип модулей, который в VBA обычно называют модулем класса - Class Module.

Модуль класса(Class Module) – это модуль, содержащий программные коды, которые реализуют работу пользовательских классов. В подавляющем большинстве случаев создается специально для отслеживания событий различных объектов. Создается так же, как и любой другой объект проекта: в окне проводника объектов щелкаем правой кнопкой мыши на нужном проекте-Insert-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 делает за нас всю грязную работу - он сам ассоциирует созданный объект с событиями, предназначенные для него заранее. Если же создать объект программно - то часть грязной работы придется делать самим. И создание модуля класса, с описанием в нем объекта ТекстБокс и его событий, как раз очень даже подойдет.
Рассмотрим сразу оба случая. Что нам для этого потребуется:

  1. для начала создать модуль класса с именем clsmTxtBxes(Insert-Class Module)
  2. создать стандартный модуль с именем mMain(Insert-Module)
  3. ну и сама форма тоже не лишняя(Insert-UserForm). У меня форма называется frmTest.
  4. очень желательно наличие у вас опыта написания хотя бы простейших процедур. Иначе может показаться все очень сложным и непонятным.

Чтобы было проще вникать советую скачать файл с готовыми кодами:

  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

но я не приветствую подобный подход, т.к. имя ClassModule1 ни о чем нам не говорит, в то время как clsmTxtBxes сразу дает понять, что там мы обрабатываем ТекстБоксы. Хотя это дело вкуса. Если в одном модуле класса собраны различные событийные процедуры для разных типов(TextBox, ComboBox, ListBox и т.д.) - то конечно, имя лучше дать более общее.
Теперь в созданный модуль класса clsmTxtBxes запишем создание объекта и код, который хотим применить для всех наших ТекстБоксов:

Option Explicit
Public WithEvents oTxtBx As MSForms.TextBox    
'событие изменения текста в TextBox-ах
Private Sub oTxtBx_Change()
    MsgBox "Вы изменили значение " & oTxtBx.Name, vbInformation, "Информационное окно"
End Sub

Public WithEvents oTxtBx As MSForms.TextBox - создаем объект типа ТекстБокс с отслеживанием его событий. Идентификатором объекта с отслеживанием событий служит оператор WithEvents (может применяться только в модулях классов).
Если необходимо отследить изменения не TextBox, а ComboBox, то соответственно объявляем объект нужного типа:
Public WithEvents oCmbBx As MSForms.ComboBox
Сами события для контролов не берутся из головы и не пишутся вручную - они уже есть и следует использовать именно те, которые доступны. Чтобы для конкретного элемента создать событие, необходимо перейти в модуль класса, вверху в левой части выбрать из списка нужный объект(в нашем случае это oTxtBx) и после этого в правом списке выбрать событие(в этом списке перечисляются все процедуры, доступные для выбранного объекта):
Создание события
Для выбранного события в модуле будет автоматически создана новая процедура.
Процедуры, события для которых уже созданы, выделяются в списке жирным шрифтом и при выборе их из списка происходит переход в процедуру выбранного события.

Завершающий этап - создаем код в модуле формы frmTest, который создаст недостающие ТекстБоксы и свяжет их и ранее созданные с модулем класса:

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

Кратко описать, что делает эта процедура, можно так:

  1. при запуске формы в массив aoTxtBxes запоминаются сначала те ТекстБоксы, которые мы предусмотрительно заранее создали на форме
  2. затем создаются еще 4 новых ТекстБокса, которые также записываются в массив aoTxtBxes
  3. Т.к. массив aoTxtBxes у нас является новым экземпляром класса, то обращаться к его содержимому мы можем только по законам работы с классами, т.е. только к тем объектам и методам, которые в классе прописаны. А у нас там пока только один объект прописан - oTxtBx(Public WithEvents oTxtBx As MSForms.TextBox). Его и используем. Ничего другого использовать VBE нам и не позволит
  4. т.к. класс мы создали, событие объекта прописали, объектам значения ТекстБоксов присвоили - остается только наслаждаться. Теперь любое изменение в любом из ТекстБоксов будет обработано и появится сообщение - "Вы изменили значение " + имя ТекстБокса

Если необходимо больше ТекстБоксов обработать - увеличиваем верхнюю границу массива aoTxtBxes(если хотим вместить 20 текстбоксов - Public aoTxtBxes(1 To 20) As New clsmTxtBxes). Если заранее неизвестно количество - либо задаем с запасом, либо объявляем aoTxtBxes как динамический массив(Public aoTxtBxes() As New clsmTxtBxes), а границы определяем в процессе(посредством ReDim Preserve). Но это уже совершенно другая тема.


Конечно, здесь я привел лишь маленький пример показа сообщения при изменении ТекстБокса. Но ведь можно таким образом отследить практически любое доступное событие. И не просто сообщение показывать, а запретить ввод букв, делать проверку введенного значения на соответствие шаблону и пр. Все зависит от конкретной задачи.
Так же дополню, что подобным образом можно создавать и отслеживать и иные элементы форм. Для этого необходимо лишь изменить тип элемента здесь:

Me.Controls.Add("Forms.TextBox.1", "TextBox" & i)

и соответственно изменить/добавить тип переменной в модуле класса:

Public WithEvents oCmbBx As MSForms.ComboBox

Всего для создания доступно 11 встроенных типов контролов:
ComboBox - MSForms.ComboBox
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 и зачем оно нужно?

Loading

17 комментариев

  1. Большое спасибо. Долго пытался найти, как отследить действия над программно созданными объектами. Нашел пример кода. Написал аналог для своей проги, но вылетала ошибка Object does not source automation events. Так как найденный мной пример был без описания, я решил разобраться в принципах работы модуля классов и наткнулся на эту статью. Решение проблемы оказалось проще простого. Нужно было просто добавить MSForms. при описании.
    Еще раз огромное спасибо.

  2. Спасибо, очень красивый и понятный пример. Есть только один вопрос: как связаны кнопки с макросами? Я обычно использую простые CommandButton, но иногда хочется для разнообразия вставить что-то другое для запуска макроса.
    Поясните, пожалуйста или подскажите направление в котором покопать...

    1. Александр, можно. Только на листе разные типы текстбоксов. Элементы управления форм и ActiveX.
      Для ActiveX почти так же все выглядит. Только заносить в массив, скорее всего надо примерно таким циклом:

      For Each oEl In ActiveSheet.OLEObjects
          If TypeOf oEl.Object Is msforms.TextBox Then
  3. Дмитрий. очень понятный пример и объяснения! большое спасибо за сатью! Мне необходимо создать контрлс не на самой форме а на MultiPage формы. Подскажите, пожауйста, как это сделать? заранее благодарен!

  4. Дмитрий, добрый день!
    не могу передать параметр внутрь Класса
    я объявил переменную внутри класса
    Public Property Let iIndexTXT(iNewIndex As Integer)
    iIndexTXT = iNewIndex
    End Property
    и попробовал передать данные
    With aoTxtBxes(i, 2).oTxtBx
    .iIndexTXT = i
    .Left = 160
    но выдает ошибку Объект не поддерживает метод или проперти

    1. Видимо, потому что процедура iIndexTXT относится к самому классу, а не к отдельному объявленному там объекту? В данном случае эта процедура не нужна - достаточно использовать свойство Tag самого TextBox.

        1. У меня получилось передать данные в класс, созданный с формой.
          В самом модуле класса через процедуру setTextBox отразил привязку Set tBox = objTbx, и через эту же процедуру передал координаты и данные для расчетов.
          в итоге создание новой формы ТекстБокса модуле формы frmTest получилось одной строке:
          clsTxtBoxes(i).setTextBox Me.Controls.Add("Forms.TextBox.1", "TextBox" & i), 10, iTop, 18, 100, arrMain(i, 1), MyOptionTmp

  5. Дмитрий, большое спасибо за статью! Долгое время не знал как прикрутить какое-нибудь действие на определенное событие множества элементов формы. С какой стороны вообще подойти к этому вопросу. И тут такое! То, что искал! Спасибо!

    1. Вася, не то чтобы она пропущена - она намеренно отсутствует :) просто с 5-го по 8-ой идет закраска текстбоксов при длине символов более 3-х, о чем честно указано в комментариях:

      Private Sub oTxtBx_Change()
          If Val(Right(oTxtBx.Name, 1)) < 5 Then 'если это ТестБоксы с 1 по 4
              MsgBox "Вы изменили значение " & oTxtBx.Name, vbInformation, "Информационное окно"
          Else 'остальные. Если длина текста более 3-х символов - закрашиваем красным
              If Len(oTxtBx.Text) > 3 Then oTxtBx.BackColor = vbRed
          End If
      End Sub

Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.