2009-04-01 42 views
19

Me gustaría poder controlar los módulos VBA de mi hoja de cálculo Excel (actualmente usando Excel 2003 SP3) para poder compartir y administrar el código utilizado por varias hojas de cálculo diferentes - y por lo tanto, me gustaría volver a cargarlos desde los archivos cuando se abre la hoja de cálculo.Control de fuente de módulos de código Excel VBA

Tengo un módulo llamado Loader.bas, que utilizo para hacer la mayoría del trabajo de burro (cargando y descargando cualquier otro módulo que se requiera) - y me gustaría poder cargarlo desde un archivo tan pronto como se abre la hoja de cálculo.

He adjuntado el siguiente código al evento Workbook_Open (en la clase ThisWorkbook).

Private Sub Workbook_Open() 
    Call RemoveLoader 
    Call LoadLoader 
End Sub 

Dónde RemoveLoader (también dentro de la clase ThisWorkbook) contiene el siguiente código:

Private Sub RemoveLoader() 
    Dim y As Integer 
    Dim OldModules, NumModules As Integer 
    Dim CompName As String 

    With ThisWorkbook.VBProject 
     NumModules = ThisWorkbook.VBProject.VBComponents.Count 
     y = 1 
     While y <= NumModules 
      If .VBComponents.Item(y).Type = 1 Then 
       CompName = .VBComponents.Item(y).Name 
       If VBA.Strings.InStr(CompName, "Loader") > 0 Then 
        OldModules = ThisWorkbook.VBProject.VBComponents.Count 
        .VBComponents.Remove .VBComponents(CompName) 
        NumModules = ThisWorkbook.VBProject.VBComponents.Count 
        If OldModules - NumModules = 1 Then 
         y = 1 
        Else 
         MsgBox ("Failed to remove " & CompName & " module from VBA project") 
        End If 
       End If 
      End If 
      y = y + 1 
     Wend 
    End With 
End Sub 

que es probablemente un poco demasiado complicado y un poco crudo - pero estoy tratando de encontrar todo lo que puede conseguirlo para cargar el módulo externo!

A menudo, cuando abro la hoja de cálculo, la función RemoveLoader encuentra que hay un módulo "Loader1" ya incluido en el proyecto VBA que no puede eliminar, y tampoco carga el nuevo módulo Loader del archivo.

Alguna idea si lo que intento hacer es posible? Excel parece muy aficionado a agregar un 1 a estos nombres de módulo, ya sea al cargar o eliminar (no estoy seguro de cuál).

+0

Olvidé agregar que pensé un poco y se me ocurrió esto: http://grumpyop.wordpress.com/2009/04/20/version-control-for-excel-workbooks-part-2/ Nota que también hay algo llamado VBAMaven mencionado en los comentarios que se parece a algún servicio externo que podría ayudar. –

Respuesta

4

Mire la página de VBAMaven. Tengo una solución local que usa los mismos conceptos. Tengo una biblioteca común con un montón de código fuente, una compilación ant y una secuencia de comandos 'import' VB. Ant controla la compilación, que toma un archivo de Excel en blanco e inserta el código necesario en él. @Mike es absolutamente correcto: cualquier definición de módulo duplicado tendrá automáticamente un número adjunto al nombre del módulo. Además, las clases de los módulos de clase (como en Sheet y ThisWorkbook) requieren un tratamiento especial. No puede crear esos módulos, debe leer el archivo de entrada y escribir el búfer en el módulo apropiado. Este es el script de VB que uso actualmente para hacer esto. La sección que contiene @ texto delimitado (es decir, @build file @) son marcadores de posición: la compilación ant reemplaza estas etiquetas con contenido significativo. No es perfecto, pero funciona para mí.

'' 
' Imports VB Basic module and class files from the src folder 
' into the excel file stored in the bin folder. 
' 

Option Explicit 

Dim pFileSystem, pFolder, pPath 
Dim pShell 
Dim pApp, book 

Dim pFileName 

pFileName = "@build [email protected]" 

Set pFileSystem = CreateObject("Scripting.FileSystemObject") 

Set pShell = CreateObject("WScript.Shell") 
pPath = pShell.CurrentDirectory 

