这份礼物送给现在想学习类知识或曾经学过但因为各种原因没有“修成正果”的朋友,我期望的结果是这篇文章后,您可以在类模块中像在标准模块中写代码一样熟练,我也期望不至于太乏味而使您没有耐心看完整篇文章,或者说期望您学习的过程是轻松愉悦的,但愿我可以做到。

文章构划为8部分:
1.什么是类
2.为什么要学习类
3.类的预备知识
4.创建类属性
5.创建类方法
6.创建类事件
7.一个完整的类实例
8.未完的结尾

什么是类 
       在我们身边,相同或相似的物品无处不在,从生活用品,到工业产品,……通常它们都是由同一个可以称为“模具”的东西生产出来。
理解类,一般要先从对象谈起,但由于从不同的角度,有不同的理解,从而也有不同的关于类的定义,本文不去探讨一个完整并且公认的类概念,在这一部分后,只要你有一个基本的判断并且在遇到时知道是类就可以了。在上面的这个例子中,一个“模具”就是一个“类”,而由它生产出的每一个产品,就是一个“对象”。看下面的VBA语句:
Dim tx1 As Textbox

不用解释它的意思吧,这里我们用到一个类Textbox定义了一个对象tx1,再来看:
Dim tx1 As Textbox
Dim tx2 As Textbox

又定义了一个对象tx2,如果你不嫌烦,我还可以继续下去。对象增加了,但As后的Textbox没有变,它可以无限制的使用下去。
类是一个隐者,上面Textbox是VBA已经给我们准备好的一个类,我们无法知道VBA是怎么准备的(它的真身被隐藏了),但我们可以知道对象tx1怎么用。VBA把对对象的使用划分为三种,属性、方法和事件(后面预备知识我们再提)。

然而,Textbox是一个类,仍然不是本文要说的类,因为它是VBA已经给我们准备好了的,我们要做的,只是知道如何使用它而已。本文要介绍的,是利用VBA已经给我们提供的资源,来构建我们自己的类,姑且可称之为自定义类。这需要在VBE下,通过插入类模块,然后向类模块中写入代码来完成。这就是你常常听说的类,本文的主题就是这个包含代码的模块!通过这个模块,可以提供给我们一个和VBA提供给我们的诸如Textbox功能性质完全相同的类,然后,再由我们自己象使用Textbox一样使用!

类是一个隐者,她把自己藏在所有模块的最后,甚至在多数情况,她从不出场。现在,她挂着神秘的微笑,向你走来,你要拒绝吗?

为什么要学习类模块
      类通常被认为是学习VBA的难点之一,之所以如此,因为相对于制造一个标准模块或用户窗体,我们可以找到的类的学习资源少之又少,甚至很多VB的书籍也只有缪缪字语(在后面的预备知识,我们再提另一个重要的原因)。从技术角度上看,类的构建,不象窗体,VBA的类也不象有些语言提供了可视的设计界面,感性上那样直接,隐者!她是不可视的设计,所有的构建都是通过在类模块中写代码来实现的。

1.学习是一种兴趣的追求
      俗语云:学的千千万,用的有几何?又曰:书到用时方恨少。
林语堂先生将做学问划分为三重境界,第二重说“为伊消得人憔悴,衣带渐宽终不悔”,大多数朋友,包括本人,都不是专业程序员,学习程序只是一种兴趣和爱好,就好象有人喜欢修炼网游一样,对喜欢程序的人,不断地学习和提高,也是一种追求优秀的态度,并且乐意享受这个追求的过程。模块、控件、链接库和类构成软件工程开发的四大技术,而类技术是控件和链接库技术的基础,我们不得不学。

2.类有什么用
      如你前面看到的我们使用Textbox类,类可以创建大量性质相近的对象,减轻我们的程序量,简洁代码并提高效率。
类定义后,在其它模块中使用时,我们就可以暂时忘记或不必考虑它内部复杂的细节,让我们变得轻松,VBA虽然不能真正封装类的形式,但在这里,我们可以封装它的概念。

