【问题标题】:Flushing changes made to VBProject.VBComponents in Excel using VBA使用 VBA 在 Excel 中刷新对 VBProject.VBComponents 所做的更改
【发布时间】:2012-02-09 23:26:27
【问题描述】:

在以编程方式删除模块然后从文件中重新导入它们时,我在 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 为后缀,但有时我同时拥有原件和副本。

有没有办法刷新对Remove 和Import 的调用,以便它们自己应用?我正在考虑在每个之后调用一个Save 函数,如果 Application 对象中有一个函数,但如果在导入过程中出现问题,这可能会导致损失。

想法?

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

【问题讨论】:

  • +1 做一些自制版本控制的聪明小方法。我应该自己做这样的事情。
  • 它的灵感来自 StackOverflow 上的 this question - 我的版本只是一个适度的翻拍。
  • 我还没有创建这样的东西,但我会尝试的事情是:从另一个工作簿/插件调用;首先备份工作簿,一次完成所有删除,一次保存,一次导入。您也可以使用 Rob Bovey 的 Code Cleaner 的 COM 版本。您可以设置对它的引用并访问导入、导出和其他功能。我很想看看你发现了什么。
  • @DougGlancy:我尝试先进行所有删除,然后重新导入,但不知何故,我保存模块名称的数组在函数之间消失了,即使它是全局的。但这是另一个问题。我有一些自己的想法,我会尝试的。 +1 建议“从另一个工作簿调用”。

标签: excel vba memory version-control flush


【解决方案1】:

与本题题目相关的另一种情况:

我在循环中添加组件(来自 python):

for file in files_to_import:
    project.VBComponents.Import(file)

这有时会损坏工作簿,有时不会。 它看起来像这样简单:

for file in files_to_import:
    project.VBComponents.Import(file)
    print(project.VBComponents.Count)

帮助“刷新” vbproject 并解决问题?

