Ste*_*fan 8 excel vba excel-vba
我在项目中有代码将Sheet中的数据读入记录集.VBA代码受密码保护.
为了测试,我简化了代码,如下所示:
Option Explicit
Sub sTest()
Dim dbtmp As DAO.Database
Set dbtmp = OpenDatabase(Application.ActiveWorkbook.FullName, False, True, _
"Excel 8.0;HDR=Yes")
dbtmp.Close
Set dbtmp = Nothing
End Sub
Run Code Online (Sandbox Code Playgroud)
每当我从Userform运行此代码时,关闭excel后,系统会提示我输入VBAProject密码.根据我的意思,工作簿中的模块数量,我必须取消,至少两次.
上周我一直在打破这个问题,阅读我能找到的网上的每一篇文章,但还没有找到解决方案.
DAO 并不是一个从 Excel 文件读取数据的好平台。
实际上,所有可用的 Microsoft 数据库驱动程序技术都没有 - 它们都有一些内存泄漏,并且较旧的技术会创建 Excel.exe 的隐藏实例 - 因此 VBA 项目中的任何内容(例如,缺少库或调用非编译代码的事件)将引发某种错误,使 Excel 认为您正在尝试访问该代码。
下面是一些使用 ADODB 的代码,ADODB 是一种更新的数据库技术,可以解决 DAO 的任何特定问题。
我没有时间删除所有与您的要求无关的内容 - 抱歉,内容太多了!- 但是保留所有这些替代连接字符串可能对您很有帮助:任何遇到此类问题的人都需要稍微尝试一下,并通过反复试验找出哪种技术有效:
公共函数 FetchRecordsetFromWorkbook(ByVal SourceFile As String, _ ByVal SourceRange 作为字符串,_ 可选 ReadHeaders 作为 Boolean = True, _ 可选 StatusMessage As String = "", _ 可选 GetSchema 作为 Boolean = False, _ 可选的 CacheFile As String = "" _ ) 作为 ADODB.Recordset 应用程序.Volatile False
' 从工作簿中的范围返回静态持久非锁定 ADODB 记录集
' 如果您的范围是工作表,请将“$”附加到工作表名称。“表”的列表 ' 可以通过设置参数 GetSchema=True 来提取工作簿中可用的名称
' 如果设置 ReadHeaders = True,数据的第一行将被视为字段 ' 表的名称;这意味着您可以传递 SQL 查询而不是范围或表
' 如果设置 ReadHeaders = False,则数据的第一行将被视为数据;这 ' 列名将自动分配为 'F1', 'F2'...
' 如果检索过程没有错误,则 StatusMessage 返回行计数,或者 '#ERROR'
' 请注意,Microsoft ACE 数据库驱动程序存在内存泄漏和稳定性问题
出错时转到 ErrSub
常量超时只要 = 60
Dim objConnect As ADODB.Connection 首先变暗为 ADODB.Recordset Dim strConnect 作为字符串 Dim bFileIsOpen As Boolean
将 objFSO 调暗为 Scripting.FileSystemObject 暗淡我只要
将临时文件变暗为字符串 将 strTest 变暗为字符串 将 SQL 变暗为字符串 将 strExtension 变暗为字符串 Dim strPathFull 作为字符串 昏暗的时间开始为单身 将 strHeaders 变暗为字符串 Dim strFilter 作为字符串
如果 SourceFile = "" 那么 退出功能 万一
' 解析出Web文件夹路径 如果 Left(SourceFile, 5) = "http:" 那么 源文件 = 右(源文件, Len(源文件) - 5) SourceFile = 替换(SourceFile, "%20", " ") SourceFile = 替换(SourceFile, "%160", " ") SourceFile = 替换(SourceFile, "/", "\") 万一
strPathFull = 源文件
如果 Len(Dir(SourceFile)) = 0 那么 Err.Raise 1004,APP_NAME 和“GetRecordsetFromWorkbook”,_ “#ERROR - 文件 '” & SourceFile & “' 未找到。” 退出功能 万一
设置 objFSO = FSO
strExtension = GetExtension(strPathFull)
bFileIsOpen = FileIsOpen(源文件) 如果不是 bFileIsOpen 那么 TempFile = objFSO.GetSpecialFolder(2).Path & "\" & TrimExtension(objFSO.GetTempName()) _ &“。” &str 扩展名 objFSO.CopyFile 源文件、临时文件、True 源文件 = 临时文件 万一
如果 InStr(1, SourceRange, "SELECT", vbTextCompare) > 0 且 _ InStr(7, SourceRange, "FROM", vbTextCompare) > 1 然后 strHeaders =“HDR=是” ElseIf ReadHeaders = True 那么 strHeaders =“HDR=是” 别的 strHeaders = "HDR=否" 万一
选择案例 strExtension 案例“.xls”
Run Code Online (Sandbox Code Playgroud)'strConnect = "ODBC;DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};" _ ' & "ReadOnly=1;DBQ=" & Chr(34) & SourceFile & Chr(34) & ";" _ ' & ";Extended Properties=" &Chr(34) & "HDR=No;IMEX=1;MaxScanRows=0" & Chr(34) & ";" 'strConnect = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Chr(34) & SourceFile & _ ' Chr(34) & ";Extended Properties=" & Chr(34) & "Excel 8.0;" & strHeaders _ ' & ";IMEX=1;MaxScanRows=0" & Chr(34) & ";" strConnect = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & Chr(34) & SourceFile & _ Chr(34) & ";Persist Security Info=True;Extended Properties=" & _ Chr(34) & "Excel 8.0;" & strHeaders & ";IMEX=1;MaxScanRows=0" & Chr(34) & ";"案例“xlsx”
Run Code Online (Sandbox Code Playgroud)strConnect = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & Chr(34) & SourceFile & _ Chr(34) & ";Persist Security Info=True;Extended Properties=" & Chr(34) & _ "Excel 12.0 Xml;" & strHeaders & ";IMEX=1;MaxScanRows=0" & Chr(34) & ";"案例“xlsm”
Run Code Online (Sandbox Code Playgroud)'strConnect = "Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};" & _ ' "ReadOnly=1;DBQ=" & SourceFile & ";" & Chr(34) & SourceFile & Chr(34) & ";" & _ ' ";Extended Properties=" & Chr(34) & "Excel 12.0;" & strHeaders & _ ' ";IMEX=1;MaxScanRows=0" & Chr(34) & ";" strConnect = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & Chr(34) & SourceFile & _ Chr(34) & ";Persist Security Info=True;Extended Properties=" & Chr(34) _ & "Excel 12.0 Macro;" & strHeaders & ";IMEX=1;MaxScanRows=0" & Chr(34) & ";"案例“xlsb”
Run Code Online (Sandbox Code Playgroud)'strConnect = "Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};" & "ReadOnly=1; _ ' DBQ=" & SourceFile & ";" & Chr(34) & SourceFile & Chr(34) & ";" & _ ' ";Extended Properties=" & Chr(34) & "Excel 12.0;" & strHeaders & _ ' ";IMEX=1;MaxScanRows=0" & Chr(34) & ";" ' This ACE driver is unstable on xlsb files... But it's more likely to return a result, if you don't mind crashes: strConnect = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & Chr(34) & SourceFile & Chr(34) & _ ";Persist Security Info=True;Extended Properties=" & Chr(34) & "Excel 12.0;" & _ strHeaders & ";IMEX=1;MaxScanRows=0" & Chr(34) & ";"其他情况 Err.Raise 999、APP_NAME 和“GetRecordsetFromWorkbook”、“#ERROR - 文件格式未知” 结束选择
出错时转到 ErrSub
Run Code Online (Sandbox Code Playgroud)'SetTypeGuessRows timeStart = VBA.Timer Set objConnect = New ADODB.Connection With objConnect .ConnectionTimeout = TIMEOUT .CommandTimeout = TIMEOUT .Mode = adModeRead .ConnectionString = strConnect .Open strConnect, , , adAsyncConnect Do While .State > adStateOpen If VBA.Timer > timeStart + TIMEOUT Then Err.Raise -559038737, _ APP_NAME & " GetRecordsetFromWorkbook", _ "Timeout: the Excel data connection object did not respond in the " _ & TIMEOUT & "-second interval specified by this application." Exit Do End If If .State > adStateOpen Then Sleep 100 If .State > adStateOpen Then Sleep 100 Loop End With Set rst = New ADODB.Recordset timeStart = VBA.Timer With rst .CacheSize = 8 .PageSize = 8 .LockType = adLockReadOnly If InStr(1, SourceRange, "SELECT", vbTextCompare) > 0 And _ InStr(7, SourceRange, "FROM", vbTextCompare) > 1 Then SQL = SourceRange Else .MaxRecords = 8192 SQL = "SELECT * FROM [" & SourceRange & "] " ' Exclude empty rows from the returned data using a 'WHERE' clause. With objConnect.OpenSchema(adSchemaColumns) strFilter = "" .Filter = "TABLE_NAME='" & SourceRange & "'" If .EOF Then .Filter = 0 .MoveFirst End If Do While Not .EOF If UCase(!TABLE_NAME) = UCase(SourceRange) Then Select Case !DATA_TYPE Case 2, 3, 4, 5, 6, 7, adUnsignedTinyInt, adNumeric ' All the numeric types you'll see in a JET recordset from Excel strFilter = strFilter & vbCrLf & " AND [" & !COLUMN_NAME & "] = 0 " Case 130, 202, 203, 204, 205 ' Text and binary types that pun to vbstring or byte array strFilter = strFilter & vbCrLf & " AND [" & !COLUMN_NAME & "] = '' " End Select ' Note that we don't try our luck with the JET Boolean data type End If .MoveNext Loop .Close End With If strFilter <> "" Then strFilter = Replace(strFilter, vbCrLf & " AND [", " [", 1, 1) strFilter = vbCrLf & "WHERE " & vbCrLf & "NOT ( " & strFilter & vbCrLf & " ) " SQL = SQL & strFilter End If End If .Open SQL, objConnect, adOpenForwardOnly, adLockReadOnly, adCmdText + adAsyncFetch i = 0 Do While .State > 1 i = (i + 1) Mod 3 Application.StatusBar = "Retrieving data" & String(i, ".") If VBA.Timer > timeStart + TIMEOUT Then Err.Raise -559038737, _ APP_NAME & " Fetch data", _ "Timeout: the Excel Workbook did not return data in the " & _ TIMEOUT & "-second interval specified by this application." Exit Do End If If .State > 1 Then Sleep 100 ' There's a very slight performance gain doing it this way If .State > 1 Then Sleep 100 Loop End With If rst.State = 1 Then CacheFile = objFSO.GetSpecialFolder(2).Path & "\" & TrimExtension(objFSO.GetTempName()) & ".xml" rst.Save CacheFile, adPersistXML ' , adPersistADTG rst.Close End If Set rst = Nothing objConnect.Close objConnect.Errors.Clear Set objConnect = Nothing Set rst = New ADODB.Recordset rst.CursorLocation = adUseClient rst.StayInSync = False rst.Open CacheFile ', , adOpenStatic, adLockReadOnly, adCmdFile StatusMessage = rst.RecordCount Set FetchRecordsetFromWorkbook = rst退出子目录: 出错时继续下一步
Run Code Online (Sandbox Code Playgroud)Set rst = Nothing objConnect.Close Set objConnect = Nothing If (bFileIsOpen = False) And (FileIsOpen(SourceFile) = True) Then For i = 1 To Application.Workbooks.Count If Application.Workbooks(i).Name = Filename(SourceFile) Then Application.Workbooks(i).Close False Exit For End If Next i End If Exit Function错误子:
Run Code Online (Sandbox Code Playgroud)StatusMessage = "" StatusMessage = StatusMessage & "" If InStr(Err.Description, "not a valid name") Then StatusMessage = StatusMessage & "Cannot read the data from your file: " StatusMessage = StatusMessage & vbCrLf & vbCrLf StatusMessage = StatusMessage & Err.Description StatusMessage = StatusMessage & vbCrLf & vbCrLf StatusMessage = StatusMessage & "It's possible that the file has been locked, _ but the most likely explanation is that the file _ doesn't contain the named sheet or range you're _ trying to read: check that you've saved the _ correct range name with the correct file name." StatusMessage = StatusMessage & vbCrLf & vbCrLf StatusMessage = StatusMessage & "If this error persists, please contact the Support team." MsgBox StatusMessage, vbCritical, APP_NAME & ": data access error:" StatusMessage = "#ERROR " & StatusMessage ElseIf InStr(Err.Description, "Could not find the object '& SourceRange") Then StatusMessage = StatusMessage & "" StatusMessage = StatusMessage & "" StatusMessage = StatusMessage & "" MsgBox Err.Description & vbCrLf & vbCrLf & "Please contact the Support team. _ This error probably means that source _ file is locked, or that the wrong file _ has been saved here: " & vbCrLf & vbCrLf & _ strPathFull, vbCritical, APP_NAME & ": file data error:" StatusMessage = "#ERROR " & StatusMessage ElseIf InStr(Err.Description, "Permission Denied") Then StatusMessage = StatusMessage & "Cannot open the file: " StatusMessage = StatusMessage & vbCrLf & vbCrLf StatusMessage = StatusMessage & vbTab & Chr(34) & strPathFull & Chr(34) StatusMessage = StatusMessage & vbCrLf & vbCrLf StatusMessage = StatusMessage & "Another user probably has this file open. _ Please wait a few minutes, and try again. _ If this error persists, please contact Desktop team." MsgBox StatusMessage, vbCritical, APP_NAME & ": file access error:" StatusMessage = "#ERROR " & StatusMessage Else StatusMessage = StatusMessage & "#ERROR " & Err.Number & ": " & Err.Description MsgBox StatusMessage, vbCritical, APP_NAME & ": file data error:" End If Resume ExitSub' # 保留这个不可访问的语句以进行调试: 恢复
结束功能
如果您遇到“_”分割线周围的换行问题,我们深表歉意。
您还需要常量“APP_NAME”的声明:
PUBLIC CONST APP_NAME As String = "SQL 蓝屏演示器"
以及“睡眠”函数的 VBA API 声明:
#If VBA7 And Win64 Then ' 64 位 Windows 下的 64 位 Excel:PtrSafe 声明和 LongLong 私有声明 PtrSafe Sub Sleep Lib“kernel32”(ByVal dwMilliseconds As LongLong) #ElseIf VBA7 Then ' 32 位环境中的 VBA7:PtrSafe 声明,但没有 LongLong 私有声明 PtrSafe Sub Sleep Lib“kernel32”(ByVal dwMilliseconds As Long) #Else ' 32 位 Excel 私有声明子睡眠库“kernel32”(ByVal dwMilliseconds As Long) #万一
对 Microsoft Excel 运行 SQL 最好被视为一件坏事:是的,SQL 是迄今为止处理大量表格数据的最佳工具;但微软不会很快修复这些内存泄漏。雷德蒙德没有人对您试图在那里做的事情感兴趣 - 当您可以购买 MS-Access 或 SQL Server 的副本并移植您的数据时。
然而,当您不打算拥有自己的 SQL Server 并且您在别人的电子表格中拥有大量数据时,它仍然是最不糟糕的解决方案。或电子表格,复数。
该文章的小标题如下:
这是一个关于开发人员不应该看到或做的事情的警示故事,涉及业务逻辑失败、变通方法和更糟糕的方法、预算仙女、业务分析师和在电梯大厅寻求奇迹治愈的贪婪朝圣者的转移和离题。
...你应该将其视为对你所面临的警告:一场漫长而痛苦的代码争论,去做一些你可能应该以其他方式做的事情。