这并不是类的全部好处,其它的,留着朋友们用的时候慢慢体会吧。

类挂着神秘的微笑,已经走到你的大门口,开门迎接她吧,你还等什么?

广义上讲,所有VBA的知识,包括语句、函数以及为我们提供的标准类甚至第三方的资源都可以在类中被使用。本文无法也不准备逐一探讨,这里只说一些最密切最基本的,但即使这样,笔者仍然不能把这些点的知识都写到,甚至因为对问题解释清晰或符合逻辑的需要,采用非规范的表述,对专门问题的全面理解,请读者注意参考有关标准帮助文档并加以甄别。

1.从构建者的角度理解对象
      上一回我们提到,类被认为是VBA难点还有一个原因,这就是我们的思想!VBA提供了大量的现成的类,我们几乎不再需要去构建自己的类,这种结果,我们熟练地习惯了从使用者的角度去理解类—的实例:对象,包括它的属性、方法和事件。但是,现在你还要尝试做一个提供者,这和你作为使用者时的思考方法是完全不同的,甚至是革命性的。这种角色的转位是痛苦的,它需要你放弃你原本可以自豪地解释出对象以及它的属性、方法、事件的定义,它们原本是如此逻辑地被划分,如此清晰,但现在,类模块中的一切,彼此交织,你会发现它们都模糊了!是需要你忘掉所有固执的“招势”的时候了,当你心中无剑时,转位也就完成了,隐者变得清晰了,她是如此美丽。且慢,在你完全忘掉前,让我们最后再看一眼它们的样子,呵呵,如果你实在忘不掉,你就提醒一下自己构建者的身份吧。下面是通常情况下关于对象、属性、方法、事件的基本表述,如果你以前没了解过,则应当找些资料先认真地理解它们,然后再按照上面的提示去做。

对象是由类创建的一个实例,它是类的实体化。

对象的引用和操作被逻辑上划分为不重叠的三个部分:
属性是指对象的特性。以前面的Textbox为例,有长度,高度,框中显示的文字等等。
方法是指对象的某个操作。如让Textbox成为当前的焦点(即光标移动到它上面)。
事件是指对象对外部动作的响应。如我们用鼠标点击Textbox时,会产生一个Click事件,改变它的值,则产生一个Change事件

变量的作用域
      变量因为声明的位置和方式不同,从而有不同的作用域。作用域是指变量在多大范围内能被代码识别。可以划分为过程级、模块级和全局变量。
过程级变量在过程中声明,这里过程指的是一个Sub或Function,也包括后面提到到属性过程。通常用Dim或Static进行声明。Dim声明的变量,只在该过程执行时存在,过程结束,变量的值也就消失了。Static声明的变量称为静态变量,这个值在整个程序运行期间都存在。
模块级变量对整个模块的所有过程都有效,但对其它模块不可用。可以在模块顶部声明。声明模块级变量用Private关键字和直接使用Dim没有区别。但推荐使用Private进行声明,因为这样可以方便地与后面的全局变量区分开来。

全局变量是对整个VBA工程的所有过程都有效的变量,使用Public关键字在标准模块的顶部来声明。

在类模块中,对变量作用域的理解要注意下面两点:
(a)由于类是生成对象的模具,每生成一个对象,相当于产生了一个副本,这个副本就是对象的“真身”,副本间是相互独立的,从而,模块级的变量只作用于副本自身。
(b)类模块中使用Public关键字,只有当对象变量是这个类的实例时,才能被访问。

过程和函数
      变量、过程(Sub)、函数(Function)是我们在标准模块中使用的最基本的构件,在类摸块中,它们仍然是最基本和重要的角色。对于它们,你已经再熟悉不过,之所以前面还要花这么多文字,是为了突出它的重要,也是想让你放松一下,哦,我花了很短的时间已经看了这么多(我也写了这么多!)。

