问题 使用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,但有时我同时拥有原件和副本。

有没有办法刷新来电 Remove 和 Import 所以他们适用自己?我想打个电话 Save 每个之后的函数,如果Application对象中有一个,虽然如果在导入过程中出现问题,这可能会导致丢失。

想法?

编辑:更改标签 synchronization 至 version-control


10867
2018-01-13 19:18


起源

+1聪明的小方法做一些自制版本控制。我自己应该这样做。 - Jean-François Corbett
它的灵感来自于 这个问题 这里是StackOverflow - 我的版本只是一个适度的翻拍。 - CamilB
我没有创建这样的东西,但我会尝试的是:从另一个工作簿/插件调用;首先备份工作簿,立即执行所有删除操作,保存,一次导入所有内容。您还可以使用Rob Bovey的Code Cleaner的COM版本。您可以设置对它的引用并访问导入,导出和其他功能。我很想知道你发现了什么。 - Doug Glancy
@DougGlancy:我尝试先删除所有删除,然后重新导入,但不知何故,我的数组中包含模块名称的函数消失了,即使它是全局的。但这是另一个问题。我有自己的想法,我会试试。 +1表示“从另一个工作簿中调用”。 - CamilB


答案:


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

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

12
2017-11-13 22:20



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


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

  1. 在发布问题后我的第一次尝试就是这个(剧透:它 几乎 工作):

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

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

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

  2. 第二次解决尝试(剧透:再次,它 几乎 工作):

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

    问题:当导入循环开始时(在其他单击处理程序内),上述字符串数组为空。当删除循环结束时肯定存在 - 我用Debug.Print打印它。我猜它是由删除(??)取消分配的。这将是 愚蠢的问题没有。 2。如果没有包含模块名称的字符串数组,则导入循环不会执行任何操作,因此此解决方法失败。

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

    我使用了2号解决方案,而不是将模块名称存储在字符串数组中,而是将它们存储在辅助工作表的一行中(我称之为“Devel”)。

就是这样。如果有人可以解释 愚蠢的问题没有。 1 和 愚蠢的问题没有。 2我求求你这样做。他们可能不是那么愚蠢 - 我仍然处于VBA的开端,但我对其他(理智和现代)语言的编程有扎实的了解。

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


1
2018-01-14 10:39



纯粹猜测:我猜想Excel的VBA引擎无法处理(可能是已编译的)VBA函数中的removemodule。 (这是做什么的,重新编译VBA并替换它来完成工作?太难了。)等到你退出VBA功能对我来说是有意义的。 - ta.speot.is
@ todda.speot.is:这是有道理的,但有些模块确实在循环过程中被删除了(至少,在我开始理解发生了什么之前,它可能与其他模块一样很奇怪) )。 VBA只是卡在一个特定的模块上,之后拒绝继续。 - CamilB
更多猜测:有些模块有全局变量,有些模块没有。那些没有被删除的很好,因为没有必要重新编译其他模块。具有全局变量(或者可能从您的函数运行的模块引用)的那些在执行结束之前无法删除。 - ta.speot.is
@ todda.speot.is:好的,现在这很有趣。它确实有意义。但是再次,这是Visual Basic,所以事情并不需要有意义:在删除时卡住的模块没有全局变量,也没有模块在它之前。对不起......无论如何,你的评论都值得+1。 - CamilB


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

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

我在导入过程中没有任何重复。


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


1
2017-07-09 07:41





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

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

现在一切都很完美。虽然,我可能会回去删除现在冗余的模块重命名并删除第二个程序,看看这是否能解决我原来的问题。这是解决方案的另一个主题......

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


1
2017-11-18 15:41





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

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

如果需要管理所有源代码,更好的解决方案是使用某些脚本或工具从头开始“构建”您的工作簿,或者切换到更适合的编程语言(即不在Office套件中生成的语言)软件;)我没试过,但你可以看看这里: Excel VBA代码模块的源代码控制


0
2017-07-11 18:35