2012-01-13 2 views
11

У меня возникли некоторые странные причуды в Excel, в то время как программно удаляются модули, а затем реимпортируются из файлов. В принципе, у меня есть модуль с именем VersionControl, который должен экспортировать мои файлы в предопределенную папку и повторно импортировать их по требованию. Это код для повторного импорта (проблемы с ней описана ниже):Сброс изменений, внесенных в VBProject.VBComponents в Excel с использованием VBA

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.

+0

+1 Clever мало способ сделать некоторые домашние управления версиями. Я должен сам сделать что-то подобное. –

+0

Это было вдохновлено [этим вопросом] (http://stackoverflow.com/questions/131605/best-way-to-do-version-control-for-ms-excel) здесь, на StackOverflow - моя версия - это всего лишь скромная переделать. – CamilB

+1

Я не создал ничего подобного, но все, что я пробовал, будет: вызов из другой книги/addin; сначала создавая резервную копию рабочей книги, делая все удаленные сразу, сохраняя, импортируя все сразу. Вы также можете повеселиться с COM-версией Code Cleaner Роба Бови. Вы можете установить ссылку на него и получить доступ к импорту, экспорту и другим функциям. Мне будет интересно узнать, что вы узнали. –

ответ

12

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

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

Я бы дал +2, если возможно. Во-первых, вы обнаружили истинную причину ошибки: редактирование живого массива; это была очень глупая ошибка, я не был уверен, как работают эти массивы.Оказывается, они фактически являются объектами «Коллекция». Во-вторых, ваш код правильно обрабатывает различные типы модулей и сохраняет их с правильным расширением файла. Я изменил свой код, чтобы сделать это слишком давно, это действительно важно; +1 для показа кода, который делает это, чтобы люди знали. – CamilB

1

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

  1. Моя первая попытка после размещения вопрос был этот (спойлер: это почти работал):

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

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

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

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

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

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

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

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

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

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

+1

Чистая спекуляция: я бы предположил, что движок VBA Excel не может обрабатывать removemodules в середине (предположительно скомпилированной) функции VBA. (Что вам нужно сделать, перекомпилируйте VBA и замените его, чтобы закончить работу? Слишком сложно.) Ожидание, когда вы выйдете из функции VBA, имеет смысл для меня. –

+0

@ todda.speot.is: Это имело бы смысл, но некоторые модули удалялись во время цикла (тот, который, как я знаю, по крайней мере, он мог вести себя странно и с другими, прежде чем я начал понимать, что происходит на). VBA только застрял в определенном модуле и отказался продолжить работу. – CamilB

+1

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

1

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

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

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


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"

0

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

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

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

1

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

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

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

Source control of Excel VBA code modules

Смежные вопросы