Flushing zmian wprowadzonych do VBProject.Komponenty VB w Excelu za pomocą VBA

Doświadczałem dziwnych dziwactw w Excelu podczas programowego usuwania modułów, a następnie ponownego importowania ich z plików. Zasadniczo mam moduł o nazwie VersionControl, który ma wyeksportować moje pliki do predefiniowanego folderu i ponownie zaimportować je na żądanie. Jest to kod do reimportingu (problem z nim jest opisany poniżej):

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

Po uruchomieniu tego zauważyłem, że niektóre moduły już się nie pojawiają, podczas gdy niektóre mają duplikaty(np. mymodule i mymodule1). Przechodząc przez kod, stało się oczywiste, że niektóre moduły nadal pozostają po wywołaniu Remove i mogą zostać ponownie zaimportowane, gdy są jeszcze w projekcie. Czasami wynikało to tylko z tego, że moduł był zakończony 1, ale czasami miałem zarówno oryginał, jak i kopię.

Czy jest sposób, aby spłukać połączenia do Remove i Import, aby się zastosowały? Myślę o wywołaniu po każdej funkcji Save, jeśli jest ona w obiekcie aplikacji, chociaż może to spowodować straty jeśli coś pójdzie nie tak podczas importu.

Pomysły?

Edit: zmieniono tag synchronization na version-control.

Author: CamilB, 2012-01-13

5 answers

Jest to tablica na żywo, dodajesz i usuwasz elementy podczas iteracji, zmieniając tym samym numery indeksów. Spróbuj przetworzyć tablicę wstecz. Oto moje rozwiązanie bez obsługi błędów:

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
Author: Karl Greenhalgh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-13 22:20:27

Tutaj... Udało mi się obejść ten dziwny problem, ale nie znalazłem prawdziwego rozwiązania. Oto co zrobiłem.

  1. Moja pierwsza próba po wrzuceniu pytania była taka (spoiler: to prawie zadziałało):

    Usuwaj oddzielnie od importu, ale w tej samej procedurze. Oznacza to, że miałem 3 pętle-jedną do przechowywania listy nazw modułów( jako zwykłe ciągi), drugą do usuwania modułów, a drugą do importowania modułów z plików (na podstawie nazw które zostały zapisane na powyższej liście).

    Problem: niektóre moduły były nadal w projekcie, gdy pętla usuwania się skończyła. Dlaczego? Nie potrafię tego wyjaśnić. Zaznaczę to jako głupi problem nr 1 . Następnie próbowałem umieścić Remove Wywołanie dla każdego modułu wewnątrz pętli, która próbowała usunąć ten pojedynczy moduł, dopóki nie mógł go znaleźć w projekcie. To utknęło w nieskończonej pętli dla pewnego modułu - nie mogę powiedzieć, co jest takiego specjalnego w tym konkretnym module.

    W końcu zorientowałem się, że moduły zostały naprawdę usunięte dopiero po tym, jak Excel znajdzie trochę czasu na oczyszczenie swoich myśli. To nie zadziałało z aplikacją.Czekaj (). Aktualnie uruchomiony kod VBA faktycznie musiał się skończyć, aby to się stało. Dziwne.

  2. Druga próba obejścia (spojler: znowu, to prawie zadziałało):

    Aby Dać Excelowi wymagany czas na oddychanie po usunięciu, umieściłem pętlę usuwania wewnątrz przycisku Click handler (bez " wywołania Remove until it 's gone" loop), a importowanie pętli w obsłudze kliknięcia innego przycisku. Oczywiście potrzebowałem listy nazw modułów, więc zrobiłem z niej globalną tablicę ciągów. Został utworzony w programie obsługi kliknięć, przed pętlą usuwania i miał być dostępny przez pętlę importowania. Powinno zadziałać, prawda?

    Problem: wspomniana tablica łańcuchów znaków była pusta podczas uruchamiania pętli importowania (wewnątrz innego programu obsługi kliknięć). To było na pewno tam, kiedy pętla usuwania zakończona-wydrukowałem ją z Debug.Drukuj. Myślę, że został zdekonspirowany przez przeprowadzki (??). To byłby głupi problem nr 2 . Bez tablicy łańcuchowej zawierającej nazwy modułów, pętla importująca nic nie zrobiła, więc to obejście nie powiodło się.

  3. Ostateczne, funkcjonalne obejście. Ten działa.

    Wziąłem pracę wokół numeru 2 i zamiast przechowywania nazw modułów w tablicy łańcuchowej, zapisałem je w wierszu arkusza pomocniczego (nazwałem ten arkusz "Devel").

