2009-04-01 4 views
19

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

У меня есть модуль под названием 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 к этим именам модулей - либо при загрузке, либо при удалении (я не уверен, какой).

+0

Я забыл добавить, что я немного подумал и придумал это: http://grumpyop.wordpress.com/2009/04/20/version-control-for-excel-workbooks-part-2/ Примечание. что в комментариях есть что-то вроде VBAMaven, которое выглядит как какая-то внешняя служба, которая может помочь. –

ответ

4

Посмотрите страницу VBAMaven. У меня есть доморощенные решения, которые используют одни и те же понятия. У меня есть общая библиотека с кучей исходного кода, сборка муравьев и скрипт «импорт» VB. Ant управляет сборкой, которая берет чистый файл excel и вставляет в него необходимый код. @Mike абсолютно прав - любые дублирующие определения модулей автоматически будут содержать число, добавленное к имени модуля. Кроме того, классные модули (как и в Листе и этой книге) требуют специального лечения. Вы не можете создавать эти модули, вы должны прочитать входной файл и записать буфер в соответствующий модуль. Это VB-скрипт, который я использую для этого. Раздел, содержащий текст с разделителями (т. Е. Файл @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 [email protected]" 

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 [email protected]@common [email protected] 
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 
+0

Спасибо за это. Я просмотрел ваш скрипт и vbaMaven, и похоже, что оба метода создают новую таблицу и копируют макросы в них. Это верно? Я надеялся найти способ загрузки макросов в электронную таблицу при открытии электронной таблицы, но я полностью не смог заставить это работать. Возможно, я пытаюсь сделать что-то, что не может быть сделано с Excel. Теперь я начинаю смотреть на замену моих макросов внешним скриптом, который обращается к Excel через COM, поэтому, по крайней мере, я могу сохранить скрипт под контролем версий. –

+0

Да, основная идея - это «пустой» лист распространения, который копируется и код копируется в него. Если бы я знал о vbaMaven, прежде чем я начал, я просто мог бы пойти с ним ... Причина, по которой я выбрал этот маршрут, заключается в том, что каждая строка кода может находиться под контролем источника. Если у вас есть код в рабочей книге, который загружает другой код, то модуль загрузки кода не находится под тем же уровнем контроля, что и все остальное, - он находится внутри файла excel. – DaveParillo

+0

Кроме того, вы должны очень тщательно модифицировать код события в событиях Auto_Open или Workbook_Open. Попытка редактировать объект ThisWorkbook из функции Auto_Open - это быстрый путь к сбою excel. – DaveParillo

2

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

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

+0

Спасибо за ответ Майк. Возможно, вы правы, AddIn может быть единственным способом сделать это. Я пытался избежать решения AddIn, потому что хотел бы сохранить макросы в SubVersion, поэтому я предпочел бы хранить их в виде текстовых файлов, чтобы я мог слить и легко разложить, а не бинарный XLA-файл. –

+0

Возможно, вы используете крошечный аддон для загрузки модулей? –

+0

Я не уверен, что использование AddIn поможет вам в этой ситуации. Чтобы загрузить не-двоичные модули VBA в excel, мне все равно придется запускать какой-то код при открытии Workbook, и я не думаю, что сохранение кода Loader в AddIn изменит суть проблемы , –

3

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

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

Чтобы избежать использования модуля в стеке вызовов, запустите свой код с помощью приложения.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 без каких-либо других уловок

+1

СПАСИБО. Это была моя точная проблема почти 4 года спустя. – enderland

12

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

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

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

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

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

+1

Я только что прошел весь процесс сборки, и есть две проблемы, я хотел бы спросить, если вы выяснили: 1. В ленте надстроек не отображается меню vbaDeveloper, если только я не запустил refreshMenu (), предоставленный в модуле меню 2. Экспорт кода происходит не автоматически, вместо этого я должен запускать действие вручную –

+3

1 Меню будет создано событием workbook_open. Для этого, чтобы построить сбор, сохраните и закройте надстройку. Затем, после его открытия, будет запущено событие workbook_open, если вы не отключили его вручную: Application.EnableEvents = False 2 Автоматический импорт/экспорт по умолчанию отключен: https://github.com/hilkoc/vbaDeveloper/issues/8 – CodeKid

Смежные вопросы