【讨论】:

    【解决方案2】:

    我在这个问题上花了很长时间。关键是任何被删除的 VBComponent 实际上在执行它的过程结束之前并没有被删除。因为重命名是立即完成的,所以这是将新导入的组件排除在外的首选方法。

    顺便说一句:对于版本控制,我使用易于使用且完美的 Github,特别是当仅导出更改的组件时 - 我就是这样做的。 有兴趣的可以看看公共 Github repo Excel-VB-Components-Management-Services 或相关帖子 Programmatically updating or synchronizing VBA code of Excel VB-Project Components

    【讨论】:

      【解决方案3】:

      不确定它是否对某人有帮助... 在 xlsm 容器中(如果您将其作为 zip 文件打开),您可以找到一个名为 vbaproject.xml 的文件。您可以覆盖它以“更新” VBProject 的全部内容(所有标准模块、类模块、用户表单、表格模块和此工作簿模块)。 使用这种方法,您可以在工作簿的副本中创建“新”代码等,最后覆盖目标工作簿中的 vbproject 文件。这可能避免了导出/导入模块的需要和重复的痛苦。 (抱歉没时间发代码)

      【讨论】:

        【解决方案4】:

        我这几天一直在努力解决这个问题。我构建了一个与此类似的粗略版本控制系统,尽管没有使用数组。在 Workbook_Open 上导入版本控制模块,然后调用启动过程来导入版本控制模块中列出的所有模块。一切都很好,除了 Excel 开始创建重复的版本控制模块,因为它会在删除现有模块完成之前导入新模块。我通过将 Delete 附加到上一个模块来解决这个问题。那么问题是仍然有两个同名的程序。 Chip Pearson 有一些用于以编程方式删除过程的代码,所以我从旧版本控制模块中删除了启动代码。尽管如此,我还是遇到了一个问题,即在调用启动过程时该过程尚未被删除。我终于在另一个堆栈溢出线程上找到了一个解决方案,它非常简单,让我想把头穿过一堵墙。我所要做的就是改变我调用启动过程的方式,使用

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

        现在一切正常。不过,我可能会回去删除现在多余的模块重命名并删除第二个过程,看看这是否能解决我原来的问题。这是解决方案的另一个线程......

        Source control of Excel VBA code modules

        【讨论】:

          【解决方案5】:

          重命名、导入和删除解决方法在我的情况下不起作用。似乎(但这纯粹是推测)Excel 可能会将已编译的对象保存在其 .XLMS 文件中,并且当重新打开此文件时,这些对象会在 ThisWorkbook_open 函数发生之前重新加载到内存中。这会导致某些模块的重命名(或删除)失败或延迟(即使尝试使用 DoEvents 调用强制它)。我发现的唯一解决方法是使用 .XLS 二进制格式。由于一些模糊的原因(我怀疑编译的对象没有捆绑在文件中),它对我有用。

          您必须知道,在您的导入代码运行时,您将无法重新导入正在/已经使用或引用的任何模块(重命名将失败并出现错误 32813/模块的删除将延迟到在您尝试导入之后,在模块名称的末尾添加烦人的“1”)。但对于任何其他模块,它应该可以工作。

          如果您的所有源代码都需要进行管理,更好的解决方案是使用一些脚本或工具从头开始“构建”您的工作簿,或者切换到更适合的编程语言(即不存在于内部的语言)一个Office套件软件;)我没试过,但你可以看这里:Source control of Excel VBA code modules。

          【讨论】:

            【解决方案6】:

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

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

            在导入过程中我不再有重复。


            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”中的代码

            【讨论】:

              【解决方案7】:

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

              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
              

              【讨论】:

              • 如果可能的话,我会给 +2。首先,您揭示了错误的真正原因:编辑实时数组;这是我的一个非常愚蠢的错误,当时我不太确定这些阵列是如何工作的。原来它们实际上是Collection 对象。其次,您的代码可以正确处理不同类型的模块,并使用正确的文件扩展名保存它们。不久前我也修改了我的代码来做到这一点,这确实非常重要; +1 显示您的代码,这样人们就会知道。
              【解决方案8】:

              在这里...我设法解决了这个奇怪的问题,但我还没有找到真正的解决方案。这就是我所做的。

              1. 我在发布问题后的第一次尝试是这样的(剧透:它几乎奏效了):

                将删除与导入分开,但过程相同。这意味着我有 3 个循环 - 一个用于存储模块名称列表(作为纯字符串),另一个用于删除模块,另一个用于从文件中导入模块(基于存储在上述列表中的名称) .

                问题:移除循环结束时,一些模块仍在项目中。为什么?我无法解释。我会将此标记为愚蠢的问题编号。 1。然后,我尝试将每个模块的Remove 调用放在一个循环中,该循环一直试图删除该单个模块,直到它在项目中找不到它。对于某个模块,这陷入了无限循环——我不知道那个特定模块有什么特别之处。

                我最终发现,只有在 Excel 抽出时间理清思路后,才真正删除了这些模块。这不适用于 Application.Wait()。当前运行的 VBA 代码实际上需要结束才能发生这种情况。很奇怪。

              2. 第二次变通尝试(剧透:再次,它几乎奏效了):

                为了在删除后给 Excel 提供所需的呼吸时间,我将删除循环放置在按钮单击处理程序中(没有“调用删除直到它消失”循环),并将导入循环放置在另一个按钮的单击处理程序中。当然,我需要模块名称列表,所以我把它做成了一个全局字符串数组。它是在点击处理程序中,在删除循环之前创建的,并且应该由导入循环访问。应该有效吧?

                问题:当导入循环开始时(在另一个点击处理程序内),上述字符串数组为空。当删除循环结束时,它肯定就在那里——我用 Debug.Print 打印了它。我猜它被删除(??)取消分配。这将是愚蠢的问题。 2。如果没有包含模块名称的字符串数组,导入循环什么也不做,所以这个解决方法失败了。

              3. 最终的功能性解决方法。这个有效。

                我采用了解决方法 2,而不是将模块名称存储在字符串数组中,而是将它们存储在辅助表的一行中(我将此表称为“开发”)。

              就是这样。如果有人能解释愚蠢的问题,没有。 1和愚蠢的问题没有。 2,求求你,照做。他们可能没那么愚蠢 - 我还是 VBA 的初学者,但我对其他(理智的和现代的)语言的编程有扎实的了解。

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

              【讨论】:

              • 纯猜测:我猜 Excel 的 VBA 引擎无法处理(可能是已编译的)VBA 函数中间的删除模块。 (怎么办,重新编译 VBA 并替换它来完成工作?太难了。)等到你退出 VBA 函数对我来说是有意义的。
              • @todda.speot.is:这是有道理的,但是在循环过程中确实删除了一些模块(我知道,至少,它可能与其他模块的行为也很奇怪,之前我开始明白发生了什么)。 VBA 只卡在特定模块,之后拒绝继续。
              • 更多推测:一些模块具有全局变量,而另一些则没有。那些没有的可以被删除,因为不需要重新编译其他模块。那些具有全局变量(或者可能从您的函数运行的模块中引用)的变量在执行结束之前无法删除。
              • @todda.speot.is:好的,现在这很有趣。这确实有道理。但是再一次,这是 Visual Basic,所以事情不需要有意义:被卡在删除中的模块没有全局变量,之前的模块也没有。抱歉......无论如何,你的两个 cmets 都应该得到 +1。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-10-22
              • 2014-07-14
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多