VBA - 安全存储变量引用

时间:2017-04-20 18:41:22

标签: vba ms-access reference

我熟悉通过引用将参数传递给过程。或者,ParamArray允许我灵活地通过引用将0或更多参数传递给过程。但是,这种方法让我想知道是否有办法保留对程序范围之外的一个或多个变量的引用。当我看到VBA Array函数被声明为这样时,我的第一个希望就是:{/ p>

Array(ParamArray ArgList() As Variant)

所以,我把以下测试代码放在一起:

Private Sub Test()

    Dim a   As Object
    Dim b() As Variant

    ParamArrayTest a
    Debug.Print TypeName(a) ' Output is 'Dictionary'
    b = Array(a)            ' b should be like ParamArray ArgList()
    Set b(0) = Nothing      ' This should clear a
    Debug.Print TypeName(a) ' Output is still 'Dictionary'

End Sub

Private Sub ParamArrayTest(ParamArray ArgList() As Variant)

    Set ArgList(0) = CreateObject("Scripting.Dictionary")

End Sub

不幸的是,这并没有像我预期的那样奏效。尽管通过Array将参数传递给ParamArray函数,但看起来返回的数组是按值而不是通过引用。

进一步的研究使我了解了未记录的VBA VarPtr / StrPtr / ObjPtr函数。我发现了许多将它们与API RtlMoveMemory函数结合使用的示例。但是,我读到的所有文章都强烈反对使用该方法,因为它很容易使应用程序崩溃。我的一些测试确实让Access崩溃了。

我的另一个想法是看我是否可以直接将一个变量的引用分配给另一个变量:

Private Sub Test()

    Dim a As Object
    Dim b As Variant

    b = ByRef a ' Throws a compiler error

End Sub

只需说,编译器根本不允许这样做。那么我的问题是,可以安全地存储/保存变量引用超出过程的范围(最好是在另一个变量中)吗?

修改

如果我对我正在努力建立的内容有所了解,我认为会更有帮助。

我目前正在创建一个包装类,它将所有表单/控件事件传递给我的一个模块中的过程。它将与2个具有相同控制结构但连接到不同源表的表单一起使用。请记住,代码不完整,但应足以说明我想要克服的问题。此外,Database是我的VBA项目名称。

代码有四个部分:

  1. Form_TEST_FORM - 表单模块

    Private Sub Form_Open(Cancel As Integer)
    
        FormHub.InitForm Me, Cancel
    
    End Sub
    
  2. FormHub - 模块

    Public Sub InitForm( _
        ByRef Form As Access.Form, _
        ByRef Cancel As Integer _
    )
    
        Dim Evt As Database.EventHandler
    
        Set Evt = New Database.EventHandler
        Evt.InitFormObject Form, Cancel
        FormList.Add Evt, Form.Name
    
    End Sub
    
    Private Function FormList() As VBA.Collection
    
        Static Init As Boolean
        Static Coll As VBA.Collection
    
        If Not Init Then
    
            Set Coll = New VBA.Collection
            Init = True
    
        End If
    
        Set FormList = Coll
    
    End Function
    
  3. FormControl - 课程模块

    Public Ptr      As Variant ' Pointer to form control variable
    Public acType   As Access.AcControlType
    
  4. EventHandler - 课程模块

    Private WithEvents Form     As Access.Form
    Private WithEvents SForm    As Access.SubForm
    Private CtrlList            As VBA.Collection
    
    Private Sub Class_Initialize()
    
        InitCtrlList
    
    End Sub
    
    Public Sub InitFormObject(FormObj As Access.Form, ByRef Cancel As Integer)
    
        Dim ErrFlag As Boolean
        Dim Ctrl    As Access.Control
        Dim FCtrl   As Database.FormControl
    
        On Error GoTo Proc_Err
    
        Set Form = FormObj
    
        If Form.Controls.Count <> CtrlList.Count Then
    
            Err.Raise 1, , _
            "Form has incorrect number of controls"
    
        End If
    
        ' This is where I want to validate the form controls
        ' and also initialize my event variables.
        For Each Ctrl In Form.Controls
    
            If Not CtrlExists(FCtrl, Ctrl.Name) Then
    
                Err.Raise 2, , _
                "Invalid control name"
    
            ElseIf FCtrl.acType <> Ctrl.ControlType Then
    
                Err.Raise 3, , _
                "Invalid control type"
    
            Else
    
                ' Initialize the correct variable with it's
                ' pointer.  This is the part I haven't been
                ' able to figure out yet.
                Set FCtrl.Ptr = Ctrl
    
            End If
    
        Next
    
    Proc_End:
    
        On Error Resume Next
    
        If ErrFlag Then
    
            ClearEventVariables
    
        End If
    
        Set Ctrl = Nothing
        Set FCtrl = Nothing
    
        Exit Sub
    
    Proc_Err:
    
        ErrFlag = True
        Debug.Print "InitFormObject " & _
        "Error " & Err & ": " & Err.Description
        Resume Proc_End
    
    End Sub
    
    Private Function CtrlExists( _
        ByRef FCtrl As Database.FormControl, _
        ByRef CtrlName As String _
    ) As Boolean
    
        On Error Resume Next
    
        Set FCtrl = CtrlList(CtrlName)
        CtrlExists = Err = 0
    
    End Function
    
    Private Sub InitCtrlList()
    
        Set CtrlList = New VBA.Collection
        CtrlList.Add SetCtrlData(SForm, acSubform), "SForm"
    
    End Sub
    
    Private Function SetCtrlData( _
        ByRef Ctrl As Access.Control, _
        ByRef acType As Access.AcControlType _
    ) As Database.FormControl
    
        Set SetCtrlData = New Database.FormControl
    
        With SetCtrlData
    
            ' This assignment is where I need to keep a reference
            ' to the variable in the class.  However, it doesn't
            ' work.
            Set .Ptr = Ctrl
            .acType = acType
    
        End With
    
    End Function
    
    Private Sub ClearEventVariables()
    
        Dim FormCtrl As Database.FormControl
    
        Set Form = Nothing
    
        For Each FormCtrl In CtrlList
    
            ' Assuming I was able to retain a reference to the
            ' class variable, this would clear it.
            Set FormCtrl.Ptr = Nothing
    
        Next
    
    End Sub
    
    Private Sub Class_Terminate()
    
        ClearEventVariables
        Set CtrlList = Nothing
    
    End Sub
    
  5. 为简单起见,我在代码示例中仅使用了1个控件。但是,我的想法是简化我需要修改的代码,以便在表单设计更改时添加/删除控件。或者,如果我必须在项目中添加更多表单。

1 个答案:

答案 0 :(得分:0)

如果只需要在单个模块中引用,请在模块头中声明为Public。如果要在任何模块中引用,请在通用模块头中声明为Global。甚至可以通过这种方式声明数组和记录集以及连接对象。请注意,如果代码在运行时中断,这些变量将丢失其值。

或者查看TempVar对象变量。如果代码中断,它们不会丢失值。但只能存储数字或文本值,而不能存储对象。