If IsExcelFile (pFileName) Then 
    Set pApp = WScript.CreateObject ("Excel.Application") 
    pApp.Visible = False 
    Set book = pApp.Workbooks.Open(pPath & "\build\" & pFileName) 
Else 
    Set pApp = WScript.CreateObject ("Word.Application") 
    pApp.Visible = False 
    Set book = pApp.Documents.Open(pPath & "\build\" & pFileName) 
End If 


'Include root source folder code if no args set 
If Wscript.Arguments.Count = 0 Then 
    Set pFolder = pFileSystem.GetFolder(pPath & "\src") 
    ImportFiles pFolder, book 
    ' 
    ' Get selected modules from the Common Library, if any 
    @common [email protected]@common [email protected] 
Else 
    'Add code from subdirectories of src . . . 
    If Wscript.Arguments(0) <> "" Then 
     Set pFolder = pFileSystem.GetFolder(pPath & "\src\" & Wscript.Arguments(0)) 
     ImportFiles pFolder, book 
    End If 
End If 





Set pFolder = Nothing 
Set pFileSystem = Nothing 
Set pShell = Nothing 


If IsExcelFile (pFileName) Then 
    pApp.ActiveWorkbook.Save 
Else 
    pApp.ActiveDocument.Save 
End If 

pApp.Quit 
Set book = Nothing 
Set pApp = Nothing 


'' Loops through all the .bas or .cls files in srcFolder 
' and calls InsertVBComponent to insert it into the workbook wb. 
' 
Sub ImportFiles(ByVal srcFolder, ByVal obj) 
    Dim fileCollection, pFile 
    Set fileCollection = srcFolder.Files 
    For Each pFile in fileCollection 
     If Right(pFile, 3) = "bas _ 
      Or Right(pFile, 3) = "cls _ 
      Or Right(pFile, 3) = "frm Then 
      InsertVBComponent obj, pFile 
     End If 
    Next 
    Set fileCollection = Nothing 
End Sub 


'' Inserts the contents of CompFileName as a new component in 
' a Workbook or Document object. 
' 
' If a class file begins with "Sheet", then the code is 
' copied into the appropriate code module 1 painful line at a time. 
' 
' CompFileName must be a valid VBA component (class or module) 
Sub InsertVBComponent(ByVal obj, ByVal CompFileName) 
    Dim t, mName 
    t = Split(CompFileName, "\") 
    mName = Split(t(UBound(t)), ".") 
    If IsSheetCodeModule(mName(0), CompFileName) = True Then 
     ImportCodeModule obj.VBProject.VBComponents(mName(0)).CodeModule, _ 
         CompFileName 
    Else 
     If Not obj Is Nothing Then 
      obj.VBProject.VBComponents.Import CompFileName 
     Else 
      WScript.Echo "Failed to import " & CompFileName 
     End If 
    End If 
End Sub 

'' 
' Imports the code in the file fName into the workbook object 
' referenced by mName. 
' @param target destination CodeModule object in the excel file 
' @param fName file system file containing code to be imported 
Sub ImportCodeModule (ByVal target, ByVal fName) 
    Dim shtModule, code, buf  
    Dim fso 
    Set fso = CreateObject("Scripting.FileSystemObject") 
    Const ForReading = 1, ForWriting = 2, ForAppending = 3 
    Const TristateUseDefault = -2, TristateTrue = -1, TristateFalse = 0 

    Set buf = fso.OpenTextFile(fName, ForReading, False, TristateUseDefault) 
    buf.SkipLine 
    code = buf.ReadAll 

    target.InsertLines 1, code 
    Set fso = Nothing 
End Sub 


'' 
' Returns true if the code module in the file fName 
' appears to be a code module for a worksheet. 
Function IsSheetCodeModule (ByVal mName, ByVal fName) 
    IsSheetCodeModule = False 
    If mName = "ThisWorkbook" Then 
     IsSheetCodeModule = False 
    ElseIf Left(mName, 5) = "Sheet" And _ 
     IsNumeric(Mid (mName, 6, 1)) And _ 
     Right(fName, 3) = "cls Then 
     IsSheetCodeModule = True 
    End If 
End Function 

'' 
' Returns true if fName has a xls file extension 
Function IsExcelFile (ByVal fName) 
    If Right(fName, 3) = "xls" Then 
     IsExcelFile = True 
    Else 
     IsExcelFile = False 
    End If 
End Function 
+0

Gracias por esto. He revisado su script y vbaMaven y parece que ambos métodos crean una nueva hoja de cálculo y copian las macros en ellos. ¿Es eso correcto? Tenía la esperanza de encontrar un método para cargar las macros en la hoja de cálculo cuando se abre la hoja de cálculo, pero no pude lograr que funcione. Tal vez estoy tratando de hacer algo que no se puede hacer con Excel. Ahora estoy empezando a considerar la sustitución de mis macros con un script externo que acceda a Excel a través de COM, por lo que al menos puedo mantener el script bajo control de versiones. –

+0

Sí, la idea básica es copiar una hoja en blanco y el código se copia en ella. Si hubiera sabido acerca de vbaMaven antes de comenzar, podría haber ido con él ... La razón por la que elegí tomar esta ruta es para que cada línea de código pueda estar bajo el control de la fuente. Si tiene un código en un libro de trabajo que carga otro código, entonces su módulo de carga de código no está bajo el mismo nivel de control que todo lo demás: está escrito dentro de un archivo de Excel. – DaveParillo

+0

Además, debe tener mucho cuidado al modificar el código de evento en los eventos Auto_Open o Workbook_Open. Intentar editar el objeto ThisWorkbook desde la función Auto_Open es una ruta rápida hacia la falla de Excel. – DaveParillo

2

Normalmente, el asunto "Loader1" ocurre cuando se le pide a Excel que importe un módulo y ya existe un módulo con el mismo nombre. Entonces, si importa "Loader", vuelva a cargarlo y obtendrá "Loader1". Esto se debe a que Excel no sabe (o tal vez simplemente no le importa) si es realmente lo mismo o si un nuevo pedazo de funcionalidad que acaba de suceder tiene el mismo nombre de módulo, por lo que lo importa de todos modos.

No puedo pensar en una solución perfecta, pero creo que me inclinaría a tratar de poner la lógica de carga/descarga en un complemento - esa cosa Workbook_Open parece un poco vulnerable y tenerlo en todos los libros es va a ser un gran dolor si el código necesita alguna vez cambiar (nunca digas nunca). La lógica XLA podría ser más compleja (más complicado para atrapar los eventos necesarios, por un lado), pero al menos solo existirá en un solo lugar.

+0

Gracias por la respuesta Mike. Puede ser correcto, un AddIn puede ser la única forma de hacerlo. Estaba tratando de evitar la solución AddIn, porque me gustaría almacenar las macros en SubVersion, así que preferiría almacenarlas como archivos de texto para que pueda fusionar y diferir fácilmente, en lugar de un archivo XLA binario. –

+0

¿Tal vez use un pequeño complemento para cargar los módulos? –

+0

No estoy seguro de cómo usar un AddIn ayudará en esta situación. Para cargar módulos VBA no binarios en Excel, todavía tendré que ejecutar algún tipo de código cuando se abra el Libro de trabajo, y no creo que mantener el código Loader en un AddIn cambie la esencia del problema. . –

3

He estado trabajando en esto exactamente durante meses. Creo que lo descubrí.

Si el Proyecto VB está tratando de eliminar un módulo que contiene algo en la pila de llamadas, retrasa la eliminación hasta que la pila de llamadas aparece el módulo que se reemplaza.

Para evitar que un módulo esté en la pila de llamadas, inicie su código con la aplicación.OnTime

Private Sub Workbook_Open() 

    'WAS: module_library (1) 

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

End Sub 

Si está auto-curación de su código como soy, usted también tendrá que poner en marcha su código que sobrescribe el código de 'llamar' con la misma estrategia.

yo no realizo extensas pruebas, sin embargo, yo estoy en el modo de celebración total, pero esto me pone muy cerca de código de auto-sanación directa del 99,9% dentro de un archivo .xls autónomo sin otros trucos

+1

GRACIAS. Este fue mi problema exacto casi 4 años después. – enderland

12

hay una excelente solución para el problema de control de versiones de VBA aquí: https://github.com/hilkoc/vbaDeveloper

la parte buena de esto es que lo que exporta el código automáticamente, tan pronto como se guarda el libro. Además, cuando abre un libro de trabajo, importa el código.

No necesita ejecutar ninguna secuencia de comandos de compilación o comandos maven y no necesita realizar ningún cambio en sus libros de trabajo. Funciona para todos.

También resolvió el problema de importación donde módulos como ModName se importan como ModName1 en un módulo duplicado. La importación funciona como debería, incluso cuando se lo hace varias veces.

Como una ventaja, viene con un formateador de código simple, que le permite formatear su código vba mientras lo escribe dentro del Editor de VBA.

+1

Acabo de pasar por todo el proceso de compilación, y hay dos cuestiones, me gustaría preguntar si se ha dado cuenta: 1. La cinta de complementos no muestra el menú vbaDeveloper, a menos que ejecute el menú de actualización () método proporcionado en el módulo Menú 2. La exportación del código no ocurre automáticamente, en su lugar tengo que activar la acción manualmente –

+3

1 El menú se creará mediante el evento workbook_open. Para que eso funcione, crea y cierra el complemento. Luego, después de abrirlo de nuevo, se activará el evento workbook_open, a menos que lo haya deshabilitado manualmente con: Application.EnableEvents = False 2 La importación/exportación automática está deshabilitada de manera predeterminada: https://github.com/hilkoc/vbaDeveloper/issues/8 – CodeKid

Cuestiones relacionadas