MS Excel — Инициализация модулей VBA

от автора

Все типы модулей VBA MS Excel позволяют создавать код, автоматически выполняемый при загрузке и/или выгрузке модуля.

Однако, для обычных модулей с макросами, VBComponent.Type=001, описание автоматической инициализации автор в сети не обнаружил.

В статье описан простой контроллер инициализации обычных модулей VBA.

Вступление

Автоматическая инициализация модуля 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.

Постановка задачи

  1. Создать автоматический вызов процедур инициализации и завершения для обычных модулей VBA.

  2. Повторить привычный механизм управления инициализацией — наличие в модуле VBA подпрограммы с известным именем.

  3. Осуществлять автоматически инициализацию и завершение только тех модулей VBA, где присутствуют соответствующие подпрограммы.

Решение задачи

Синтаксис MS EXCEL VBA, допускает:

  1. размещение в разных модулях подпрограмм с одинаковыми именами и интерфейсом;

  2. косвенный вызов подпрограмм VBA по имени внутри переменной;

  3. уточняющий синтаксис 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


ссылка на оригинал статьи https://habr.com/ru/post/682468/