Промывка изменений, внесенных в VBProject.Компоненты VB в Excel с использованием VBA

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

есть ли способ сбросить вызовы Remove и Import чтобы они сами? Я думаю позвонить Save функция после каждого, если есть один в объекте приложения, хотя это может привести к потерям, если что-то пойдет не так во время импорта.

идеи?

Edit: изменен тег synchronization to version-control.

5 ответов


это живой массив, вы добавляете и удаляете элементы во время итерации, тем самым изменяя номера индексов. Попробуйте обработать массив назад. Вот мое решение без какой-либо обработки ошибок:

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 здесь... Мне удалось обойти эту странную проблему, но я не нашел истинного решения. Вот что я сделал.

  1. моя первая попытка после публикации вопроса была такой (спойлер: it почти работала):

    продолжайте удалять отдельно от импорта, но в той же процедуре. Это означает, что у меня было 3 цикла - один для хранения списка имен модулей (как простых строк), другой для удаления модулей, а другой для импорта модулей из файлы (на основе имен, которые были сохранены в вышеупомянутом списке).

    проблема: некоторые модули все еще находились в проекте, когда цикл удаления закончился. Почему? Я не могу объяснить. Я отмечу это как глупая проблема нет. 1. Затем я попытался разместить Remove вызов для каждого модуля внутри цикла это продолжало пытаться удалить этот единственный модуль, пока он не смог найти его в проекте. Это застряло в бесконечном цикле для определенного модуля - я не могу сказать что в нем такого особенного?

    в конце концов я понял, что модули были действительно удалены только после того, как Excel найдет время, чтобы очистить свои мысли. Это не сработало с приложением.Ждать.)( В настоящее время запущенный код VBA фактически должен был закончиться, чтобы это произошло. Странный.

  2. вторая попытка обхода (спойлер: опять же, это почти работала):

    чтобы дать Excel необходимое время для дыхания после удаления, I разместил цикл удаления внутри обработчика нажатия кнопки (без цикла "вызов удалить, пока он не исчезнет") и цикл импорта в обработчике нажатия другой кнопки. Конечно, мне нужен был список имен модулей, поэтому я сделал его глобальным массивом строк. Он был создан в обработчике click перед циклом удаления, и к нему должен был получить доступ цикл импорта. Должно было сработать, верно?

    проблема: вышеупомянутый массив строк был пуст, когда цикл импорта запущен (внутри другого обработчика щелчка). Он определенно был там, когда цикл удаления закончился - я напечатал его с Debug.Печать. Я предполагаю, что он был де-выделен удалениями (??). Это было бы глупая проблема нет. 2. Без строкового массива, содержащего имена модулей, цикл импорта ничего не сделал, поэтому эта работа не удалась.

  3. финал, функциональное решение. Этот работает.

    Я взял рабочий номер 2 и, вместо того, чтобы хранить имена модулей в строковом массиве, я сохранил их в строке вспомогательного листа (я назвал этот лист "Devel").

Это был он. Если кто-нибудь может объяснить глупая проблема нет. 1 и глупая проблема нет. 2, умоляю вас, сделайте это. Они, вероятно, не так глупы - я все еще в начале с VBA, но у меня есть твердые знания программирования на других (нормальных и современных) языках.

Я мог бы добавить код, чтобы проиллюстрировать глупая проблема нет. 2, но этот ответ уже давно. Если то, что я сделал, не было ясно, я помещу его здесь.


чтобы избежать дублирования при импорте, я изменил скрипт со следующей стратегией:

  • переименовать существующий модуль
  • модуль импорта
  • удалить переименованный модуль

у меня больше нет дубликатов во время импорта.


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"


Я боролся с этой проблемой в течение нескольких дней. Я построил грубую систему управления версиями, похожую на эту, хотя и не используя массивы. Модуль управления версиями импортируется в Workbook_Open, а затем вызывается процедура запуска для импорта всех модулей, перечисленных в модуле управления версиями. Все отлично работает, кроме Excel начал создавать дубликаты модулей управления версиями, потому что он будет импортировать новый модуль до завершения удаления существующего. Я работал. это путем добавления Delete к предыдущему модулю. Проблема тогда заключалась в том, что все еще было две процедуры с тем же именем. У чипа Пирсона есть код для программного удаления процедуры, поэтому я удалил код запуска из старого модуля управления версиями. Тем не менее, я столкнулся с проблемой, когда процедура не была удалена к моменту вызова процедуры запуска. Я, наконец, нашел решение в другом потоке переполнения стека, который настолько прост, что мне хочется положить голову через стену. Все, что мне нужно было сделать, это изменить способ вызова процедуры запуска с помощью

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

теперь все работает отлично. Хотя, я, вероятно, вернусь и удалю теперь избыточное переименование модуля и удаление второй процедуры и посмотрю, решит ли это только мою первоначальную проблему. Вот другая нить с решением...

управление версиями модулей кода Excel VBA


обходной путь переименования, импорта и удаления не работал в моем случае. Кажется (но это чистая спекуляция), что Excel может сохранить скомпилированные объекты в своем .XLMS-файл, и при повторном открытии этого файла эти объекты перезагружаются в памяти до возникновения функции ThisWorkbook_open. И это приводит к переименованию (или удалению) определенных модулей к сбою или задержке (даже при попытке заставить его с помощью вызова DoEvents). Единственное решение, которое я нашел, - использовать .Двоичный формат XLS. Для по какой-то неясной причине (я подозреваю, что скомпилированные объекты не включены в файл), это работает для меня.

вы должны знать, что вы не сможете повторно импортировать какой-либо модуль, используемый/используемый или на который ссылаются во время выполнения кода импорта (переименование завершится ошибкой 32813/удаление модуля будет отложено до тех пор, пока вы не попытаетесь импортировать, добавив раздражающие " 1 " в конце имен модулей). Но для любого другого модуля, он должен работать.

Если весь ваш источник код должен управляться, лучшим решением было бы "построить" вашу книгу с нуля с помощью какого-либо скрипта или инструмента или переключиться на более подходящий язык программирования (т. е. тот, который не живет внутри программного обеспечения Office suite ;) я не пробовал, но вы можете посмотреть здесь: управление версиями модулей кода Excel VBA.