过程和函数并无实质的区别,当需要返回值时,就使用Function,如果不需要返回任何结果,随你的爱好,但这时推荐你使用Sub,因为这样更符合微软的本意。过程(Sub)、函数(Function)也有作用域,在标准模块中通过使用Private和Public关键字(可以省略Public关键字,因为它是默认的),可以划分为模块级和全局级,以决定它是在当前的模块有效还是整个工程有效。

集合Collection 
      Collection是我们在使用类时最常用到的对象。一个Collection对象代表一组相关的项目,虽然它的成员并不被强制要求是同一类型的的,但请记住,这通常并不能给我们带来额外的方便,相反,我们通常是用来收集同一类型的数据。
建立集合的方法和建立其它对象一样,如:
Dim col As New Collection
集合建立后,可以使用Add方法添加成员,用Remove方法删除成员,用Item方法从集合中返回特定成员。
Private Sub CommandButton1_Click()
  Dim col As New Collection
  Dim i%
  Dim ct As Control
  For Each ct In Me.Controls
    If Left(ct.Name, 7) = "TextBox" Then col.Add ct
  Next ct
  For i = col.Count To 1 Step -1
    MsgBox "下面删除成员" & col.Item(i).Name
    col.Remove i
  Next i
End Sub

上面的代码先将窗体上所有的TextBox加入到集合中,然后再删除掉。Count属性返回集合的成员数量,Remove方法后面的参数是集合成员的索引号。成员的索引号通常是按照加入的顺序自然编号,从1开始,但可以在加入时使用Add方法的参数进行改变。Add方法的完整语法是:
object.Add item[, key][, before][, after]
item 必需的。任意类型的表达式,指定要添加到集合中的成员。
key 可选的。唯一字符串表达式,指定可以使用的键字符串,代替位置索引来访问集合中的成员。
before/after 可选的。表达式,指定集合中的相对位置。
下面语句向集合增加一个对象TextBox1,并定义该成员的关键字为tx1。
col.Add TextBox1, "tx1"
然后,下面两句都可以向集合中增加一个TextBox2,并把它放在成员TextBox1的前面。
col.Add TextBox2, , col.Count
col.Add TextBox2, , "tx1"
第一句中,因为只有一个成员,所以col.Count也是索引号

使用事件的WithEvents变量
      WithEvents不是一个单独的语句,为了使用对象的事件,需要在声明该对象时使用WithEvents关键字。例如:
Dim WithEvents app As Application
将上面的语句写入ThisWorkBook的模块,可以看到在通用框中出现了一个变量app:

需要注意的是,使用WithEvents只是声明了对象变量,而并不实际生成对象,为了生成真实的对象,你仍然需要在声明后向生成其它对象一样,使用Set语句进行指定。此外,WithEvents变量不能是通用类变量如Object,而必须指定类名,也不能把WithEvents变量声明为As New。不能在标准模块中使用WithEvents.

初识类模块
     现在,请打开你的VBE,主菜单-插入-类模块。
     插入了一个类模块,也就建立了一个类。类模块的名字就是类的名字。你现在看到的,她的名字叫“类1”,这是VBA按她姐妹排行给她取的的,是的,VBA一贯如此,你早就熟悉了这种规则,现在,在标准模块或其它模块中输入Dim …As的时候,提示框中她已经出现了。但我知道,有件事你正耿耿于怀,“类1”,太没个性了,想改成自己要的名字吧。很容易,和你改标准模块的名字一样,打开属性窗口,看到了吧,第一行就是她的名字,随你的意愿修改吧。

此主题相关图片如下:

你或许已经注意到,在名字下面,只有一个属性:Instancing,其值也只有两个选项:Private和PublicNotCreatable。事实上,你完全可以忽略这个Instancing,就象你完全忽略条件编译指令一样,因为在VBA中我们几乎用不到它们,而只需维持她的默认值即可。至少我是这样认为的,但我给不了您充足的理由,而只是个人的一种狭隘经历。既然提到了,就简单说明一下:
Instancing属性决定该“类”在其它工程中是否可以被使用。我们知道,标准模块中的Public过程,可以保存在宏工作簿甚至直接被另一工作簿的工程调用,但类中的代码是不可分割的整体,所以必须整体决定是否允许外用。当Instancing属性设为Private(默认)时,不允许其它工程访问。当设置为PublicNotCreatable时,只有在自己的工程创建了该类的对象时,其它工程才允许使用这个对象,注意,仅仅是在本工程中创建的对象,而不能用她在其它工程中创建对象。

