Сброс изменений, внесенных в VBProject.VBComponents в Excel с использованием VBA
Я испытываю некоторые странные причуды в Excel, в то время как программно удаляет модули, а затем реимпортирует их из файлов. В принципе, у меня есть модуль с именем VersionControl, который должен экспортировать мои файлы в предопределенную папку и reimport их по требованию. Это код для реимпортации (проблема с ним описана ниже):
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
Это живой массив, который вы добавляете и удаляете элементы во время итерации, тем самым изменяя номера индексов. Попробуйте обработать массив назад. Вот мое решение без обработки ошибок:
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
OP здесь... Мне удалось обойти эту странную проблему, но я не нашел правильного решения. Вот что я сделал.
-
Моя первая попытка после публикации вопроса была в этом (спойлер: он почти сработал):
Продолжайте удаление отдельно от импорта, но в той же процедуре. Это означает, что у меня было 3 цикла: один для хранения списка имен модулей (в виде простых строк), другой для удаления модулей, а другой для импорта модулей из файлов (на основе имен, которые были сохранены в вышеупомянутом списке),
Проблема: некоторые модули все еще были в проекте, когда цикл удаления закончился. Зачем? Я не могу объяснить. Я отмечу это как глупую проблему нет. 1. Затем я попытался разместить вызов Remove
для каждого модуля внутри цикла, который пытался удалить этот единственный модуль, пока он не смог найти его в проекте. Это застряло в бесконечном цикле для определенного модуля - я не могу сказать, что так особенного в этом конкретном.
В конце концов я понял, что модули были действительно удалены после того, как Excel находит некоторое время, чтобы очистить свои мысли. Это не с Application.Wait(). Текущий код VBA на самом деле нужен, чтобы это произошло. Weird.
-
Вторая попытка совместной работы (спойлер: опять же, она почти сработала):
Чтобы предоставить Excel необходимое время для дыхания после удаления, я поместил цикл удаления внутри обработчика нажатия кнопки (без цикла "Удалить до конца" ) и цикла импорта в обработчике кликов другой кнопки. Конечно, мне нужен список имен модулей, поэтому я сделал его глобальным массивом строк. Он был создан в обработчике кликов, перед циклом удаления, и он должен был получить доступ к циклу импорта. Должно было работать, правильно?
Проблема: вышеупомянутый строковый массив был пуст при запуске цикла импорта (внутри другого обработчика кликов). Это было точно, когда цикл удаления закончился - я напечатал его с помощью Debug.Print. Я предполагаю, что он был удален из-за удаления (??). Это была бы глупая проблема. 2. Без строкового массива, содержащего имена модулей, цикл импорта ничего не сделал, поэтому этот обход не удался.
-
Окончательный, функциональный обход. Это работает.
Я взял Work-around номер 2 и вместо хранения имен модулей в массиве строк я сохранил их в строке вспомогательного листа (я назвал этот лист "Devel" ).
Вот оно. Если кто-нибудь может объяснить глупую проблему нет. 1 и глупой проблемой нет. 2, прошу вас, сделайте это. Вероятно, они не такие глупые - я все еще начинаю с VBA, но у меня есть твердые знания программирования на других (нормальных и современных) языках.
Я мог бы добавить код для иллюстрации глупой проблемы нет. 2, но этот ответ уже длинный. Если то, что я сделал, было неясно, я поместил его здесь.
Ответ 3
Чтобы избежать дублирования при импорте, я изменил script на следующую стратегию:
- Переименовать существующий модуль
- Модуль импорта
- Удалить переименованный модуль
У меня больше нет дубликатов во время импорта.
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"
Ответ 4
Я борюсь с этой проблемой в течение нескольких дней. Я построил грубую систему управления версиями, подобную этой, хотя и не используя массивы. Модуль управления версиями импортируется в Workbook_Open, а затем запускается процедура запуска для импорта всех модулей, перечисленных в модуле управления версиями. Все отлично работает, за исключением того, что Excel начал создавать дублирующие модули управления версиями, поскольку он импортировал новый модуль до удаления существующего. Я работал над этим, добавив Delete в предыдущий модуль. Проблема тогда заключалась в том, что все еще были две процедуры с тем же именем. У Чипа Пирсона есть код для удаления программы программно, поэтому я удалил код запуска из более старого модуля управления версиями. Тем не менее, у меня возникла проблема, когда процедура не была удалена к моменту запуска процедуры запуска. Я, наконец, нашел решение в другом потоке, которое так просто, что заставляет меня хотеть просунуть голову через стену. Все, что мне нужно было сделать, это изменить способ, которым я вызываю свою процедуру запуска, используя
Application.OnTime Now + TimeValue("00:00:01"), "StartUp"
Все работает отлично. Хотя, я, вероятно, вернусь и удалю теперь избыточное переименование модуля и удалю вторую процедуру и посмотрю, решит ли это мою первоначальную проблему. Вот еще один поток с решением...
Управление версиями модулей кода Excel VBA
Ответ 5
Переименование, импорт и удаление обходного пути не было выполнено в моем случае. Кажется (но это чисто предположение), что Excel может сохранить скомпилированные объекты в своем файле .XLMS, и когда этот файл будет снова открыт, эти объекты будут перезагружены в памяти до того, как произойдет функция ThisWorkbook_open. И это приводит к переименованию (или удалению) определенных модулей с ошибкой или задержке (даже при попытке принудительно вызвать вызов DoEvents). Единственным обходным решением, которое я нашел, является использование бинарного формата .XLS. По какой-то неясной причине (я подозреваю, что скомпилированные объекты не связаны в файле), это работает для меня.
Вы должны знать, что вы не сможете повторно импортировать какой-либо модуль, который был использован или был указан в момент запуска вашего кода импорта (переименование не будет выполнено с ошибкой 32813/удаление модуля будет отложено до тех пор, пока после того, как вы попытаетесь импортировать, добавив раздражающий "1 в конце ваших имен модулей" ). Но для любого другого модуля он должен работать.
Если вам нужно управлять всем исходным кодом, лучшим решением будет "построить" вашу книгу с нуля с помощью некоторого script или инструмента или переключиться на более подходящий язык программирования (т.е. тот, который не жить внутри программного пакета Office;) Я не пробовал, но вы могли бы посмотреть здесь: Управление версиями модулей кода Excel VBA.