Access开发培训
网站公告
·Access专家课堂QQ群号:151711184    ·Access快速开发平台下载地址及教程    ·欢迎加入Access专家课堂微信群!    ·如何快速搜索本站文章|示例|资料    
您的位置: 首页 > 技术文章 > Access数据库-模块/函数/VBA

VBA类模块完全教程(下篇)

时 间:2013-02-19 09:07:06
作 者:周芳(转)   ID:24526  城市:上海
摘 要:创建类属性 创建类方法 创建类事件
正 文:

创建类属性
      让我们想一下作为类的使用者时,我们是如何操作对象的属性的,对象属性的操作不外乎读和写两种。当我们要给对象的某个属性赋值时,我们会:
TextBox1.Text=”abc”
当我们要读取对象的属性时,
S= TextBox1.Text
现在,看看作为类的提供者需要怎样做。
我们将“类1”改名为“MyClass”并为它创建一个名称为x的字符型属性。
1.使用Public变量创建类属性
在类模块中写下行代码:
Public x$
是的,就这么简单,通常情况下,只需要这么简单

 

使用Property过程创建类属性
Private s$
Public Property Get x() As String
  x = s
End Property
Public Property Let x(ByVal c As String)
  s = c
End Property

      我们可以省去上面默认的Public。但看上去还是有点麻烦哦,不仅需要两个公共过程,而且还要一个辅助的私有变量s和一个参数c。在类模块中,Property过程把对属性的读写分开了,说一下Property过程的工作机制,当标准模块中的代码读取对象的属性时,便会触发存在的Property Get过程,或者说Property Get过程提供了属性的读功能,同样,Property Let过程提供了写属性。这样,上面的两个过程(当然在模块中没有先后的要求),可以只有一个,或者虽然两个都有,但却不全是Public,从而提供出去的属性是只读或只写(呵呵,没见过只写哈)。仅仅是为了提供只读或只写的属性,代码就从一行变成了七行?!这样的理由,你不会信服, VBA中的类通常是提供给我们自己使用的!如果它确实是只读的,我们自觉地去只读就是了!我们使用Property过程还有其它理由,最基本的一条,我们可以利用这个“过程”来做我们想做的事。看一看:

Public Property Let x(ByVal c As String)
  s = Format(c, "0000")
End Property

这里我们只是简单的利用了一下,更多的在后面你会看到。此外,谁会保证有一天你不使用VB给别人提供类呢,这个技术可是通用的。提供一段标准模块的测试代码,来看看我们上面构建的类属性,你自己试试吧。

Sub aTest()
  Dim mc As New MyClass
  mc.x = "123"
  Debug.Print mc.x
End Sub

就象我们给普通变量和对象变量赋值的方式不同一样,对象变量是使用Set赋值的。对“对象”属性,VBA提供了Property Set来代替构建“普通”属性使用的Property Let。来看一段代码:
 
Private tx As Object
Property Get x() As Object
  Set x = tx
End Property
Property Set x(ByVal o As Object)
  Set tx = o
End Property
和前面的比较一下,除了多一个Set,实在没有什么不同。

告诉你一个小秘诀,你可以按照Function去记住Property Get的用法,按照Sub去记住Property Let /Set

 

属性的初始值
      我们常常希望,当一个对象建立的时候,它的某些属性会被自动赋予一个初始值,这样,对具有最常见的属性值的对象可以减少重复性的赋值工作。这需要借助于类的构建函数来完成。


      在类模块代码窗口的“通用”框中点击向下的小三角箭头,选择“Class”,右面声明框中可以看到两个选项,“Initialize”和“Terminate”,我们对它们应该不陌生,很多对象都有这两个事件,Initialize事件当对象建立时发生,Terminate事件在对象对释放时发生。由于类是静态存在的,它并不是真正的对象,所以在类模块中,它们通常被称为构建函数和析构函数,或构建过程和析构过程。对它们的理解和你在对象中的用法并没有什么不同。当一个对象被建立时,构建函数将被首先执行,同样,当对象释放后,将执行析构函数。
 
