VBA 类模块系列之十三——给类对象安装心脏起搏器

原计划本篇应该谈谈如何定义自己的事件的,比如给我们创建的第一个类Person定义一个生日提醒事件。然而,我发现 … 继续阅读“VBA 类模块系列之十三——给类对象安装心脏起搏器”

原计划本篇应该谈谈如何定义自己的事件的,比如给我们创建的第一个类Person定义一个生日提醒事件。然而,我发现,我们的Person类对象从一出生(Set objperson = New Person)到最后死亡(Set objperson = Nothing),中间是没有呼吸和脉搏的。

你在说什么?什么是呼吸和脉搏?

Sub Test()
'
'以上代码省略
    objperson.Name = "赵冰冰"
    objperson.Gender = 3
    objperson.Speak
'
'以下代码省略
End Sub

上面的3句代码不是类对象的呼吸和脉搏吗?

呃,你说的没错,这3句是类对象的呼吸和脉搏,但它的心脏就只跳动了3下而已。因为外界就只刺激了它3下,这3句代码执行完,我们的类对象的心脏就停止了跳动。

一个人,虽然活着,却需要外界刺激才能反应,这个人其实跟植物人没什么两样。一个植物人,你怎么能期望她主动告诉你:“下周我过生日”呢?

所以,在给我们创建的第一个类Person定义事件之前,我们得先给她安装一个心脏起搏器,要让她有一定的主观能动性才行。

心脏起搏器是个什么东西?它具体长什么样,我也不知道,但是你可以捂住你的胸口感受一下自己的心跳,你就会知道,它是一个非常有规律的脉冲,比如0.7秒跳动一下。我们从哪里去给我们的类Person找一个每0.7秒跳动一下的东西?

作为一个有一定经验的Access VBA开发人员,不知道你使用过窗体的Timer事件没有?没有没关系,我们一起来创建一个窗体3,在窗体3的属性表的事件选项卡中,将“计时器间隔”设置为一个大于0的数(比如说2000),然后为“计时器触发事件”编写一段响应程序,那么,每隔2000毫秒(2秒)时间,这段响应程序就会自动执行一次。如下图所示:

'窗体3
Option Compare Database
Option Explicit

Private Sub Form_Timer()
    Debug.Print "每隔2秒,这里就自动执行一次"
End Sub

你保存一下窗体3,然后打开它,进到IDE界面,你会看到立即窗口中,每隔2秒,就会有一行文字“每隔2秒,这里就自动执行一次”。

如果将2000改为700,就是心脏跳动的频率了。这就是我们要给Person类安装的心脏起搏器。

那怎么安装到我们的Person类中呢?我们先来分析一下我们的需求。我们的Person类需要每隔0.7秒获得一个脉冲,而窗体3,它每隔0.7秒就能发起一个Timer事件,所以,如果我们在Person类中能够监听窗体3的Timer事件的话,我们的目的就达到了。

所以,我们在Person类的头部,定义一个与窗体3一样类型的窗体变量mfrm,因为Person类还要监听窗体变量mfrm所指向的窗体3的Timer事件,所以要添加WithEvents修饰符:

'Person 类代码
Option Compare Database
Option Explicit
 
Private mstrName As String
Private menumGender As pGender
Private mstrSituation As String
Private mdatDOB As Date

Private WithEvents mfrm As Access.Form

Public Enum pGender
    Female = 0
    Male = 1
End Enum
'
'以下代码省略

到目前位置,只是定义好了窗体变量mfrm,它还需要指向窗体3。套用上一篇的方法,在Person类的实例化事件Class_Initialize()中(对应着上一篇的窗体2的Form_Load()事件),将mfrm指向窗体3(Set mfrm = Forms(“窗体3”))。

当然这样做是没有问题的,需要注意的一点是,代码执行前,先要打开窗体3,否则Forms(“窗体3”)就报错。

窗体3是Person类的心脏起搏器,现在你把它打开了,呃,人人都看得到Person类的心脏起搏器,一个人,她的心脏长在外面,吓人就算了,你发现她的心脏右上角还有一个关闭按钮,那就真的有点说不过去了。一不小心人把她的心脏给关闭了可咋整?(当然,应该是关不掉的,原因暂按不表)

所以我们这里,换一种方法,如下:

Private Sub Class_Initialize()
    Debug.Print "我出生了!"
    Situation = "一般"
    
    Set mfrm = New Form_窗体3

End Sub

第5行代码,有没有让你惊掉下巴^_^?其实很好理解,Form_窗体3是一个类的名字,所以 New Form_窗体3 就是创建一个Form_窗体3的类的对象,让mfrm这个窗体变量指向这个类对象。于是窗体3就在内存中被创建,但是,它不会显示出来,除非你设置它的Visible属性等于True。使用这种方式打开的窗体3,不会出现在Forms()中,所以你引用Forms(“窗体3”)是会报错的。

如果你问我,我们在定义mfrm的时候,指定的类型是Access.Form,为什么我们不给mfrm指定“Form_窗体3”类型?如果你能有这种疑问,说明你是带着脑子在看我的文章^_^。这里也先按下不表。因为涉及到面向对象的多态的概念,以后再谈。

