
В статье описан простой контроллер инициализации модулей VBA Excel.
Все типы модулей VBA MS Excel позволяют создавать код, автоматически выполняемый при загрузке и/или выгрузке модуля.
Однако, для обычных модулей с макросами, VBComponent.Type=001, примера автоматической инициализации автор в сети не обнаружил.
Вступление
Автоматическая инициализация модуля VBA MS Excel производится по факту наличия в модуле подпрограммы с заданным интерфейсом.
Это удобно. Наличие подпрограммы с заданным интерфейсом включает автоматические инициализацию модуля. Удаление из кода модуля подпрограммы — выключает.
Например, для включения автоматической инициализации листа электронной таблицы, достаточно добавить две подпрограммы в модуль макросов листа.
Private Sub Worksheet_Activate() ' your code here End Sub Private Sub Worksheet_Deactivate() ' your code here End Sub
Первая подпрограмма автоматически вызывается перед получением фокуса ввода. Например, для установки начальных значений переменных.
Вторая — перед тем как, как фокус ввода будет потерян.
В других листах электронных таблиц эти функции могут отсутствовать, если в инициализации там потребности нет.
Таким же образом устроен механизм инициализации классов, книг и форм MS Excel.
Но, иногда возникает потребность автоматической инициализации обычных модулей с макросами, Type=001.
Постановка задачи
Создать автоматический вызов процедур инициализации и завершения для обычных модулей VBA.
Повторить привычный механизм управления инициализацией — наличие в модуле VBA подпрограммы с известным именем.
Осуществлять автоматически инициализацию и завершение только тех модулей VBA, где присутствуют соответствующие подпрограммы.
Решение задачи
Синтаксис MS EXCEL VBA, допускает:
размещение в разных модулях подпрограмм с одинаковыми именами и интерфейсом;
косвенный вызов подпрограмм VBA по имени внутри переменной;
уточняющий синтаксис VBA.
Договоримся об именовании подпрограмм инициализации и завершения:
moduleInit(ByRef Wb As Workbook) — автоматически вызываемая процедура инициализации;
moduleLeave(ByRef Wb As Workbook) — автоматически вызываемая процедура завершения;
В любом модуле VBA, подпрограммы инициализации могут отсутствовать или присутствовать, совместно или по одной.
Подпрограммы инициализации принимают один параметр, Wb, хранящий ссылку на книгу, для которой производится инициализация мод��ля макросов.
Инициализируемый модуль определяется местоположением вызываемой подпрограммы.
Для включения автоматической инициализации добавляем в любое место модуля макросов подпрограмму инициализации:
Option Explicit ' Your VBA module code here Public Sub moduleInit(ByRef Wb As Workbook) ' The starting code for your VBA module is here End Sub
Для завершения работы модуля, помещаем соответствующую подпрограмму где-нибудь рядом:
Option Explicit ' Your VBA module code here Public Sub moduleLeave(ByRef Wb As Workbook) ' The final code for your VBA module is here End Sub Public Sub moduleInit(ByRef Wb As Workbook) ' The starting code for your VBA module is here End Sub
Удаляем текст договорных подпрограмм, если потребности в инициализации модуля нет.
Контроллер инициализации
Интерфейс контроллера инициализации содержит две константы и одну подпрограмму.
Public Const vbaMODULE_INIT As String = "moduleInit" Public Const vbaMODULE_LEAVE As String = "moduleLeave" Public Sub vbaWbModuleControl( _ subName As String, _ Optional printDebugOnly As Boolean = False)
Константы закрепляют договор вызова подпрограмм инициализации и завершения обычных модулей VBA MS Excel.
Прототипы подпрограмм инициализации завершения:
Public Sub moduleInit(ByRef Wb As Workbook) ' module initialization Public Sub moduleLeave(ByRef Wb As Workbook) ' module completion
В подпрограмме контроллера инициализации vbaWbModuleControl два параметра:
subName — название подпрограммы инициализации или завершения;
printDebugOnly — запуск контроллера в режиме отладки.
Каждый раз при вызове, контроллер инициализации «пробегает» по всем модулям проекта VBA MS Excel, создаёт список подпрограмм с именем subName по факту их присутствия, запускает на исполнение передавая, как параметр, объект рабочей книги.
Порядок запуска подпрограмм инициализации определяется порядком создания модулей VBA.
Запуск vbaWbModuleControl с параметром printDebugOnly=True выдаёт на консоль отладки список всех функций инициализации или завершения в порядке вызова, но без вызова.

