Управление исходным кодом модулей кода Excel VBA - PullRequest
21 голосов
/ 01 апреля 2009

Я хотел бы иметь возможность контролировать источники модулей VBA моей электронной таблицы Excel (в настоящее время использую Excel 2003 SP3), чтобы я мог делиться и управлять кодом, используемым кучей различных электронных таблиц - и поэтому я хотел бы повторно - загрузить их из файлов при открытии электронной таблицы.

У меня есть модуль с именем Loader.bas, который я использую для выполнения большей части работы с ослом (загрузка и выгрузка любых других необходимых модулей) - и я бы хотел иметь возможность загрузить его из файл, как только открывается электронная таблица.

Я прикрепил следующий код к событию Workbook_Open (в классе ThisWorkbook).

Private Sub Workbook_Open()
    Call RemoveLoader
    Call LoadLoader
End Sub

Где RemoveLoader (также в классе ThisWorkbook) содержит следующий код:

Private Sub RemoveLoader()
    Dim y As Integer
    Dim OldModules, NumModules As Integer
    Dim CompName As String

    With ThisWorkbook.VBProject
        NumModules = ThisWorkbook.VBProject.VBComponents.Count
        y = 1
        While y <= NumModules
            If .VBComponents.Item(y).Type = 1 Then
                CompName = .VBComponents.Item(y).Name
                If VBA.Strings.InStr(CompName, "Loader") > 0 Then
                    OldModules = ThisWorkbook.VBProject.VBComponents.Count
                    .VBComponents.Remove .VBComponents(CompName)
                    NumModules = ThisWorkbook.VBProject.VBComponents.Count
                    If OldModules - NumModules = 1 Then
                        y = 1
                    Else
                        MsgBox ("Failed to remove " & CompName & " module from VBA project")
                    End If
                End If
            End If
            y = y + 1
        Wend
    End With
End Sub

Что, вероятно, немного усложнено и немного грубо - но я пытаюсь сделать все возможное, чтобы загрузить его для внешнего модуля!

Часто, когда я открываю электронную таблицу, функция RemoveLoader обнаруживает, что в проект VBA уже включен модуль "Loader1", который он не может удалить, а также не удается загрузить новый модуль Loader из файла.

Есть идеи, возможно ли то, что я пытаюсь сделать? Excel, похоже, очень любит добавлять 1 к этим именам модулей - либо при загрузке, либо при удалении (я не уверен, какой именно).

Ответы [ 5 ]

15 голосов
/ 23 сентября 2014

Здесь есть отличное решение проблемы контроля версий vba: https://github.com/hilkoc/vbaDeveloper

Приятно то, что он экспортирует ваш код автоматически , как только вы сохраните свою книгу. Кроме того, когда вы открываете книгу, она импортирует код.

Вам не нужно запускать сценарии сборки или команды maven, и вам не нужно вносить какие-либо изменения в свои рабочие книги. Это работает для всех.

Это также решило проблему импорта, когда такие модули, как ModName, импортируются как ModName1 в дубликат модуля. Импорт работает должным образом, даже если он выполняется несколько раз.

В качестве бонуса он поставляется с простым средством форматирования кода, которое позволяет форматировать код vba по мере его написания в редакторе VBA.

4 голосов
/ 10 сентября 2009

Посмотрите на страницу VBAMaven. У меня есть доморощенное решение, которое использует те же понятия. У меня есть общая библиотека с кучей исходного кода, сборкой муравья и VB-скриптом import. Ant контролирует сборку, которая берет пустой файл Excel и помещает в него необходимый код. @Mike абсолютно прав - любые повторяющиеся определения модуля автоматически добавляются к имени модуля. Кроме того, классы модулей (как в Sheet и ThisWorkbook) классы требуют особой обработки. Вы не можете создавать эти модули, вы должны прочитать входной файл и записать буфер в соответствующий модуль. Это сценарий VB, который я сейчас использую для этого. Раздел, содержащий текст с разделителями @ (т.е. файл @build @), является заполнителями - ant build заменяет эти теги значимым содержимым. Это не идеально, но работает для меня.

''
' Imports VB Basic module and class files from the src folder
' into the excel file stored in the bin folder. 
'

Option Explicit

Dim pFileSystem, pFolder,  pPath
Dim pShell
Dim pApp, book

Dim pFileName

pFileName = "@build file@"

Set pFileSystem = CreateObject("Scripting.FileSystemObject")

Set pShell = CreateObject("WScript.Shell")
pPath = pShell.CurrentDirectory

