使用VBA在Excel中刷新对VBProject.VBComponents的更改

我一直在Excel中遇到一些奇怪的怪癖,同时编程删除模块,然后重新从文件中导入它们。 基本上,我有一个名为VersionControl的模块,应该将我的文件导出到预定义的文件夹,并按需重新导入它们。 这是重新导入的代码(问题在下面描述):

Dim i As Integer Dim ModuleName As String Application.EnableEvents = False With ThisWorkbook.VBProject For i = 1 To .VBComponents.Count If .VBComponents(i).CodeModule.CountOfLines > 0 Then ModuleName = .VBComponents(i).CodeModule.Name If ModuleName <> "VersionControl" Then If PathExists(VersionControlPath & "\" & ModuleName & ".bas") Then Call .VBComponents.Remove(.VBComponents(ModuleName)) Call .VBComponents.Import(VersionControlPath & "\" & ModuleName & ".bas") Else MsgBox VersionControlPath & "\" & ModuleName & ".bas" & " cannot be found. No operation will be attempted for that module." End If End If End If Next i End With 

运行后,我注意到一些模块不再出现,而有些模块有重复(例如mymodule和mymodule1)。 在单步执行代码的过程中,显然有些模块在Remove调用之后依然存在,在项目中仍然可以重新导入。 有时,这只会导致模块后缀为1 ,但有时我有原始和副本。

有没有办法刷新调用RemoveImport使他们自己应用? 我打算在每个应用程序对象之后调用Save函数,但是如果在导入过程中出现问题,这可能会导致损失。

想法?

编辑:将标签synchronization更改为version-control

这是一个实时数组,您在迭代期间添加和删除项目,从而更改索引编号。 尝试向后处理数组。 这是我的解决scheme,没有任何error handling:

 Private Const DIR_VERSIONING As String = "\\VERSION_CONTROL" Private Const PROJ_NAME As String = "PROJECT_NAME" Sub EnsureProjectFolder() ' Does this project directory exist If Len(Dir(DIR_VERSIONING & PROJ_NAME, vbDirectory)) = 0 Then ' Create it MkDir DIR_VERSIONING & PROJ_NAME End If End Sub Function ProjectFolder() As String ' Ensure the folder exists whenever we try to access it (can be deleted mid execution) EnsureProjectFolder ' Create the required full path ProjectFolder = DIR_VERSIONING & PROJ_NAME & "\" End Function Sub SaveCodeModules() 'This code Exports all VBA modules Dim i%, sName$ With ThisWorkbook.VBProject ' Iterate all code files and export accordingly For i% = 1 To .VBComponents.count ' Extract this component name sName$ = .VBComponents(i%).CodeModule.Name If .VBComponents(i%).Type = 1 Then ' Standard Module .VBComponents(i%).Export ProjectFolder & sName$ & ".bas" ElseIf .VBComponents(i%).Type = 2 Then ' Class .VBComponents(i%).Export ProjectFolder & sName$ & ".cls" ElseIf .VBComponents(i%).Type = 3 Then ' Form .VBComponents(i%).Export ProjectFolder & sName$ & ".frm" ElseIf .VBComponents(i%).Type = 100 Then ' Document .VBComponents(i%).Export ProjectFolder & sName$ & ".bas" Else ' UNHANDLED/UNKNOWN COMPONENT TYPE End If Next i End With End Sub Sub ImportCodeModules() Dim i%, sName$ With ThisWorkbook.VBProject ' Iterate all components and attempt to import their source from the network share ' Process backwords as we are working through a live array while removing/adding items For i% = .VBComponents.count To 1 Step -1 ' Extract this component name sName$ = .VBComponents(i%).CodeModule.Name ' Do not change the source of this module which is currently running If sName$ <> "VersionControl" Then ' Import relevant source file if it exists If .VBComponents(i%).Type = 1 Then ' Standard Module .VBComponents.Remove .VBComponents(sName$) .VBComponents.Import fileName:=ProjectFolder & sName$ & ".bas" ElseIf .VBComponents(i%).Type = 2 Then ' Class .VBComponents.Remove .VBComponents(sName$) .VBComponents.Import fileName:=ProjectFolder & sName$ & ".cls" ElseIf .VBComponents(i%).Type = 3 Then ' Form .VBComponents.Remove .VBComponents(sName$) .VBComponents.Import fileName:=ProjectFolder & sName$ & ".frm" ElseIf .VBComponents(i%).Type = 100 Then ' Document Dim TempVbComponent, FileContents$ ' Import the document. This will come in as a class with an increment suffix (1) Set TempVbComponent = .VBComponents.Import(ProjectFolder & sName$ & ".bas") ' Delete any lines of data in the document If .VBComponents(i%).CodeModule.CountOfLines > 0 Then .VBComponents(i%).CodeModule.DeleteLines 1, .VBComponents(i%).CodeModule.CountOfLines ' Does this file contain any source data? If TempVbComponent.CodeModule.CountOfLines > 0 Then ' Pull the lines into a string FileContents$ = TempVbComponent.CodeModule.Lines(1, TempVbComponent.CodeModule.CountOfLines) ' And copy them to the correct document .VBComponents(i%).CodeModule.InsertLines 1, FileContents$ End If ' Remove the temporary document class .VBComponents.Remove TempVbComponent Set TempVbComponent = Nothing Else ' UNHANDLED/UNKNOWN COMPONENT TYPE End If End If Next i End With End Sub 

OP在这里…我设法解决这个奇怪的问题,但我还没有find一个真正的解决scheme。 这是我做的。

  1. 我发布这个问题后的第一次尝试是(扰stream板:它几乎工作):

    保持与导入分离,但在相同的程序。 这意味着我有3个循环 – 一个用于存储模块名称列表(作为纯文本string),另一个用于删除模块,另一个用于从文件中导入模块(基于存储在上述列表中的名称) 。

    问题:当移除循环结束时,一些模块仍然在项目中。 为什么? 我无法解释。 我会把这个标记为愚蠢的问题。 1 。 然后,我试着将每个模块的Remove调用放在一个循环中 ,直到在项目中找不到该单个模块。 这被困在一个特定模块的无限循环中 – 我不能说这个特殊的模块有什么特别之处。

    我终于明白,在Excel找出一些时间来清除它的想法之后,模块才真正被删除。 这不适用于Application.Wait()。 当前正在运行的VBA代码实际上需要结束发生。 奇怪的。

  2. 第二个解决方法尝试(扰stream:再次,它几乎工作):

    为了让Excel在删除后有足够的时间来呼吸,我将删除循环放到了一个button点击处理程序(不需要“调用Remove直到它消失”循环),并且在另一个button的点击处理程序中导入循环。 当然,我需要模块名称的列表,所以我把它做成了一个全局的string数组。 它是在删除循环之前的点击处理程序中创build的,并且应该由导入循环访问。 应该工作吧?

    问题:上面的string数组在导入循环开始时是空的(在另一个点击处理程序中)。 当删除循环结束时,它确实存在 – 我用Debug.Print打印它。 我想它被清除(??)。 这将是愚蠢的问题没有。 2 。 如果没有包含模块名称的string数组,导入循环什么都不做,所以这个解决方法失败了。

  3. 最后,function解决方法。 这个工作。

    我采取了解决scheme2号,而不是将模块名称存储在一个string数组中,我将它们存储在一个辅助表(我称之为“Devel”表)。

就是这样。 如果任何人都可以解释愚蠢的问题没有。 1愚蠢的问题没有。 2 ,求求你,这样做。 他们可能并不那么愚蠢 – 我还是以VBA开头,但是我对其他(理智和现代)语言的编程有深入的了解。

我可以添加代码来说明愚蠢的问题没有。 2 ,但是这个答案已经很久了。 如果我做的不清楚,我会把它放在这里。

为了避免导入时出现重复,我使用以下策略修改了脚本:

  • 重命名现有的模块
  • 导入模块
  • 删除重命名的模块

我在import时不再有重复。


 Sub SaveCodeModules() 'This code Exports all VBA modules Dim i As Integer, name As String With ThisWorkbook.VBProject For i = .VBComponents.Count To 1 Step -1 name = .VBComponents(i).CodeModule.name If .VBComponents(i).Type = 1 Then ' Standard Module .VBComponents(i).Export Application.ThisWorkbook.Path & "\trunk\" & name & ".module" ElseIf .VBComponents(i).Type = 2 Then ' Class .VBComponents(i).Export Application.ThisWorkbook.Path & "\trunk\" & name & ".classe" ElseIf .VBComponents(i).Type = 3 Then ' Form .VBComponents(i).Export Application.ThisWorkbook.Path & "\trunk\" & name & ".form" Else ' DO NOTHING End If Next i End With End Sub 

 Sub ImportCodeModules() Dim i As Integer Dim delname As String Dim modulename As String With ThisWorkbook.VBProject For i = .VBComponents.Count To 1 Step -1 modulename = .VBComponents(i).CodeModule.name If modulename <> "VersionControl" Then delname = modulename & "_to_delete" If .VBComponents(i).Type = 1 Then ' Standard Module .VBComponents(modulename).name = delname .VBComponents.Import Application.ThisWorkbook.Path & "\trunk\" & modulename & ".module" .VBComponents.Remove .VBComponents(delname) ElseIf .VBComponents(i).Type = 2 Then ' Class .VBComponents(modulename).name = delname .VBComponents.Import Application.ThisWorkbook.Path & "\trunk\" & modulename & ".classe" .VBComponents.Remove .VBComponents(delname) ElseIf .VBComponents(i).Type = 3 Then ' Form .VBComponents.Remove .VBComponents(modulename) .VBComponents.Import Application.ThisWorkbook.Path & "\trunk\" & modulename & ".form" Else ' DO NOTHING End If End If Next i End With End Sub 

代码被粘贴到一个新的模块“VersionControl”

现在我一直在为这个问题苦苦挣扎。 我build立了一个类似于这个粗略的版本控制系统,尽pipe不使用数组。 版本控制模块导入到Workbook_Open中,然后调用启动程序导入版本控制模块中列出的所有模块。 一切都很好,除了Excel开始创build重复的版本控制模块,因为它会在删除现有模块之前导入新模块。 我通过附加删除到前面的模块解决了这个问题。 问题在于,还有两个程序名字相同。 Chip Pearson有一些编程删除程序的代码,所以我从旧版本的控制模块中删除了启动代码。 不过,我遇到了一个问题,在调用启动程序的时候程序没有被删除。 我终于find了另一个堆栈溢出线程的解决scheme,它非常简单,它使我想把我的头穿过墙壁。 我所要做的只是改变我使用启动程序的方式

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

现在一切正常。 虽然,我可能会回头删除模块的现有冗余重命名,并删除第二个程序,看看是否能解决我原来的问题。 这是解决scheme的另一个线程…

Excel VBA代码模块的源代码控制

重命名,导入和删除解决方法在我的情况下不起作用。 看起来(但这是纯粹的猜测)Excel可能会将编译对象保存在其.XLMS文件中,当重新打开这个文件时,在ThisWorkbook_open函数发生之前,这些对象会重新加载到内存中。 这导致某些模块的重命名(或删除)失败或被延迟(甚至在试图用DoEvents调用强制执行时)。 我发现唯一的解决方法是使用.XLS二进制格式。 对于一些晦涩的理由(我怀疑编译的对象没有捆绑在文件中),这对我很有用。

您必须知道,在导入代码运行时,您将无法重新导入正在使用或引用的任何模块(重命名将失败,并显示错误32813 /移除模块将被延迟,直到您尝试导入,在模块名称的末尾添加令人讨厌的'1')。 但对于任何其他模块,它应该工作。

如果您需要pipe理所有的源代码,更好的解决scheme就是使用一些脚本或工具从头开始构build您的工作簿,或者切换到更适合的编程语言(即,不在Office套件内部的语言软件;)我还没有尝试过,但你可以看看这里: Excel VBA代码模块的源代码控制 。