下面建立MyClass,属性x初始值为”0001”的全部测试代码:
[类模块MyClass的代码]
Option Explicit
Private s$
Public Property Get x() As String
  x = s
End Property
Public Property Let x(ByVal c As String)
  s = c
End Property
Private Sub Class_Initialize()
  s = "0001"
End Sub
[标准模块1的代码]
Option Explicit
Sub aTest()
  Dim mc As New MyClass
  Debug.Print mc.x
End Sub
隐者为你揭开了第一层面纱,你隐约已看到她美丽的面厐,虽然还不是很清晰,但你知道,早晚会的

 

创建类方法
      放松一下,请拿出你家的紫砂壶,泡上一壶好茶,听我给你将类的方法的故事,你的茶品完了,我的故事也差不多就讲完了。
1.构建类的方法其实就是在类模块中写公共的Sub和Function
现在我们给前面提到的MyClass创建一个方法PutIntoActiveCell,功能是将x属性值写入活动单元格。
Public x$
Sub PutIntoActiveCell()
  ActiveCell = x
End Sub
在标准模块中用下面的代码测试一下:
Sub aTest()
  Dim mc As New MyClass
  mc.x = "abc"
  mc.PutIntoActiveCell
End Sub
这是本回要告诉你的全部吗?你还没有开始品茶吧?就这样了结束?这是最重要和基本的,但却不是全部。
你是否有一种感觉,但你不能清楚地说出来? 端起你可爱的茶杯,品一口茶,我们继续。

 

类的方法环境
      借用广为众知的一个名词“数据环境”,虽然不准确,但我实在想不出更好的称谓来代替,姑且这么叫吧。稍后你就会知道它的含义。


      类可以象VBA提供给我们的很多标准类一样风光无限,所有的程序设计者都在工程中使用它,但更多时候,我们所构建的类只在特定的环境下被使用,类的方法环境是指包括类所在工程的其它成员在内的,可以调用的资源的集合。工作簿、工作表、窗体或其它,在类模块中,你可以象在标准模块中一样操作它们,千万不要因为换成了类模块而产生任何疑虑,作为类的创建者,你要让类模块中的代码象你在标准模块中一样亲近它们,只要你认为必要。脱离了方法环境的、谨小慎微的、封闭的类实在没有什么意义。如果你预期方法环境在运行时可能会有变化,你要事先预知它们并象在标准模块中一样使用恰当的措施,比如你不能确定运行时活动工作表的名称(但你确定届时会是一个工作表),你可以使用ActiveSheet。


      我反复说“和标准模块一样”,就是想告诉你在类模块中创建方法时,对工程中其它成员的操作,和你已经熟悉的标准模块中的方式的实在没有什么不同,这一原则适用于类模块中所有代码(也许叫代码环境更准确些),而不仅仅是构建方法的代码。

      现在,你知道了,你刚才的感觉到的是开放的方法环境。是的,以后你会更深地体会到,作为好的提供者,开放的思维有多重要

方法的兄弟—成员事件
      类方法的执行需要在代码中以显性的方式指定,象上面的mc.PutIntoActiveCell,有时候,当最终操作者触发类对象成员(属性)的某个事件,需要在事件发生时产生一系列的操作,这时,我们要运用成员事件。成员事件和方法都是类提供的一系列代码的操作,倆兄弟的区别在于,成员事件无法也不必再由代码显性调用。
我们来看一个具有普遍意义的事例。


[重要例]
窗体UserForm1上有5个CommandButton控件(名称分别为默认CommandButton 1- CommandButton 5)和1个TextBox控件(名称为TextBox1)。要求当各个CommandButton控件被点击时,它的按钮文字(Caption)会写入TextBox1。
如果不用类,我们需要为5个CommandButton控件分别写5个相同的Click事件代码。如:
Private Sub CommandButton 1_Click()
  TextBox1 = CommandButton 1.Caption
