【问题标题】:Get Form Recordsource without opening the form在不打开表单的情况下获取表单记录源
【发布时间】:2016-07-21 00:46:32
【问题描述】:

MS Access 是否允许在不打开表单本身的情况下获取表单的记录源值?到目前为止,我正在尝试优化我的代码,我所做的只是隐藏表单然后获取 Recordsource 表单查询,但是加载需要时间,因为某些表单在加载时会触发代码。

【问题讨论】:

  • 您可以在设计视图中打开表单,获取记录源并关闭表单,因此不会触发任何事件。
  • 没有对依赖信息做一些花哨的工作。 Jens 的解决方案可能是您最好的选择。但请注意,如果您曾经发布到 ACCDE,那么您会遇到以编程方式在设计视图中打开表单的问题。
  • @Jens 我明白了,我不能接受你的建议,因为我们正在使用 ACCDE。 :(还有其他解决方案吗?谢谢 :)
  • @Newd 是的,你是对的,我不能使用设计表单,因为我们使用的是 accde :(
  • @user2180795 我很好奇你为什么需要这样做。也许您可以详细说明这一点,也许我们可以为您提供其他建议?

标签: ms-access vba


【解决方案1】:

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

[更新,2016 年 6 月 6 日]

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

我已经修改了代码,这适用于 Access 2010 和 2013。

[/更新]

大多数表单属性仅在表单打开时可用,但有些在 DAO 文档集合中的表单条目中可用。

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

FormName = "我的表单" For i = 0 至 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)。属性(i).值 下一个

为您的表单运行该 sn-p,您将看到一个“NameMap”属性,其中包含表单控件列表和 一些表单属性。

...在一个真正可怕的格式,需要一个二进制解析器。您可能想立即停止阅读并服用阿司匹林,然后再继续。

健康警告:

NameMap 属性未记录。因此,它不受支持,并且无法保证此解决方案将在 Microsoft Access 的未来版本中运行。

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

这是一个可怕的黑客行为:我对对您的理智造成的任何影响不承担任何责任。

好的,代码如下:

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

私有函数 FormRecordSource_FromNameMap(FormName As String) As String
' 从表单的 Document 对象的 NameMap 属性中读取记录源。

' 警告:这里有一个潜在的错误:如果表单的 RecordSource 属性为空 ' 并且它有一个或多个列表控件,其中填充了 .RecordSource 属性 ' 列表,此函数将返回第一个列表控件的记录源。
' 如果您在表单名称中使用非 ASCII 字符 (Char > 255),这将不起作用。

将 i 调暗为整数 将 j 调暗为整数 将 k 调暗为整数
将 arrByte() 调暗为字节
将 strOut 调暗为字符串 If Application.Version < 12 然后
arrByte = Application.CodeDb.Containers("Forms").Documents(FormName).Properties("NameMap").Value
For i = 1 To UBound(arrByte) - 2 Step 2
' NameMap 中 querydef 的 2 字节标记:
如果 (arrByte(i) = 228 并且 arrByte(i + 1) = 64) 那么

j = 我 + 2 Do While arrByte(j) = 0 And arrByte(j + 1) = 0 And j < UBound(arrByte) ' 遍历标记和字符串开头之间的空字符 j = j + 2 循环

strOut = "" 直到 (arrByte(j) = 0 和 arrByte(j + 1) = 0) 或 j >= UBound(arrByte) - 2 如果 arrByte(j) = 0 那么 j = j + 1 ' 循环直到我们到达终止这个字符串的空字符 ' 附加表或查询的 Bchars(不是 unicode Wchars!) strOut = strOut & Chr(arrByte(j)) j = j + 2 循环

Exit For ' 我们只想要第一个数据源 结束如果

接下来我
其他
arrByte = Nz(DLookup("[NameMap]", "[MSYSNameMap]", "[Name] = '" & FormName & "'"), vbNullChar)

If UBound(arrByte) < 4 然后退出函数

strOut = "" 对于 j = 60 到 UBound(arrByte) - 2 步骤 2

如果 arrByte(j) = 0 并且 arrByte(j + 1) = 0 则退出 For

strOut = strOut & Chr(arrByte(j))

下一个 j

结束如果

frmRecordSource_FromNameMap = strOut
擦除 arrByte
结束函数

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

现在,您没有要求的额外内容,但如果其他人在此处搜索“MS Access RecordSource for a closed form”,他们将会寻找:

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

大多数情况下,您的表单将处于打开状态。问题是,你不知道......如果它是一个子表单,它可能在 Forms() 集合中不可见。更糟糕的是,作为子表单托管的表单可能作为多个实例存在于多个打开的表单中。

祝你好运,如果你想提取动态属性...比如过滤器,或者记录源,如果它是由 VBA 'on the fly' 设置的。

公共函数 GetForm(FormName As String, Optional ParentName As String = "") As Form ' 返回一个表单对象,如果一个名称类似 FormName 的表单是打开的 ' FormName 可以包含通配符。 ' 如果没有打开匹配的表单,则不返回任何内容。

' 枚举打开表单中的子表单,并返回子表单 .form 对象,如果 ' 它有一个匹配的名称。请注意,一个表单可能会作为多个实例打开 ' 如果有多个子表单托管它;该函数返回第一个匹配项 ' 实例。指定命名的父窗体(或子窗体控件的名称),如果 '您需要避免由表单的多个实例引起的错误。

将 objForm 调暗为 Access.Form

如果                                                                                                                                                                              , 对于表单中的每个 objForm If objForm.Name Like FormName 那么 设置 GetForm = objForm 退出功能 万一 下一个 结束如果

