在不打开表单的情况下获取表单记录源

时间:2015-02-10 09:11:23

标签: ms-access access-vba

MS Access是否允许获取表单的记录源值而无需打开表单本身?我现在正在尝试优化我的代码,我做的是我只是隐藏表单然后获取Recordsource表单查询但是加载需要时间,因为某些表单在onload上触发代码。

4 个答案:

答案 0 :(得分:2)

一种选择是将表单的记录源保存为查询。假设您有一个名为[AgentForm]的表单,其记录源为

SELECT ID, AgentName FROM Agents

在开发数据库的.accdb副本中,在“设计视图”中打开表单,然后在“查询构建器”中打开“记录源”。点击“另存为”按钮...

SaveAs.png

并将查询保存为“AgentForm_RecordSource”。现在,表单的Record Source属性只是对已保存查询的引用,并且可以通过QueryDef对象直接访问查询本身。因此,您可以使用

检索表单的Record Source的SQL语句
Dim cdb As DAO.Database, qdf As DAO.QueryDef, sql As String
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
sql = qdf.SQL

或者您可以继续使用

打开Recordset
Dim cdb As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
Set rst = qdf.OpenRecordset

答案 1 :(得分:2)

我这里的游戏迟到了 - 我有时会在原始问题发布后数月或数年发布答案,因为当我快速搜索'Stack'找到与我自己当天的问题相关的问题时,我会发布自己的解决方案,但没有我可以实际使用的答案。

[更新,2016年6月6日]

从Access 2010开始,“NameMap”属性在文档对象中不可用。但是,'Stacker Thunderframe已经指出现在可以在'MsysNameMap'表中找到它。

我修改了代码,这在Access 2010和2013中有效。

[/ UPDATE]

表单的大部分属性仅在表单打开时可用,但有些表单可在DAO Documents集合中的表单条目中找到。

DAO'文档'是一个可怕的对象:它不会持久存在于内存中,每次使用它时都必须明确引用它:

FormName = "MyForm"
For i = 0 To Application.CodeDb.Containers("Forms").Documents(FormName).Properties.Count - 1
    Debug.Print i & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Name & vbTab & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Value
Next                                     

为您的表单运行该代码段,您将看到一个“NameMap”属性,其中包含表单控件的列表,以及表单属性的部分

......以一种真正可怕的格式,需要二进制解析器。你可能想要在继续​​阅读之前停止阅读并服用阿司匹林。

健康警告:

NameMap属性未记录。因此,它不受支持,并且无法保证此解决方案在Microsoft Access的未来版本中可以正常工作。

如果NameMap的记录源的双字节二进制标签发生变化,或者它是特定于语言环境的,那么下面代码中的解决方案将停止工作。

这是一个可怕的黑客:我对你的理智的任何影响不承担任何责任。

好的,这是代码:

从封闭的MS-Access表单返回记录源的VBA函数:

Private Function FormRecordSource_FromNameMap(FormName As String) As String
' Reads the Record Source from the NameMap Property of the Document object for the form.

' WARNING: there is a potential error here: if the form's RecordSource property is blank ' and it has one or more list controls with a .RecordSource property populating ' the list, this function will return the first list control's Record Source.
' This won't work if you're using non-ASCII characters (Char > 255) in your form name.

Dim i As Integer Dim j As Integer Dim k As Integer
Dim arrByte() As Byte
Dim strOut As String If Application.Version < 12 Then
arrByte = Application.CodeDb.Containers("Forms").Documents(FormName).Properties("NameMap").Value
For i = 1 To UBound(arrByte) - 2 Step 2
' 2-byte marker for a querydef in the NameMap:
If (arrByte(i) = 228 And arrByte(i + 1) = 64) Then

j = i + 2 Do While arrByte(j) = 0 And arrByte(j + 1) = 0 And j < UBound(arrByte) ' loop through the null chars between the marker and the start of the string j = j + 2 Loop

strOut = "" Do Until (arrByte(j) = 0 And arrByte(j + 1) = 0) Or j >= UBound(arrByte) - 2 If arrByte(j) = 0 Then j = j + 1 ' loop until we reach the null char which terminates this string ' appending the Bchars (not unicode Wchars!) of the table or query strOut = strOut & Chr(arrByte(j)) j = j + 2 Loop

Exit For ' we only want the first datasource End If

Next i
Else
arrByte = Nz(DLookup("[NameMap]", "[MSYSNameMap]", "[Name] = '" & FormName & "'"), vbNullChar)

If UBound(arrByte) < 4 Then Exit Function

strOut = "" For j = 60 To UBound(arrByte) - 2 Step 2

If arrByte(j) = 0 And arrByte(j + 1) = 0 Then Exit For

strOut = strOut & Chr(arrByte(j))

Next j

End If

frmRecordSource_FromNameMap = strOut
Erase arrByte
End Function

如果你在(比方说)OpenRecordset或DCOUNT函数中使用RecordSource,我建议你将它封装在方括号中:你可能会从RecordSource中的'SELECT'语句中获取隐藏查询对象的名称,该名称将包含需要特殊处理的“〜”代字符。

现在,你没有要求的额外的东西,但是其他人会在寻找他们在这里搜索'MS Access RecordSource for a closed form'的方式:

获取MS-Access表单的RecordSource,无论是否打开