End Sub
下面是用类的成员事件方法的代码:
‘类模块Cmds的代码
Option Explicit
Public WithEvents cmd As CommandButton
Private Sub cmd_Click()
  UserForm1.TextBox1 = cmd.Caption
End Sub
‘窗体UserForm1的代码
Option Explicit
Dim co As New Collection
Private Sub UserForm_Initialize()
  Dim i%
  Dim myc As Cmds
  For i = 1 To 5
    Set myc = New Cmds
    Set myc.cmd = Me.Controls("CommandButton" & i)
    co.Add myc
  Next i
  Set myc = Nothing
End Sub

 

      仔细玩味上例的每一行代码,直至品完你壶中的茶。呵呵,因为它实在很有用。最后提一下Friend关键字,虽然在VBA中几乎没有什么用,但如果有一天你要制作ActiveX部件,可能会用到它。之所以要有Friend关键字,是因为类的私有部分在类模块外是不可见的,但有时却需要从外面访问这些私有部分,这时,可以使用Friend关键字使属性和方法成为“友元成员”。友元成员在本工程中相当于Public,但在工程外,它仍是Private 。


      隐者为你揭去了第二层面纱,你几乎已看清她美丽的面庞,她带着甜蜜的微笑,似乎在问:什么才是最美的期待?


创建类事件
      在VBA中,因为我们既是提供者,也是使用者,所以通过良好地构建类的属性和方法,已可以满足我们需要全部的要求。我不再去解释这个观点,在本回后你自然会明白。从这个意义上讲,创建类事件实在没有必要。唯一的遗憾是,我们没有体会到作为创建者的全部乐趣,标准类给我们提供了各种事件,当然希望自己也可以做到,想象中这应当是一件激动人心的事,所以,追求快乐是创建类事件的重要理由,另一个理由,前面已经提到。

 
      回到前面我们的MyClass类,我们将x属性改名为Value属性,虽然对属性、方法以及事件的命名,VBA没有特别的限制,但建议您不要象我前面那样,随便取一个x,可能的话,要尽量和标准类的成员(属性、方法以及事件)名称相一致。


      现在我们为“使用”者提供一个“Change”事件,不错,我们给它取名为“Change”,而不再是随意的“y”或其它(虽然也可以),这样,我也不用解释这个事件的用意了,呵呵。为了做到这一点,看看我们应该做什么。

 

1.第一步:使用Event语句声明事件
看一下类模块中现在的代码:
Option Explicit
Public Event Change(ByRef Cancel As Boolean)
Private s$
Public Property Get Value() As String
  Value = s
End Property
Public Property Let Value(ByVal c As String)
  s = c
End Property
Private Sub Class_Initialize()
  s = "abc"  ‘初始值
End Sub
和前面的代码比较,多出了一句:
Public Event Change(ByRef Cancel As Boolean)

 

这就是Event语句,只此一句,我们已经为我们的类声明(我想使用“注册”一词是不是更妥切)了一个事件Change。在看Event语句产生的效果前,先来看它的特性:
(1)为了声明事件,Event总是Public的,这好理解吧。
(2)事件可以不带参数,如Public Event Change(),也可以带参数,如我们上面给出的,但参数不能是命名参数,可选参数或数组参数。这里我只解释一下命名参数的含义。我们知道,事件可以因特定的用户事件而触发,也可以在代码中象方法一样指定执行,如下面的CommandButton1_Click:
Private Sub CommandButton2_Click()
  CommandButton1_Click
End Sub
但在调用对象的方法时我们通常喜欢这样的方式:
Selection.Sort Key1:=Range("A2"), order1:=xlAscending
这里Key1、Order1就是命名参数,命名参数的好处是我们不必记住它们的次序,调用时直接以名称和冒号后加等于号指定它的值,但对事件的调用却不允许这样。
(3)事件没有返回值。
 
