我希望能够控制我的Excel电子表格的VBA模块(目前使用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模块。 / p>
任何想法,如果我想要做的是可能的? Excel似乎非常喜欢在这些模块名称上附加1 - 无论是在加载还是删除时(我不确定哪个)。
答案 0 :(得分:15)
这里有一个很好的解决vba版本控制问题的方法:https://github.com/hilkoc/vbaDeveloper
关于这一点的好处是,只要您保存工作簿,它就会自动导出代码 。此外,当您打开工作簿时,它会导入代码。
您不需要运行任何构建脚本或maven命令,也不需要对工作簿进行任何更改。它适用于所有人。
它还解决了导入问题,其中ModName等模块作为ModName1导入到重复模块中。导入工作正常,即使多次执行也是如此。
作为奖励,它带有一个简单的代码格式化程序,允许您在VBA编辑器中编写vba代码时格式化。
答案 1 :(得分:4)
查看VBAMaven页面。我有一个使用相同概念的本土解决方案。我有一个包含大量源代码,ant构建和“import”VB脚本的公共库。 Ant控制构建,它获取一个空白的excel文件并将所需的代码推入其中。 @Mike绝对正确 - 任何重复的模块定义都会自动在模块名称后附加一个数字。此外,类模块(如Sheet和ThisWorkbook)类需要特殊处理。您无法创建这些模块,您必须读取输入文件并将缓冲区写入适当的模块。这是我目前用来执行此操作的VB脚本。包含@分隔文本(即@build文件@)的部分是占位符 - ant构建用有意义的内容替换这些标记。它并不完美,但对我有用。
''
' 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
答案 2 :(得分:3)
我几个月来一直在努力。我想我明白了。
如果VB项目试图删除包含调用堆栈中某些内容的模块,则会延迟删除,直到调用堆栈弹出正在替换的模块。
要避免模块在调用堆栈中,请使用Application.OnTime
启动代码Private Sub Workbook_Open()
'WAS: module_library (1)
Application.OnTime (Now + TimeValue("00:00:01")), "load_library_kicker_firstiter"
End Sub
如果你像我一样自我修复你的代码,你还必须启动你的代码,用相同的策略覆盖'调用'代码。
我还没有进行大量的测试,我处于完全庆祝模式,但这让我非常接近单独的.xls文件中直接的99.9%自我修复代码而没有任何其他技巧
答案 3 :(得分:2)
当要求Excel导入模块并且已存在具有相同名称的模块时,通常会发生“Loader1”事件。因此,如果您导入“Loader”,然后再次加载它,您将获得“Loader1”。这可能是因为Excel不知道(或者可能只是不关心)如果它真的是相同的东西或刚刚发生的新功能块具有相同的模块名称,所以无论如何它都会导入它。
我无法想到一个完美的解决方案,但我认为我倾向于尝试将加载/卸载逻辑放入一个加载项中 - Workbook_Open看起来有点脆弱,并且在所有工作簿中都有它如果代码需要改变(从不说永远),那将是一个巨大的痛苦。 XLA逻辑可能更复杂(更难以捕获必要的事件,但有一件事)但至少它只存在于一个地方。
答案 4 :(得分:0)
不能发表评论
有很好的解决vba版本控制问题的方法 此处:https://github.com/hilkoc/vbaDeveloper
关于使用此XLAM保存自定义VBAProject。 在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项目。
答案 5 :(得分:0)
如果您不需要自动导出VBA代码,则以下是一个易于实现的答案。只需调用以下子项,它将在一个名为“ VC_nameOfTheWorkBook”的子文件夹中导出(作为文本)当前活动工作簿的VBA代码。如果您的项目是.xlam,则需要暂时将IsAddin属性设置为false。然后,您可以轻松地将新的子文件夹添加到Git。这是对由史蒂夫·詹森(Steve Jansen)进行的here发现的代码的略微修改。有关更完整的解决方案,请参见Ron de Bruin post。
您需要在VBE编辑器中设置对“ Microsoft Visual Basic for Applications Extensibility 5.3”和“ Microsoft Scripting Runtime”的引用。
Public Sub ExportVisualBasicCode()
Const Module = 1
Const ClassModule = 2
Const Form = 3
Const Document = 100
Const Padding = 24
Dim VBComponent As Object
Dim path As String
Dim directory As String
Dim extension As String
Dim fso As New FileSystemObject
directory = ActiveWorkbook.path & "\VC_" & fso.GetBaseName(ActiveWorkbook.Name)
If Not fso.FolderExists(directory) Then
Call fso.CreateFolder(directory)
End If
Set fso = Nothing
For Each VBComponent In ActiveWorkbook.VBProject.VBComponents
Select Case VBComponent.Type
Case ClassModule, Document
extension = ".cls"
Case Form
extension = ".frm"
Case Module
extension = ".bas"
Case Else
extension = ".txt"
End Select
On Error Resume Next
Err.Clear
path = directory & "\" & VBComponent.Name & extension
Call VBComponent.Export(path)
If Err.Number <> 0 Then
Call MsgBox("Failed to export " & VBComponent.Name & " to " & path, vbCritical)
Else
Debug.Print "Exported " & Left$(VBComponent.Name & ":" & Space(Padding), Padding) & path
End If
On Error GoTo 0
Next
End Sub