If IsExcelFile (pFileName) Then
    Set pApp = WScript.CreateObject ("Excel.Application")
    pApp.Visible = False
    Set book = pApp.Workbooks.Open(pPath & "\build\" & pFileName)
Else
    Set pApp = WScript.CreateObject ("Word.Application")
    pApp.Visible = False
    Set book = pApp.Documents.Open(pPath & "\build\" & pFileName)
End If


'Include root source folder code if no args set
If Wscript.Arguments.Count = 0 Then
    Set pFolder = pFileSystem.GetFolder(pPath & "\src")
    ImportFiles pFolder, book
    '
    ' Get selected modules from the Common Library, if any
    @common path@@common file@
Else
    'Add code from subdirectories of src . . .
    If Wscript.Arguments(0) <> "" Then
        Set pFolder = pFileSystem.GetFolder(pPath & "\src\" & Wscript.Arguments(0))
        ImportFiles pFolder, book
    End If
End If





Set pFolder = Nothing
Set pFileSystem = Nothing
Set pShell = Nothing


If IsExcelFile (pFileName) Then
    pApp.ActiveWorkbook.Save
Else
    pApp.ActiveDocument.Save
End If

pApp.Quit
Set book = Nothing
Set pApp = Nothing


'' Loops through all the .bas or .cls files in srcFolder
' and calls InsertVBComponent to insert it into the workbook wb.
'
Sub ImportFiles(ByVal srcFolder, ByVal obj)
    Dim fileCollection, pFile
    Set fileCollection = srcFolder.Files
    For Each pFile in fileCollection
        If Right(pFile, 3) = "bas _
          Or Right(pFile, 3) = "cls _
          Or Right(pFile, 3) = "frm Then
            InsertVBComponent obj, pFile
        End If
    Next
    Set fileCollection = Nothing
End Sub


'' Inserts the contents of CompFileName as a new component in 
'  a Workbook or Document object.
'
'  If a class file begins with "Sheet", then the code is
'  copied into the appropriate code module 1 painful line at a time.
'
'  CompFileName must be a valid VBA component (class or module) 
Sub InsertVBComponent(ByVal obj, ByVal CompFileName)
    Dim t, mName
    t = Split(CompFileName, "\")
    mName = Split(t(UBound(t)), ".")
    If IsSheetCodeModule(mName(0), CompFileName) = True Then
        ImportCodeModule obj.VBProject.VBComponents(mName(0)).CodeModule, _
                         CompFileName
    Else
        If Not obj Is Nothing Then
            obj.VBProject.VBComponents.Import CompFileName
        Else
            WScript.Echo  "Failed to import " & CompFileName
        End If
    End If 
End Sub

''
' Imports the code in the file fName into the workbook object
' referenced by mName.
' @param target destination CodeModule object in the excel file
' @param fName file system file containing code to be imported
Sub ImportCodeModule (ByVal target, ByVal fName)
    Dim shtModule, code, buf    
    Dim fso
    Set fso = CreateObject("Scripting.FileSystemObject") 
    Const ForReading = 1, ForWriting = 2, ForAppending = 3
    Const TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0

    Set buf = fso.OpenTextFile(fName, ForReading, False, TristateUseDefault)
    buf.SkipLine
    code = buf.ReadAll

    target.InsertLines 1, code
    Set fso = Nothing
End Sub


''
' Returns true if the code module in the file fName
' appears to be a code module for a worksheet.
Function IsSheetCodeModule (ByVal mName, ByVal fName)
    IsSheetCodeModule = False
    If mName = "ThisWorkbook" Then
       IsSheetCodeModule = False
    ElseIf Left(mName, 5) = "Sheet" And _
       IsNumeric(Mid (mName, 6, 1)) And _
       Right(fName, 3) = "cls Then
       IsSheetCodeModule = True
    End If
End Function

''
' Returns true if fName has a xls file extension
Function IsExcelFile (ByVal fName)
    If Right(fName, 3) = "xls" Then
        IsExcelFile = True
    Else
        IsExcelFile = False
    End If 
End Function
3 голосов
/ 29 ноября 2009

Я работал над этим в течение нескольких месяцев. Я думаю, что я понял это.

Если VB Project пытается удалить модуль, содержащий что-то в стеке вызовов, он задерживает удаление до тех пор, пока в стеке вызовов не появится заменяемый модуль.

Чтобы модуль не находился в стеке вызовов, запустите свой код с помощью Application.OnTime

Private Sub Workbook_Open()

    'WAS: module_library (1)

    Application.OnTime (Now + TimeValue("00:00:01")), "load_library_kicker_firstiter"

End Sub

Если вы лечите свой код, как я, вам также придется запустить код, который перезаписывает код вызова с помощью той же стратегии.

Я еще не проводил подробное тестирование, я нахожусь в режиме общего празднования, но это очень сильно приближает меня к прямому 99,9% коду самовосстановления в автономном файле .xls без каких-либо других уловок

2 голосов
/ 01 апреля 2009

Обычно «Loader1» происходит, когда Excel просят импортировать модуль, и модуль уже существует с таким именем. Поэтому, если вы импортируете «Loader», загрузите его снова, и вы получите «Loader1». Это может быть связано с тем, что Excel не знает (или, может быть, просто не заботится), действительно ли это то же самое или новая функциональность, имеющая только одно и то же имя, имеет такое же имя модуля, поэтому он все равно импортирует его.

Я не могу придумать идеального решения, но думаю, что был бы склонен попытаться включить логику загрузки / выгрузки в надстройку - эта вещь Workbook_Open выглядит немного уязвимой, и ее использование во всех рабочих книгах быть огромной болью, если код когда-либо должен измениться (никогда не говори никогда). Логика XLA может быть более сложной (хитрее, ловить необходимые события, с одной стороны), но, по крайней мере, она будет существовать только в одном месте.

0 голосов
/ 13 ноября 2018

Не могу оставить комментарий к комментарию

Существует отличное решение проблемы контроля версий vba. здесь: https://github.com/hilkoc/vbaDeveloper

О сохранении пользовательских VBAP-проектов с использованием этого XLAM. Попробуйте это в Build.bas:

'===============
Public Sub testImport()
    Dim proj_name As String
    Dim vbaProject As Object

    'proj_name = "VBAProject"
    'Set vbaProject = Application.VBE.VBProjects(proj_name)

    Set vbaProject = Application.VBE.ActiveVBProject
    proj_name = vbaProject.name

    Build.importVbaCode vbaProject
End Sub
'===============
Public Sub testExport()
    Dim proj_name As String
    Dim vbaProject As Object

    'proj_name = "VBAProject"
    'Set vbaProject = Application.VBE.VBProjects(proj_name)

    Set vbaProject = Application.VBE.ActiveVBProject
    proj_name = vbaProject.name

    Build.exportVbaCode vbaProject
End Sub
'===============

Это будет экспорт / импорт Active VBA Project.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...