好了,内存中创建了窗体3对象,mfrm也指向了窗体3。现在我们可以用指向窗体3的mfrm对象变量,任意的调用窗体3的属性和方法了。又由于mfrm前面加了关键字WithEvents,Person类对象可以监听窗体3的任何事件了!为什么任意任何被我加粗了?我想强调的是一种自由,一种想干嘛就可以干嘛的自由。通过mfrm,窗体3现在是你的提线木偶,一切听你的指示,做上帝的感觉又来了,有木有?^_^

现在可以监听窗体3的Timer事件了吗?呃,要看情况。要看什么情况?我们知道窗体的Timer事件的触发,是需要条件的,什么条件?窗体的“计时器触发”属性和“计时器间隔”属性必须要设置正确,Timer事件才能正常被触发。我们的第一个图中,“计时器触发”属性的值是“[事件过程]”,“计时器间隔”属性的值是“2000”,都已经设置好了呀?

是的,你没有看错,上面的截图确实是这样设置好了的。但是,心脏这么重要的事情,你能交给别人去帮你设置这2个重要属性的值吗?如果万一某个人打开窗体3,把这2个属性值给改了,怎么办?所以要确保万无一失,我们使用木偶的线mfrm来设置窗体3的这2个属性:

Private Sub Class_Initialize()
    Debug.Print "我出生了!"
    Situation = "一般"
    
    Set mfrm = New Form_窗体3
    mfrm.OnTimer = "[事件过程]"
    mfrm.TimerInterval = 700
End Sub

这里,直接将计时器间隔(TimerInterval)2000的原始值,覆盖为700。

好了,现在可以为mfrm监听到的窗体3的Timer事件写事件响应代码了:

Private Sub mfrm_Timer()
    Debug.Print "我是" & Me.Name & ",现在时间是:" & Now() & ",我有脉搏和心跳了!"
End Sub

现在窗体3的Timer事件,有了2个监听者,一个是Person类对象,通过mfrm监听,另一个是窗体3本身,还记得窗体3中的 Form_Timer()代码吧?窗体3的Timer事件,只需要Person类对象来监听就好了,我们将窗体3中的Form_Timer()代码注释掉。

'窗体3
Option Compare Database
Option Explicit

'Private Sub Form_Timer()
'    Debug.Print "每隔2秒,这里就自动执行一次"
'End Sub

最后,在Person类的销毁事件中,将mfrm对象变量的指针释放掉:

Private Sub Class_Terminate()
    Set mfrm = Nothing
    Debug.Print "我挂了!"
End Sub

好了,Person类对象的心脏起搏器安装完毕。我们在模块1中写如下测试代码:

'模块1 测试类对象
Option Compare Database
Option Explicit

Sub Test()
On Error GoTo ErrHandler
    Dim objPerson As Person
    Set objPerson = New Person
    
    objPerson.Name = "赵冰冰"
    objPerson.Gender = Female
    objPerson.DOB = #2/3/1995#
    
    While True
        DoEvents
    Wend
'    objperson.Speak
'
'    MsgBox "修改前性别:" & objperson.Gender
'    objperson.Gender = Male
'    MsgBox "修改后性别:" & objperson.Gender
    
'    Debug.Print objperson.DOBtoString       '立即窗口值:1995年02月03日
'    Debug.Print objperson.Age               '立即窗口值:23
'    objperson.DOB = #6/10/1995#
'    Debug.Print objperson.DOBtoString       '立即窗口值:1995年02月03日
ExitProcedure:
    On Error Resume Next
    Set objPerson = Nothing
Exit Sub
ErrHandler:
    MsgBox "错误代码:" & Err.Number & VBA.vbCrLf & _
           "错误描述:" & Err.Description & VBA.vbCrLf & _
           "错误来源: " & Err.Source, _
           vbCritical, _
           "Test 报错"
    Resume ExitProcedure
End Sub

第14到16行代码,写了一个无限循环的语句。目的是为了让Test中定义的objPerson对象一直存活下去。DoEvents是释放线程控制权的意思。运行Test代码,你会在立即窗口中看到如下的消息:

这段测试代码,你不干预,它会永远运行下去,所以,当你听厌了赵冰冰的呼喊声后,可以点停止按钮强行停止执行。

对代码有洁癖的人,是不会就此罢休的。所以,再创建一个窗体4,在窗体4的代码窗口写如下测试代码:

'窗体4
Option Compare Database
Option Explicit

Private objPerson As Person

Private Sub Form_Load()
    Set objPerson = New Person
    objPerson.Name = "赵冰冰"
    objPerson.Gender = Female
    objPerson.DOB = #2/3/1995#
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Set objPerson = Nothing
End Sub

使用窗体4做类的代码测试的好处是,只要窗体4打开后不关闭,objPerson对象就一直存活在内存中,你就可以一直听到赵冰冰的心跳和呼喊声,直到关闭窗体4。^_^

这回的出生入死的记录就完整了^_^

最后,就不按惯例贴出所有代码了。因为有点长,窗体也比较多。直接将我的Access文件贴在这里,供大家下载。VBA 类模块系列之十三——给类对象安装心脏起搏器

下一篇应该不会跳票了,开讲自定义事件和触发自定义事件。敬请期待^_^

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注