如果 GetForm 什么都不是,那么 对于表单中的每个 objForm 设置 GetForm = SearchSubForms(objForm, FormName, ParentName) 如果不是 GetForm 什么都不是,那么 退出 万一 下一个 结束如果

结束函数

私有函数 SearchSubForms(objForm As Access.Form, SubFormName As String, Optional ParentName As String = "") As Form ' 如果命名对象 SubFormName 是 subform,则返回一个 Form 对象,其名称类似于 SubFormName ' 的开放形式,或者可以递归地枚举为开放子形式的子形式。

' 此函数返回第一个匹配的表单:请注意,一个表单可以被多个实例化 ' 实例,如果它被多个子表单控件使用。

将 objCtrl 调暗为控件 对于每个 objCtrl 在 objForm 中

    如果 TypeName(objCtrl) = “SubForm” 那么 If objCtrl.Form.Name Like SubFormName 那么 If ParentName = “” 或 objForm.Name Like ParentName 或 objCtrl.Name  Like ParentName 然后 设置 SearchSubForms = objCtrl.Form 退出 万一 别的 设置 SearchSubForms = SearchSubForms(objCtrl.Form, SubFormName, ParentName) 如果不是 SearchSubForms 什么都不是,那么 退出 万一 万一 结束如果

下一个 objCtrl

结束函数

公共函数 FormRecordSource(FormName As String, Optional ParentName As String = "") As String '返回表单的记录源,即使它没有在 Forms() 集合中打开

' 这将首先查找打开的表单。如果您正在寻找子表单,您可能需要一个 ' 托管子表单的表单的父名称:您的命名表单可能会以 ' 多个父表单中的子表单实例。

' 警告:这里有一个潜在的错误:如果表单没有打开,并且它有一个空白 '          RecordSource 属性,并且它具有一个或多个带有 .RecordSource 的控件 '          填充列表的属性,可以返回列表控件的 RecordSource

将 objForm 作为表单调暗

如果 FormName = "" 那么 退出功能 结束如果

设置 objForm = GetForm(FormName, ParentName)

如果 objForm 什么都不是,那么 FormRecordSource = FormRecordSource_FromNameMap(FormName) 别的 FormRecordSource = objForm.RecordSource 设置 objForm = 无 结束如果

结束函数

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

【讨论】:

  • 我很感兴趣。您是如何找到 NameMap 二进制文件的详细信息的?你知道如何完整地解释它吗?
  • @ThunderFrame - 我是如何找到它的:我需要一个封闭对象的属性,这意味着检查一个文档对象,而不是一个瞬态 forms() 对象。您要问的其他所有问题都是“目视检查和猜测”,不,我不知道这个未记录和不受支持的界面实际上是如何工作的 - 我怀疑它是否会在更高版本的 MS-Office 中公开。
  • NameMap 绝对仍然存在于 2010、2013 和 2016 下的 MSysNameMap 系统表和 MSysObjects 表的 LvProp 字段中,即使它不再作为 API 公开对象的属性。
  • @ThunderFrame - 谢谢 - 当我当前的客户进入当前十年时,我将需要它。
  • @ThunderFrame - 修改代码示例以从 MSysNameMap 中提取数据源。
【解决方案2】:

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

SELECT ID, AgentName FROM Agents

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

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

检索表单记录源的 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

或者你可以继续打开一个记录集

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

【讨论】:

    【解决方案3】:

    如果表单的记录源是SELECT 语句而不是表的名称或已保存的查询,您可以检查QueryDefs 集合以查找Access 为该记录源语句创建的隐藏QueryDef

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

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

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

    【讨论】:

      【解决方案4】:

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

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

      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 论坛。之后,您只需打开文件并搜索记录源。如果记录源为空,则不会导出,因此请记住这一点。另外,我怀疑这会提高性能,但谁知道呢!

      【讨论】:

      • +1 用于第一种方法。第二个我不确定它是否比让表单打开和加载更多的工作。一个小建议,我会选择第一个。我会使用 Cancel = True 而不是 DoCmd.Close 因为这可能会触发 Form_UnloadForm_BeforeUpdate 等。所以取消表单打开将是一个更好的解决方案。
      • @PaulFrancis 您对在发布到 ACCDE 之前通过函数更新表有什么想法。该表将包含所有表单名称及其记录源,可能还有一些其他信息。由于它是 ACCDE,因此一旦发布就不会更改,并且所有信息都可以直接从表格中轻松获得。该表可能是本地平板电脑,而不是链接的平板电脑。
      • @Newd,这可能是一个很好的解决方案,前提是表单的 RecordSource 在作为 ACCDE 发布后永远不会改变。我不确定 OP 是如何设计的,在我的应用程序中,一个表单可以根据从何处或如何打开它来拥有许多记录源。如果表格是一次性使用,则可以;表格中的记录源可能是 一个可行的选择。然后再次需要确保表中的 Recordsource 字段设置为Memo/Long Text。如果是这样,它需要确保数据没有被截断。除此之外,这也是一个很好的解决方案:)
      • @Newd - 关于Since it is ACCDE then it wouldn't be changing once it is published,不是真的。您唯一不能做的更改是设计更改,如添加控件、删除控件、编辑控件名称等。Recordsource 可以通过代码进行修改。
      • @PaulFrancis 有趣的一点,所以基本上只要在发布后不调整记录源,它至少是一个可行的选择。尽管在发布到 ACCDE 后,您使用什么方法来更改 RecordSource 而无需在代码中使用设计视图?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-21
      • 1970-01-01
      • 2010-10-15
      • 1970-01-01
      相关资源
      最近更新 更多