Для инициализации модулей макросов по событиям получения или потери фокуса ввода электронной таблицы, контроллер инициализации размещается в обработчике событий _Activate().
Private Sub Workbook_Activate() vbaWbModuleControl vbaMODULE_INIT End Sub Private Sub Workbook_Deactivate() vbaWbModuleControl vbaMODULE_LEAVE End Sub
Для одноразового вызова процедур инициализации и завершения достаточно переместить вызов контроллера в обработчики открытия и закрытия книги электронной таблицы.
Private Sub Workbook_Open() vbaWbModuleControl vbaMODULE_INIT End Sub Private Sub Workbook_BeforeClose(Cancel As Boolean) vbaWbModuleControl vbaMODULE_LEAVE End Sub
Для восстановления инициализации после сбоя во время отладки контроллер запускается в ручном режиме.
Sub ReInitProject() vbaWbModuleControl vbaMODULE_INIT End Sub
Интеграция контроллера
Контроллер инициализации интегрируется в новый проект простым переносом исходного текста [^C;^V] в модуль макросов VBA Excel.
Контроллер работает без начальной инициализации.
В современных версиях MS Excel дополнительно отмечается чекбокс «Доверять доступ к объектной модели макросов VBA» в разделе «Центр управления безопасности».

Исходный код контроллера инициализации
Attribute VB_Name = "mWbInit" '*************************************************************************** ' Module "mWbInit.bas" ' Controller for automatic initialization of VBA modules ' ' Copyright (c) 2022, "Nikolay E. Garbuz" <nik_garbuz@list.ru> ' ' This program is free software: you can redistribute it and/or modify ' it under the terms of the GNU Lesser General Public License version 3 as ' published by the Free Software Foundation. ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY; without even the implied warranty of ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ' GNU General Public License for more details. ' ' You should have received a copy of the GNU Lesser General Public License ' along with this program. If not, see <http://www.gnu.org/licenses/>. ' ' Authored by Nikolay Garbuz <nik_garbuz@list.ru> ' Modified by ' ' TAB Size .EQ 4 '*************************************************************************** Option Explicit Option Compare Text ' Public Sub moduleInit(ByRef Wb As Workbook) Public Const vbaMODULE_INIT As String = "moduleInit" ' Public Sub moduleLeave(ByRef Wb As Workbook) Public Const vbaMODULE_LEAVE As String = "moduleLeave" ' Call vbaWbModuleControl vbaMODULE_INIT ' for initialization ' Call vbaWbModuleControl vbaMODULE_LEAVE ' for release Public _ Sub vbaWbModuleControl( _ subName As String, _ Optional printDebugOnly As Boolean = False _ ) vbaWbModuleRun ThisWorkbook, subName, printDebugOnly End Sub Public _ Sub vbaWbModuleRun( _ ByRef Wb As Workbook, _ subName As String, _ Optional printDebugOnly As Boolean = False _ ) Dim i As Integer Dim subList() As String i = vbaSubroutineList(ThisWorkbook, subName, subList) If i > 0 Then If printDebugOnly Then Debug.Print Join(subList(), vbCrLf) Else For i = LBound(subList) To UBound(subList) Application.Run subList(i), Wb Next i End If End If Erase subList End Sub Private _ Function vbaSubroutineList( _ ByRef Wb As Workbook, _ sName As String, _ ByRef sList() As String _ ) As Integer Dim i As Long Dim j As Long Dim k As Long Dim l As Long Dim sc As Long Dim fc As Long Dim sLine As String Dim modName As String Dim subName As String Dim chkName As String ReDim sList(0) i = InStr(sName, ".") If i > 0 Then modName = Left(sName, i - 1) subName = Mid(sName, i + 1) Else modName = "" subName = sName End If sc = vbaModuleIdx(Wb, modName) If sc > 0 Then fc = sc Else sc = 1 fc = Wb.VBProject.VBComponents.Count End If With Wb.VBProject.VBComponents For i = sc To fc l = .Item(i).CodeModule.CountOfLines For j = 1 To l chkName = "" Do sLine = .Item(i).CodeModule.Lines(j, 1) If Right(sLine, 1) = "_" Then chkName = chkName & Left(sLine, Len(sLine) - 1) j = j + 1 Else chkName = chkName & sLine Exit Do End If Loop chkName = vbaRemComment(chkName) chkName = vbaRemPrefix(chkName) chkName = vbaRemIdentLine(chkName) chkName = vbaSubroutineName(chkName) If chkName <> "" Then If subName = "*" Or StrComp(subName, chkName) = 0 Then If UBound(sList) < k Then ReDim Preserve sList(UBound(sList) + 10) End If sList(k) = .Item(i).Name & "." & chkName k = k + 1 End If End If Next j Next i End With If k > 0 Then ReDim Preserve sList(k - 1) End If vbaSubroutineList = k End Function Private _ Function vbaModuleIdx( _ ByRef Wb As Workbook, _ sModuleName As String _ ) As Integer Dim i As Integer Dim m As String vbaModuleIdx = 0 With Wb.VBProject.VBComponents For i = 1 To .Count m = .Item(i).Name If StrComp(sModuleName, m) = 0 Then vbaModuleIdx = i Exit Function End If Next i End With End Function Private _ Function vbaSubroutineName(sLn As String) As String Const maskSubName As String = "sub *(*)*" Const maskFuncName As String = "function *(*)*" Dim p_space As Integer Dim p_bra As Integer Dim sn As String sn = "" If (sLn Like maskSubName) Or (sLn Like maskFuncName) Then p_space = InStr(sLn, " ") + 1 p_bra = InStr(sLn, "(") sn = Mid(sLn, p_space, p_bra - p_space) End If vbaSubroutineName = Trim(sn) End Function Private _ Function vbaRemComment(sLn As String) As String Const comSymbols = "REM ,', REM ,: REM " Dim i As Long Dim s As String Dim pc As Long Static csym() As String On Error GoTo InitArray i = 0 Do s = csym(i) pc = InStr(sLn, s) If pc = 1 Then sLn = "" Exit Do Else If pc > 1 And i > 0 Then sLn = Left(sLn, pc - 1) Exit Do End If End If i = i + 1 Loop Until i > UBound(csym) vbaRemComment = sLn On Error GoTo 0 Exit Function InitArray: csym = Split(comSymbols, ",") s = csym(0) Resume Next End Function Private _ Function vbaRemPrefix(sLn As String) As String Const prefixKeys = "Public ,Private ,Friend ,Static " Dim i As Long Dim s As String Dim ps As Long Dim pf As Long Static pref() As String On Error GoTo InitArray i = 0 Do s = pref(i) ps = InStr(sLn, s) If ps > 0 Then pf = ps + Len(s) If ps = 1 Then sLn = Mid(sLn, pf) Else sLn = Left(sLn, ps - 1) & Mid(sLn, pf) End If End If i = i + 1 Loop Until i > UBound(pref) vbaRemPrefix = sLn On Error GoTo 0 Exit Function InitArray: pref = Split(prefixKeys, ",") s = pref(0) Resume Next End Function Private _ Function vbaRemIdentLine(sLn As String) As String Const lnSymbols = " " & vbTab & vbCr & vbLf While sLn <> "" And InStr(lnSymbols, Left(sLn, 1)) > 0 sLn = Mid(sLn, 2) Wend vbaRemIdentLine = sLn End Function