隐者已经来到你的身边,透过薄薄的面纱,你似乎已看到她神秘的微笑。站起身来,走过去吧

让我们想一下作为类的使用者时,我们是如何操作对象的属性的,对象属性的操作不外乎读和写两种。当我们要给对象的某个属性赋值时,我们会:
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类最基本最重要的部分已经给朋友们介绍完了。余下的,留着您在未来的探索路上慢慢体会吧,也请您不要忘了和大家分享您的喜悦。
隐者已向你展示了她所有的秘密,铅华去尽,只有美丽

现在,提供一道简单的测试,帮大家回顾一下前面的知识。建议您做一下,因为VBA是实践性的。

题目要求:
(一)构建两个类:
1.Student类
具有2个属性:
(1)Name:可读写。
(2)Id:可读写,但只能写一次。格式为字母S加两位整数,如S01,S02…等。

2.Students类
具有1个属性,3个方法,2个事件:
(1)Count属性:只读,返回Student成员数量。
(2)Item方法:使用下标如Stus.Item(i)的方式调用,返回相应的Student成员,i可以是Student成员的自然顺序,也可以是Student成员的Id。
(3)Add方法:增加Student成员。当增加成员时,按顺序递增生成成员的Id,每个Id号只用一次,不因删除成员受影响。
(4)Remove方法:删除Student成员。
(5)BeforeAdd事件:在增加成员前作出响应,允许用户取消增加成员。
(6)AfterRemove事件:在删除成员后响应。

(二)构建一个用户窗体测试前面的类:
1.窗体上包含四个CommandButton,分别完成如下功能:
(1)增加成员
使用InputBox输入Student成员的Name,完成增加。
(2)删除成员
使用InputBox输入Student成员的Id或自然序号,完成删除。
(3)显示学员总数。
使用MsgBox显示Student成员总数。
(4)查询学员
使用InputBox输入Student成员的Id或自然序号,然后使用MsgBox显示相应Student成员的Name。

2.一个ListView,即时显示现有的所有Student成员。

3.事件处理
(1)BeforeAdd事件,查找现有成员的Name是否有和要增加的成员的Name相同的,如有,则给出提示,让用户选择是否增加。
(2)AfterRemove事件,刷新ListView显示

(三)未尽之处自由发挥,假定用户操作规范,可不考虑错误处理。