现在我们看一下,Event为我们做了什么。
建立一窗体UserForm1,添加一个TextBox控件(名称为TextBox1),两个CommandButton控件(名称为CommandButton1和CommandButton2),CommandButton1的Caption设置为“赋值”,CommandButton2的Caption设置为“读值”,窗体的代码如下:
Option Explicit
Dim WithEvents mc As MyClass
Private Sub CommandButton1_Click()
  mc.Value = TextBox1 '赋值
End Sub
Private Sub CommandButton2_Click()
  MsgBox "mc当前的值为" & mc.Value '读值
End Sub
Private Sub UserForm_Initialize()
  Set mc = New MyClass
End Sub
    上面这段代码实现的是,当点击CommandButton1时便会将TextBox1的值赋给mc的Value,当点击CommandButton2时便会显示mc当前的Value值。
来运行一下这个窗体,先点击CommandButton2,此时显示“abc”,是mc的初始值,然后在TextBox1输入“123”,点击CommandButton1,再点击CommandButton2,显示“123”,说明赋值成功了。
    呵呵,忘了,我们要做什么了!现在,请从UserForm1代码窗口的“通用”框中选择mc,哇!我们声明的事件在右边“声明”框中已经出现了!
此主题相关图片如下:

我们定义这个事件是希望当mc的值改变时响应的,现在就迫不及待地给它写一句代码吧:
Private Sub mc_Change(ByRef Cancel As Boolean)
  If MsgBox("要改变mc的值吗?", vbYesNo) = vbNo Then Cancel = True
End Sub
    上面这句代码你不会陌生吧,希望当用户选择了在改变时给用户一个确认的机会。
但是,现在点击CommandButton1,却不会给你选择的机会,我们还有一步没有做。
第二步:使用RaiseEvent语句引发事件
声明了事件后,我们要做的,便是找到所有与事件发生关联的地方,使用RaiseEvent语句引发事件,这里引发的含义相当于Call,就是调用用户在事件中写的代码。在本例中,只有一个地方,就是Property Let Value过程中:
  Dim chyn As Boolean
  RaiseEvent Change(chyn)
  If chyn Then Exit Property
通过传递回的chyn,决定是否执行后面的赋值语句。下面就是添加了RaiseEvents语句后的类模块的代码:
Option Explicit
Public Event Change(ByRef Cancel As Boolean)
Private s$
Public Property Get Value() As String
  Value = s
End Property
Public Property Let Value(ByVal c As String)
  Dim chyn As Boolean
  RaiseEvent Change(chyn)
  If chyn Then Exit Property
  s = c
End Property
Private Sub Class_Initialize()
  s = "abc"
End Sub
现在你可以去运行你的窗体了,我们要的效果应该是达到了吧。为了便于你调试,下面给出窗体的全部代码:
Option Explicit
Dim WithEvents mc As MyClass
Private Sub CommandButton1_Click()
  mc.Value = TextBox1 '赋值
End Sub
Private Sub CommandButton2_Click()
  MsgBox "mc当前的值为" & mc.Value '读值
End Sub
Private Sub UserForm_Initialize()
  Set mc = New MyClass
End Sub
Private Sub mc_Change(ByRef Cancel As Boolean)
  If MsgBox("要改变mc的值吗?", vbYesNo) = vbNo Then Cancel = True
End Sub

 

    当然,我们可以把上面mc_Change的代码要做的直接在Property Let Value过程的代码中,从而不使用事件。这就是在本回的开头说的。

 
      事件的构建已经完成,说了这么多,其实你只要记住两步的标题就可以了。到这里,关于VBA类最基本最重要的部分已经给朋友们介绍完了。余下的,留着您在未来的探索路上慢慢体会吧,也请您不要忘了和大家分享您的喜悦。
隐者已向你展示了她所有的秘密,铅华去尽,只有美丽

 



Access软件网QQ交流群 (群号:54525238)       Access源码网店

常见问答:

技术分类:

相关资源:

专栏作家

关于我们 | 服务条款 | 在线投稿 | 友情链接 | 网站统计 | 网站帮助