递归地将MS Access表单和类/模块导出为文本文件?

24

我在一个古老的留言板上找到了一些代码,可以很好地导出所有类、模块和表单中的VBA代码(见下文):

Option Explicit
Option Compare Database
Function SaveToFile()                  'Save the code for all modules to files in currentDatabaseDir\Code

Dim Name As String
Dim WasOpen As Boolean
Dim Last As Integer
Dim I As Integer
Dim TopDir As String, Path As String, FileName As String
Dim F As Long                          'File for saving code
Dim LineCount As Long                  'Line count of current module

I = InStrRev(CurrentDb.Name, "\")
TopDir = VBA.Left(CurrentDb.Name, I - 1)
Path = TopDir & "\" & "Code"           'Path where the files will be written

If (Dir(Path, vbDirectory) = "") Then
  MkDir Path                           'Ensure this exists
End If

'--- SAVE THE STANDARD MODULES CODE ---

Last = Application.CurrentProject.AllModules.Count - 1

For I = 0 To Last
  Name = CurrentProject.AllModules(I).Name
  WasOpen = True                       'Assume already open

  If Not CurrentProject.AllModules(I).IsLoaded Then
    WasOpen = False                    'Not currently open
    DoCmd.OpenModule Name              'So open it
  End If

  LineCount = Access.Modules(Name).CountOfLines
  FileName = Path & "\" & Name & ".vba"

  If (Dir(FileName) <> "") Then
    Kill FileName                      'Delete previous version
  End If

  'Save current version
  F = FreeFile
  Open FileName For Output Access Write As #F
  Print #F, Access.Modules(Name).Lines(1, LineCount)
  Close #F

  If Not WasOpen Then
    DoCmd.Close acModule, Name         'It wasn't open, so close it again
  End If
Next

'--- SAVE FORMS MODULES CODE ---

Last = Application.CurrentProject.AllForms.Count - 1

For I = 0 To Last
  Name = CurrentProject.AllForms(I).Name
  WasOpen = True

  If Not CurrentProject.AllForms(I).IsLoaded Then
    WasOpen = False
    DoCmd.OpenForm Name, acDesign
  End If

  LineCount = Access.Forms(Name).Module.CountOfLines
  FileName = Path & "\" & Name & ".vba"

  If (Dir(FileName) <> "") Then
    Kill FileName
  End If

  F = FreeFile
  Open FileName For Output Access Write As #F
  Print #F, Access.Forms(Name).Module.Lines(1, LineCount)
  Close #F

  If Not WasOpen Then
    DoCmd.Close acForm, Name
  End If
Next
MsgBox "Created source files in " & Path
End Function

然而,这段代码并不能解决我的问题,因为我有110个需要从中导出vba的ms-access *.mdb文件,以便于生成适合grep的文本文件。我感兴趣的110个文件的路径已经存储在一个表中,我的代码已经递归地获取了这些信息(以及一些其他过滤条件)...所以递归部分已经完成。这些文件中的大多数都由单个访问用户安全文件(.mdw)打开,我已经尝试了几种打开它们的方法。当我在这些目录中搜索链接表时,ADO和ADOX效果很好...但是上面的代码涉及到在导出数据的数据库内部,我想能够从另一个打开所有的mdb并对每个进行导出的数据库中执行此操作。我尝试过使用PrivDBEngine类来外部连接到数据库,但它不允许我访问Application对象,而这正是上面的导出代码所需的。
Private Sub exportToFile(db_path As String, db_id As String, loginInfo As AuthInfoz, errFile As Variant)

    Dim pdbeNew As PrivDBEngine
    Dim db As DAO.Database
    Dim ws As DAO.Workspace
    Dim rst As DAO.Recordset

    Dim cn As ADODB.Connection ' ADODB.Connection
    Dim rs As ADODB.Recordset ' ADODB.Recordset
    Dim strConnect As String
    Dim blnReturn As Boolean

    Dim Doc              As Document
    Dim mdl              As Module
    Dim lngCount         As Long
    Dim strForm          As String
    Dim strOneLine       As String
    Dim sPtr             As Integer

    Set fso = CreateObject("Scripting.FileSystemObject")
    Set exportFile = fso.CreateTextFile("E:\Tickets\CSN1006218\vbacode\" & db_id & ".txt", ForAppending)

    ' Export stuff...

    On Error GoTo errorOut

    Set pdbeNew = New PrivDBEngine
    With pdbeNew
        .SystemDB = loginInfo.workgroup
        .DefaultUser = loginInfo.username
        .DefaultPassword = loginInfo.password
    End With


    Set ws = pdbeNew.Workspaces(0)


    Set db = ws.OpenDatabase(db_path)

    For Each Doc In db.Containers("Modules").Documents
        DoCmd.OpenModule Doc.Name
        Set mdl = Modules(Doc.Name)

        exportFile.WriteLine ("---------------------")
        exportFile.WriteLine ("Module Name: " & Doc.Name)
        exportFile.WriteLine ("Module Type: " & mdl.Type)
        exportFile.WriteLine ("---------------------")

        lngCount = lngCount + mdl.CountOfLines

        'For i = 1 To lngCount
        '    strOneLine = mdl.Lines(i, 1)
        '    exportFile.WriteLine (strOneLine)
        'Next i

        Set mdl = Nothing
        DoCmd.Close acModule, Doc.Name
    Next Doc

Close_n_exit:

    If Not (db Is Nothing) Then
        Call wk.Close
        Set wk = Nothing
        Call db.Close
    End If



    Call exportFile.Close
    Set exportFile = Nothing
    Set fso = Nothing

    Exit Sub

errorOut:
    Debug.Print "----------------"
    Debug.Print "BEGIN: Err"
    If err.Number <> 0 Then
        Msg = "Error # " & Str(err.Number) & " was generated by " _
         & err.Source & Chr(13) & "Error Line: " & Erl & Chr(13) & err.Description
        'MsgBox Msg, , "Error", err.HelpFile, err.HelpContext
        Debug.Print Msg
    End If
    Resume Close_n_exit

End Sub

有没有办法从PrivDBEngine访问application对象?我有很多需要搜索的模块。

6个回答

35
您也可以尝试这段代码。它将保留项目的文件类型(.bas、.cls、.frm)。记得在VBE > 工具 > 引用中参考/检查Microsoft Visual Basic For Applications Extensibility Library
Public Sub ExportAllCode()

    Dim c As VBComponent
    Dim Sfx As String

    For Each c In Application.VBE.VBProjects(1).VBComponents
        Select Case c.Type
            Case vbext_ct_ClassModule, vbext_ct_Document
                Sfx = ".cls"
            Case vbext_ct_MSForm
                Sfx = ".frm"
            Case vbext_ct_StdModule
                Sfx = ".bas"
            Case Else
                Sfx = ""
        End Select

        If Sfx <> "" Then
            c.Export _
                Filename:=CurrentProject.Path & "\" & _
                c.Name & Sfx
        End If
    Next c

End Sub

1
嗨,你的代码真的很好用(甚至在2019年),但我想知道是否有办法也导出我们访问文件中表格的结构? - RegularNormalDayGuy

15
你可以使用 Access.Application 对象。
此外,为了避免在打开数据库时出现多个确认对话框,只需在“工具/宏/安全性”中更改安全级别即可。
如果要使用用户/密码打开多个数据库,可以加入工作组(工具/安全/工作组管理员),并使用所需的用户/密码登录(从 SaveToFile 函数的数据库中),然后运行代码。记得以后加入默认工作组(你可以尝试加入不存在的工作组,Access 将恢复到默认设置)。
Option Explicit
Option Compare Database


'Save the code for all modules to files in currentDatabaseDir\Code
Public Function SaveToFile()

   On Error GoTo SaveToFile_Err
    
   Dim Name As String
   Dim WasOpen As Boolean
   Dim Last As Integer
   Dim i As Integer
   Dim TopDir As String, Path As String, FileName As String
   Dim F As Long                          'File for saving code
   Dim LineCount As Long                  'Line count of current module
    
   Dim oApp As New Access.Application
    
   ' Open remote database
   oApp.OpenCurrentDatabase ("D:\Access\myDatabase.mdb"), False

    
   i = InStrRev(oApp.CurrentDb.Name, "\")
   TopDir = VBA.Left(oApp.CurrentDb.Name, i - 1)
   Path = TopDir & "\" & "Code"           'Path where the files will be written
    
   If (Dir(Path, vbDirectory) = "") Then
      MkDir Path                           'Ensure this exists
   End If
    
   '--- SAVE THE STANDARD MODULES CODE ---
    
   Last = oApp.CurrentProject.AllModules.Count - 1
    
   For i = 0 To Last
      Name = oApp.CurrentProject.AllModules(i).Name
      WasOpen = True                       'Assume already open
    
         If Not oApp.CurrentProject.AllModules(i).IsLoaded Then
            WasOpen = False                    'Not currently open
            oApp.DoCmd.OpenModule Name              'So open it
         End If
    
      LineCount = oApp.Modules(Name).CountOfLines
      FileName = Path & "\" & Name & ".vba"
    
      If (Dir(FileName) <> "") Then
        Kill FileName                      'Delete previous version
      End If
    
      'Save current version
      F = FreeFile
      Open FileName For Output Access Write As #F
      Print #F, oApp.Modules(Name).Lines(1, LineCount)
      Close #F
    
      If Not WasOpen Then
         oApp.DoCmd.Close acModule, Name         'It wasn't open, so close it again
      End If
   Next
    
   '--- SAVE FORMS MODULES CODE ---
    
   Last = oApp.CurrentProject.AllForms.Count - 1
   
   For i = 0 To Last
      Name = oApp.CurrentProject.AllForms(i).Name
      WasOpen = True
    
      If Not oApp.CurrentProject.AllForms(i).IsLoaded Then
         WasOpen = False
         oApp.DoCmd.OpenForm Name, acDesign
      End If
    
      LineCount = oApp.Forms(Name).Module.CountOfLines
      FileName = Path & "\" & Name & ".vba"
    
      If (Dir(FileName) <> "") Then
         Kill FileName
      End If
    
      F = FreeFile
      Open FileName For Output Access Write As #F
      Print #F, oApp.Forms(Name).Module.Lines(1, LineCount)
      Close #F
    
      If Not WasOpen Then
         oApp.DoCmd.Close acForm, Name
      End If
   Next
   
   '--- SAVE REPORTS MODULES CODE ---
    
   Last = oApp.CurrentProject.AllReports.Count - 1
   
   For i = 0 To Last
      Name = oApp.CurrentProject.AllReports(i).Name
      WasOpen = True
    
      If Not oApp.CurrentProject.AllReports(i).IsLoaded Then
         WasOpen = False
         oApp.DoCmd.OpenReport Name, acDesign
      End If
    
      LineCount = oApp.Reports(Name).Module.CountOfLines
      FileName = Path & "\" & Name & ".vba"
    
      If (Dir(FileName) <> "") Then
         Kill FileName
      End If
    
      F = FreeFile
      Open FileName For Output Access Write As #F
      Print #F, oApp.Reports(Name).Module.Lines(1, LineCount)
      Close #F
    
      If Not WasOpen Then
         oApp.DoCmd.Close acReport, Name
      End If
   Next
   
   MsgBox "Created source files in " & Path
    
   ' Reset the security level
   Application.AutomationSecurity = msoAutomationSecurityByUI
   
SaveToFile_Exit:
   
   If Not oApp.CurrentDb Is Nothing Then oApp.CloseCurrentDatabase
   If Not oApp Is Nothing Then Set oApp = Nothing
   Exit function

SaveToFile_Err:

   MsgBox ("Error " & Err.Number & vbCrLf & Err.Description)
   Resume SaveToFile_Exit

End Function

我已添加了报表模块的代码。等我有时间时,我会尝试重构代码。
我认为这是一个很好的贡献。感谢你的分享。
敬礼!
================= 编辑 ==================
一段时间后,我找到了导出整个数据库(包括表和查询)并在Git中进行版本控制的方法。
当然,如果您有非常大的表格,则真正想要的是备份。这只用于其初始状态的表格,其中许多为空,仅用于开发目的。
         Option Compare Database
         Option Explicit

  Private Const VB_MODULE               As Integer = 1
  Private Const VB_CLASS                As Integer = 2
  Private Const VB_FORM                 As Integer = 100
  Private Const EXT_TABLE               As String = ".tbl"
  Private Const EXT_QUERY               As String = ".qry"
  Private Const EXT_MODULE              As String = ".bas"
  Private Const EXT_CLASS               As String = ".cls"
  Private Const EXT_FORM                As String = ".frm"
  Private Const CODE_FLD                As String = "code"

  Private Const mblnSave                As Boolean = True               ' False: just generate the script
'
'

Public Sub saveAllAsText()

            Dim oTable                  As TableDef
            Dim oQuery                  As QueryDef
            Dim oCont                   As Container
            Dim oForm                   As Document
            Dim oModule                 As Object
            Dim FSO                     As Object
        
            Dim strPath                 As String
            Dim strName                 As String
            Dim strFileName             As String
    
'**
    On Error GoTo errHandler
    
    strPath = CurrentProject.path
    Set FSO = CreateObject("Scripting.FileSystemObject")
    strPath = addFolder(FSO, strPath, Application.CurrentProject.name & "_" & CODE_FLD)
    strPath = addFolder(FSO, strPath, Format(Date, "yyyy.mm.dd"))

    
    For Each oTable In CurrentDb.TableDefs
        strName = oTable.name
        If left(strName, 4) <> "MSys" Then
            strFileName = strPath & "\" & strName & EXT_TABLE
            If mblnSave Then Application.ExportXML acExportTable, strName, strFileName, strFileName & ".XSD", strFileName & ".XSL", , acUTF8, acEmbedSchema + acExportAllTableAndFieldProperties
            Debug.Print "Application.ImportXML """ & strFileName & """, acStructureAndData"
        End If
    Next
    
    For Each oQuery In CurrentDb.QueryDefs
        strName = oQuery.name
        If left(strName, 1) <> "~" Then
            strFileName = strPath & "\" & strName & EXT_QUERY
            If mblnSave Then Application.SaveAsText acQuery, strName, strFileName
            Debug.Print "Application.LoadFromText acQuery, """ & strName & """, """ & strFileName & """"
        End If
    Next
    
    Set oCont = CurrentDb.Containers("Forms")
    For Each oForm In oCont.Documents
        strName = oForm.name
        strFileName = strPath & "\" & strName & EXT_FORM
        If mblnSave Then Application.SaveAsText acForm, strName, strFileName
        Debug.Print "Application.LoadFromText acForm, """ & strName & """, """ & strFileName & """"
    Next
    
    strPath = addFolder(FSO, strPath, "modules")
    For Each oModule In Application.VBE.ActiveVBProject.VBComponents
        strName = oModule.name
        strFileName = strPath & "\" & strName
        Select Case oModule.Type
            Case VB_MODULE
                If mblnSave Then oModule.Export strFileName & EXT_MODULE
                Debug.Print "Application.VBE.ActiveVBProject.VBComponents.Import """ & strFileName & EXT_MODULE; """"
            Case VB_CLASS
                If mblnSave Then oModule.Export strFileName & EXT_CLASS
                Debug.Print "Application.VBE.ActiveVBProject.VBComponents.Import """ & strFileName & EXT_CLASS; """"
            Case VB_FORM
                ' Do not export form modules (already exported the complete forms)
            Case Else
                Debug.Print "Unknown module type: " & oModule.Type, oModule.name
        End Select
    Next
    
    If mblnSave Then MsgBox "Files saved in  " & strPath, vbOKOnly, "Export Complete"

Exit Sub

errHandler:
    MsgBox "Error " & Err.Number & ": " & Err.Description & vbCrLf
    Stop: Resume

End Sub
'

'
' Create a folder when necessary. Append the folder name to the given path.
'
Private Function addFolder(ByRef FSO As Object, ByVal strPath As String, ByVal strAdd As String) As String
    addFolder = strPath & "\" & strAdd
    If Not FSO.FolderExists(addFolder) Then MkDir addFolder
End Function
'

编辑2


在保存查询时,它们经常会因为微小的细节而发生变化,而我不希望这些变化被提交到git仓库。我修改了代码,所以它只导出查询中的SQL代码。

For Each oQuery In CurrentDb.QueryDefs
    strName = oQuery.Name
    If Left(strName, 1) <> "~" Then
        strFileName = strPath & "\" & strName & EXT_QUERY
        saveQueryAsText oQuery, strFileName
    End If
Next

'
' Save just the SQL code in the query
'
Private Sub saveQueryAsText(ByVal oQuery As QueryDef, ByVal strFileName As String)
        
   Dim intFile As Integer

   intFile = FreeFile
   Open strFileName For Output As intFile
   Print #intFile, oQuery.sql
   Close intFile

End Sub

我使用另一个模块mDBImport来导入和重新创建数据库。在代码仓库中,这些模块都包含在“modules”子文件夹中:

Private Const repoPath As String = "C:\your\repository\path\here"

Public Sub loadFromText(Optional ByVal strPath As String = REPOPATH)

   dim FSO as Object

   Set oFolder = FSO.GetFolder(strPath)
   Set FSO = CreateObject("Scripting.FileSystemObject")

   For Each oFile In oFolder.files
      Select Case FSO.GetExtensionName(oFile.Path)
      Case "tbl"
         Application.ImportXML oFile.Path, acStructureAndData
      Case "qry"
         intFile = FreeFile
         Open oFile.Path For Input As #intFile
         strSQL = Input$(LOF(intFile), intFile)
         Close intFile
         CurrentDb.CreateQueryDef Replace(oFile.Name, ".qry", ""), strSQL
        
      Case "frm"
         Application.loadFromText acForm, Replace(oFile.Name, ".frm", ""), oFile.Path
      End Select
   Next oFile

   ' load modules and class modules
   strPath = FSO.BuildPath(strPath, "modules")
   If Not FSO.FolderExists(strPath) Then Err.Raise vbObjectError + 4, , "Modules folder doesn't exist!"
   Set oFolder = FSO.GetFolder(strPath)
   
   With Application.VBE.ActiveVBProject.VBComponents
      For Each oFile In oFolder.files
         Select Case FSO.GetExtensionName(oFile.Path)
         Case "cls", "bas"
            If oFile.Name <> "mDBImport.bas" Then .Import oFile.Path
         End Select
      Next oFile
   End With

   MsgBox "The database objects where correctly loaded.", vbOKOnly, "LoadFromText"

Exit Sub

errHandler:
   MsgBox Err.Description, vbCritical + vbOKOnly

End Sub

2
这是很棒的代码。你有将文件导入Access的代码吗? - Séb Cô
2
我在这里找到了答案:https://dev59.com/CI3da4cB1Zd3GeqPyVfz,现在它运行得很好。 - Séb Cô
3
这个项目是一个插件,可以导出和导入所有Access对象:https://github.com/joyfullservice/msaccess-vcs-integration - HackSlash

5

与MS Excel类似,您也可以使用循环遍历Application.VBE.VBProjects(1).VBComponents并使用Export方法导出您的模块/类/窗体:

Const VB_MODULE = 1
Const VB_CLASS = 2
Const VB_FORM = 100
Const EXT_MODULE = ".bas"
Const EXT_CLASS = ".cls"
Const EXT_FORM = ".frm"
Const CODE_FLD = "Code"

Sub ExportAllCode()

Dim fileName As String
Dim exportPath As String
Dim ext As String
Dim FSO As Object

Set FSO = CreateObject("Scripting.FileSystemObject")
' Set export path and ensure its existence
exportPath = CurrentProject.path & "\" & CODE_FLD
If Not FSO.FolderExists(exportPath) Then
    MkDir exportPath
End If

' The loop over all modules/classes/forms
For Each c In Application.VBE.VBProjects(1).VBComponents
    ' Get the filename extension from type
    ext = vbExtFromType(c.Type)
    If ext <> "" Then
        fileName = c.name & ext
        debugPrint "Exporting " & c.name & " to file " & fileName
        ' THE export
        c.Export exportPath & "\" & fileName
    Else
        debugPrint "Unknown VBComponent type: " & c.Type
    End If
Next c

End Sub

' Helper function that translates VBComponent types into file extensions
' Returns an empty string for unknown types
Function vbExtFromType(ByVal ctype As Integer) As String
    Select Case ctype
        Case VB_MODULE
            vbExtFromType = EXT_MODULE
        Case VB_CLASS
            vbExtFromType = EXT_CLASS
        Case VB_FORM
            vbExtFromType = EXT_FORM
    End Select
End Function

只需要不到一秒钟就能执行。

干杯!


我最终遇到了编译错误,指出它找不到c。 - Daniel L. VanDenBosch

3
可爱的回答Clon。
如果您要打开具有启动表单和/或AutoExec宏的MDB,那么上面的方法并不总是可靠。查看另一个网站上的答案:越过启动表单/宏,并且在讨论的最后几行中有些代码,可以暂时消除启动表单设置,并将AutoExec宏提取到您的数据库中,然后用TempAutoExec宏(什么也不做)覆盖它,做一些工作(在“读取命令栏”和“app.CloseCurrentDatabase”之间),然后再将所有内容修复回来。

2

我不知道为什么之前没有人提出这个建议,但是下面是我用于此的一小段代码。非常简单明了。

Public Sub VBAExportModule()
    On Error GoTo Errg
    Dim rs As DAO.Recordset
    Set rs = CurrentDb.OpenRecordset("SELECT MSysObjects.Name FROM MSysObjects WHERE Type=-32761", dbOpenDynaset, dbSeeChanges)

    Do Until rs.EOF
        Application.SaveAsText acModule, rs("Name"), "C:\" & rs("Name") & ".txt"
        rs.MoveNext
    Loop

Cleanup:
    If Not rs Is Nothing Then rs.Close
    Set rs = Nothing
    Exit Sub
Errg:
    GoTo Cleanup
End Sub

如果您只想导出模块,那么这很好。 - Clon

0

另一种方法是将最常用的代码保存在一个外部的master.mdb文件中,并通过Modules->Tools->References->Browse->...\master.mdb连接到任意数量的*.mdbs。

唯一的问题是,在旧版的97 Access中,您可以直接在destination.mdb中进行调试、编辑和保存,但在所有更新版本中,自从MA 2000以来,“保存”选项已经消失了,并且在关闭未保存代码时会出现任何警告。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接