Сброс изменений, внесенных в VBProject.VBComponents в Excel с использованием VBA - PullRequest
11 голосов
/ 13 января 2012

Я обнаружил некоторые странные причуды в 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.

Ответы [ 5 ]

12 голосов
/ 14 ноября 2012

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

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
1 голос
/ 18 ноября 2014

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

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

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

Контроль версий модулей кода Excel VBA

1 голос
/ 09 июля 2014

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

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

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


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 голос
/ 14 января 2012

ОП здесь ... Мне удалось обойти эту странную проблему, но я не нашел правильного решения. Вот что я сделал.

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

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

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

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

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

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

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

  3. Окончательный, функциональный обходной путь. Это работает.

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

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

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

0 голосов
/ 11 июля 2014

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

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

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

...