To było to. Jeśli ktoś potrafi wyjaśnić głupi problem nr 1 i głupi problem nr 2 , błagam, zrób to. Pewnie nie są aż tak głupi - wciąż jestem na początku z VBA, ale mam solidną znajomość programowania w innych (zdrowych i nowoczesnych) językach.

Mógłbym dodać kod, aby zilustrować głupi problem nr 2 , ale ta odpowiedź jest już długa. Jeśli to, co zrobiłem, nie było jasne, umieszczę to tutaj.

 1
Author: CamilB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-14 10:39:53

Aby uniknąć duplikatów podczas importowania, zmodyfikowałem skrypt następującą strategią:

  • Zmień nazwę istniejącego modułu
  • moduł importu
  • Usuń przemianowany moduł

Nie mam już duplikatu podczas importu.


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

Kod do wklejenia w nowym module "VersionControl"

 1
Author: David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-11 13:39:34

Zmagam się z tym problemem od kilku dni. Zbudowałem prymitywny system kontroli wersji podobny do tego, choć nie używając tablic. Moduł kontroli wersji jest importowany w Workbook_Open, a następnie wywoływana jest procedura uruchamiania, aby zaimportować wszystkie moduły wymienione w module kontroli wersji. Wszystko działa świetnie, z wyjątkiem programu Excel zaczął tworzyć zduplikowane moduły kontroli wersji, ponieważ importowałby nowy moduł przed zakończeniem usuwania istniejącego. I work around to poprzez dodanie Delete do poprzedniego modułu. Problem w tym, że były jeszcze dwie procedury o tej samej nazwie. Chip Pearson ma jakiś kod do programowego usuwania procedury, więc usunąłem kod startowy ze starszego modułu kontroli wersji. Mimo to napotkałem problem, w którym procedura nie została usunięta do czasu wywołania procedury uruchamiania. W końcu znalazłem rozwiązanie na innym wątku przepełnienia stosu, które jest tak proste, że sprawia, że chcę położyć głowę przez ścianę. Wszystko, co musiałem zrobić, to zmienić sposób, w jaki nazywam moją procedurę uruchamiania za pomocą

Application.OnTime Now + TimeValue("00:00:01"), "StartUp"    
Teraz wszystko działa idealnie. Chociaż prawdopodobnie wrócę i usunę teraz zbędną zmianę nazwy modułu i usunięcie drugiej procedury i zobaczę, czy to samo rozwiąże mój pierwotny problem. Oto drugi wątek z rozwiązaniem...

Kontrola źródłowa modułów kodu Excel VBA

 1
Author: Logan Smyth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:46

Obejście zmiany nazwy, importu i usunięcia nie zadziałało w moim przypadku. Wydaje się (ale to czysta spekulacja), że Excel może zapisać skompilowane obiekty w swoim .Plik XLMS, a po ponownym otwarciu tego pliku obiekty te są przeładowywane w pamięci przed wystąpieniem funkcji ThisWorkbook_open. Powoduje to, że zmiana nazwy (lub usunięcie) niektórych modułów nie powiedzie się lub zostanie opóźniona (nawet gdy próbuje się wymusić to wywołaniem DoEvents). Jedyne obejście, jakie znalazłem, to użycie .Format binarny XLS. Dla niektórych niejasny powód (podejrzewam, że skompilowane obiekty nie są wiązane w pliku), działa dla mnie.

Musisz wiedzieć, że nie będziesz w stanie ponownie zaimportować żadnego modułu, który był/był używany lub odwoływany w momencie uruchomienia kodu importowego (zmiana nazwy nie powiedzie się z błędem 32813/usunięcie modułu będzie opóźnione do momentu, gdy spróbujesz zaimportować, dodając irytujące '1' na końcu nazw modułów). Ale dla każdego innego modułu powinien działać.

Jeśli cały kod źródłowy musi być zarządzane, lepszym rozwiązaniem byłoby "zbudowanie" skoroszytu od podstaw za pomocą jakiegoś skryptu lub narzędzia, lub przejście na lepiej dopasowany język programowania (tj. taki, który nie mieszka w oprogramowaniu pakietu biurowego;) nie próbowałem tego, ale można zajrzeć tutaj: Source control of Excel VBA code modules .

 0
Author: Apteryx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:46