大部分时间,您的表单都会打开。问题是,你不知道......如果它是一个子表单,它可能在Forms()集合中不可见。更糟糕的是,作为子表单托管的表单可能作为多个打开表单中的多个实例存在。

祝你好运,如果你想要提取动态属性......就像过滤器一样,如果由VBA设置'动态',则记录源。

Public Function GetForm(FormName As String, Optional ParentName As String = "") As Form ' Returns a form object, if a form with a name like FormName is open ' FormName can include wildcards. ' Returns Nothing if no matching form is open.

' Enumerates subforms in open forms, and returns the subform .form object if ' it has a matching name. Note that a form may be open as multiple instances ' if more than one subform hosts it; the function returns the first matching ' instance. Specify the named parent form (or the subform control's name) if ' you need to avoid an error arising from multiple instances of the form.

Dim objForm As Access.Form

If ParentName = "" Then     For Each objForm In Forms         If objForm.Name Like FormName Then             Set GetForm = objForm             Exit Function         End If     Next End If

If GetForm Is Nothing Then     For Each objForm In Forms         Set GetForm = SearchSubForms(objForm, FormName, ParentName)         If Not GetForm Is Nothing Then             Exit For         End If     Next End If

End Function

Private Function SearchSubForms(objForm As Access.Form, SubFormName As String, Optional ParentName As String = "") As Form ' Returns a Form object with a name like SubFormName, if the named object SubFormName is subform ' of an open form , or can be recursively enumerated as the subform of an open subform.

' This function returns the first matching Form: note that a form can be instantiated in multiple ' instances if it is used by more than one subform control.

Dim objCtrl As Control For Each objCtrl In objForm

    If TypeName(objCtrl) = "SubForm" Then              If objCtrl.Form.Name Like SubFormName Then             If ParentName = "" Or objForm.Name Like ParentName Or objCtrl.Name Like ParentName Then                 Set SearchSubForms = objCtrl.Form                 Exit For             End If         Else             Set SearchSubForms = SearchSubForms(objCtrl.Form, SubFormName, ParentName)             If Not SearchSubForms Is Nothing Then                 Exit For             End If         End If          End If

Next objCtrl

End Function

Public Function FormRecordSource(FormName As String, Optional ParentName As String = "") As String ' Returns the Recordsource for a form, even if it isn't open in the Forms() collection

' This will look for open forms first. If you're looking for a subform, you may need a ' parent name for the form which hosts the subform: your named form might be open as a ' subform instance in more than one parent form.

' WARNING: there is a potential error here: if the form isn't open, and it has a blank '          RecordSource property, and it has one or more controls with a .RecordSource '          property populating a list, a list control's RecordSource could be returned

Dim objForm As Form

If FormName = "" Then     Exit Function End If

Set objForm = GetForm(FormName, ParentName)

If objForm Is Nothing Then     FormRecordSource = FormRecordSource_FromNameMap(FormName) Else     FormRecordSource = objForm.RecordSource     Set objForm = Nothing End If

End Function

分享和享受:请接受我对代码示例中任何不需要的换行符的道歉。

答案 2 :(得分:1)

由于您无法在设计视图中打开表单并定期打开表单会导致性能问题,因此还有一些解决方法:

根据您要检查已关闭表单的记录源的方式,您可以在单独的模块中以下列方式设置全局变量:

Public glb_getrecordsource As String

之后,根据您调用代码的方式,您可以执行以下操作:

Private Sub Command1_Click()
glb_getrecordsource = "Yes"
DoCmd.OpenForm "Form1"

'... Do something

End Sub

然后,作为最后一步,将以下内容放在表单OnLoad事件的开头:

Private Sub Form_Load()
If glb_getrecordsource = "Yes" Then
    glb_getrecordsource = Me.Form.RecordSource
    DoCmd.Close acForm, "Form1", acSaveYes
    Exit Sub
End If

'... Usual OnLoad events

End Sub

这至少可以解决性能问题,因为您不会在表单的加载事件中触发任何耗时的事件。

另一种解决方法: 您可以将表单导出为.txt文件,然后在文本文件中搜索记录源。以下代码将表单导出到指定文件夹中的.txt文件:

Dim db As Database
Dim d As Document
Dim c As Container
Dim sExportLocation As String

Set db = CurrentDb()

sExportLocation = "C:\AD\" 'Do not forget the closing back slash! ie: C:\Temp\
Set c = db.Containers("Forms")
    For Each d In c.Documents
        Application.SaveAsText acForm, d.Name, sExportLocation & "Form_" & d.Name & ".txt"
    Next d

代码部分借鉴this论坛。之后,您只需打开文件并搜索记录源。如果recordsource为空,则不会导出,因此请记住这一点。另外,我怀疑这会改善性能,但谁知道呢!

答案 3 :(得分:1)

如果表单的记录源是SELECT语句而不是表或保存的查询的名称,则可以检查QueryDefs集合中隐藏的QueryDef为其创建的Access记录来源声明。

如果存在,您可以检查其.SQL属性。

strFormName = "Form15"
? CurrentDb.QueryDefs("~sq_f" & strFormName).SQL
SELECT DISTINCTROW *
FROM [DB Audits];

您可以捕获错误#3265,“此集合中找不到的项目”,如果QueryDef不存在,将会抛出该错误。