VBA类模块完全教程(www.accessoft.com软件网)的更多相关文章

  1. VB类模块中属性的参数——VBA中Range对象的Value属性和Value2属性的一点区别

    在VB中,属性是可以有参数的,而VBA中属性使用参数非常常见.比如最常用的:Worksheet.Range("A1:A10")  VB的语法,使用参数的不一定是方法,也有可能是属性 ...

  2. VBA标准模块与类模块

    大家通过之前的介绍,已知道怎么将一个空模块插入VBA的工程中.从插入模块中可以看到,模块有有两种——标准模块与类模块.类模块是含有类定义的特殊模块,包括其属性和方法的定义.在后面会有介绍与说明. 随着 ...

  3. Excel VBA批量处理寸照名字(类模块加FSO版)

    需求:因为处理学生学籍照片,从照相馆拿回来的寸照是按班级整理好,文件名是相机编号的文件.那么处理的话,是这么一个思路,通过Excel表格打印出各班A4照片列表,让学生自行填上照片对应姓名.表格收回来后 ...

  4. VB6/VBA中跟踪鼠标移出窗体控件事件(类模块成员函数指针CHooker类应用)

    一.关于起因 前几天发了一篇博文,是关于获取VB类模块成员函数指针的内容(http://www.cnblogs.com/alexywt/p/5880993.html):今天我就发一下我的应用实例. V ...

  5. 获取VB类模块成员函数指针(转)

    最近在做一些VB6.VBA的项目,被如何获取类模块中的函数指针这个问题所困扰,收集整理后,有2分资料值得收藏,特将关键部分留存,以备后续查找. 参照连接1:http://www.cnblogs.com ...

  6. 从零开始实现asp.net MVC4框架网站的用户登录以及权限验证模块 详细教程

    从零开始实现asp.net MVC4框架网站的用户登录以及权限验证模块 详细教程   用户登录与权限验证是网站不可缺少的一部分功能,asp.net MVC4框架内置了用于实现该功能的类库,只需要简单搭 ...

  7. ansible笔记(7):常用模块之系统类模块

    ansible笔记():常用模块之系统类模块 cron模块 cron模块可以帮助我们管理远程主机中的计划任务,功能相当于crontab命令. 在了解cron模块的参数之前,先写出一些计划任务的示例,示 ...

  8. ansible笔记(8):常用模块之系统类模块(二)

    ansible笔记():常用模块之系统类模块(二) user模块 user模块可以帮助我们管理远程主机上的用户,比如创建用户.修改用户.删除用户.为用户创建密钥对等操作. 此处我们介绍一些user模块 ...

  9. ansible笔记(6):常用模块之命令类模块

    ansible笔记():常用模块之命令类模块 command模块 command模块可以帮助我们在远程主机上执行命令 注意:使用command模块在远程主机中执行命令时,不会经过远程主机的shell处 ...

  10. VB-创建类模块DLL文件

    最近需要调用MSCOMM32.OCX控件,但是ABAP调用过程中发现无法同时发送多条记录,则需调整实现方式: a.创建DLL文件封装MSCOMM控件相关属性及方法 b.系统注册DLL文件 c.ABAP ...

随机推荐

  1. Demo of canvas, canvas optimization and svg

    It used the canvas to draw the curves in the old project, and the client felt that it was vague, so ...

  2. Linux 查询 磁盘空间 系统报错:No space left on device

    报这个错误是磁盘空间不足导致的 使用   du -h -d 1 / | sort -nr 这个命令 一级一级排查

  3. 此平台不支持虚拟化的 Intel VT-x/EPT。不使用虚拟化的 Intel VT-x/EPT,是否继续?

    1.cpu虚拟化是否打开 2.Windows安全中心>设备安全性>内核隔离

  4. vulnhub:Victim01靶机

    kali:192.168.111.111 靶机:192.168.111.170 信息收集 端口扫描 nmap -A -v -sV -T5 -p- --script=http-enum 192.168. ...

  5. k8s资源清单

    资源清单就是k8s当中用来定义pod的文件,语法格式遵循yaml语法,在yaml当中可以定义控制器类型,元数据,容器端口号等等等....,也可以针对于清单对pod进行删除等操作. 我们可以用kubec ...

  6. canvas合并图片并长按保存

    代码实现 <div class="pho-bg"> <img src="../../assets/images/FeedbackActivity/pos ...

  7. Hashtable多线程遍历问题

    If a thread-safe implementation is not needed, it is recommended to use HashMap in place of code Has ...

  8. HCIP-ICT实战进阶03-OSPF高级特性

    HCIP-ICT实战进阶03-OSPF高级特性 1 ospf的快速收敛 ospf快速收敛是为了提高路由的手来你熟读而做的扩展特性, 包括PRC(Partial Route Calculation, 部 ...

  9. 本地jar包怎么导入到maven仓库中?

    1.找到你所需要的jar包 2.打开cmd找到jar包的文件夹下 3.输入安装命令实例命令 1 安装指定文件到本地仓库命令:mvn install:install-file 2 -DgroupId=& ...

  10. AJAX-动力节点

    AJAX(Asynchronous Javascript And Xml) 传统请求及缺点 传统的请求都有哪些? 直接在浏览器地址栏上输入URL. 点击超链接 提交form表单 使用JS代码发送请求 ...