-
2020-06-24 09:17:09
写过易语言模块的都知道,我们可以建立类模块也可以建立程序集模块,那么二者有何不同,听小编总结如下。
1、同样的功能 用类模块和程序集 都可以实现。
2、类的程序集变量(也称类成员)同一个变量(类型就是这个类的名称)是独立的。
3、程序集,是子程序的集合,是窗口组件的事件及你自己定义的子程序所在的地方。
4、类模块,是一个模型,它有自己的成员及方法,只能在你的程序集中调用类的方法才能实现你要的功能。
5、多线程时每条线程内部如果是不同的变量(类型是这个类的名称),那么他们的程序集变量(即类成员)是独立的,不会发生交错,如果是程序集的话程序集变量在多线程中是公用的,类似与全局变量,多线程读写程序集变量需要加许可证。
6、基于以上理解,当我们自己写一个大漠多线程的模块时,还是用类模块效果好。7、类模块里的成员和方法可以设为私有成员,或者方法不公开,相对是安全的。
8、当你的程序调用了很多别人写的模块,很可能有重复的方程函数名称,或者变量,数据类型,而类模块的调用就避免了这些。
总结,我们尽量推荐使用类模块。
更多相关内容 -
VBA 类模块理解和使用总结
2021-04-19 18:52:59VBA 类模块理解和使用总结 一、类的概念 记得有人总结,VBA是基于面向对象(OOP)的编程语言,而java是完全面向对象的编程语言,为了更好地理解面向对象,去学习了一下java,现在回过来看VBA的面向对象特性就比较好...VBA 类模块理解和使用总结 一、类的概念
记得有人总结,VBA是基于面向对象(OOP)的编程语言,而 java是完全面向对象的编程语言,为了更好地理解面向对象编程,去学习了一下 java基础知识,现在回过来看VBA的面向对象特性就比较好理解,只是两者语法相差甚远。
刚开始时,如何理解“对象”这个概念也是个问题,在面向对象编程中使用的单词是“object”,查字典发现其含义是 “物体;物品;东西;(极欲得到、研究、注意等的)对象;宗旨;目的;目标”,才知道对象就是物体、东西或研究的对象,我们中文翻译成对象。
要理解对象,还必须要了解“类”这个概念。我们可以从生活中理解一下,如果我们到超市购买一些饼干,可能是圆形或星型等各种形状,上面有许多好看的花纹或图案,这在流水线上生产时肯定有相应的模具的才能生成这种饼干,这个模具就是类。
其实面向对象编程中的类就是对某种一个个具体对象进行一定抽象而形成的概念。例如,现实世界上有各种各样的人——男人、女人、白人、有色人、大人、小孩等等,通过一定的抽象为人类,如果往更高一层抽象可以是动物。同样的还有狗类,汽车类……,这些类的概念就是面向对象编程中的需要定义的类,然后可以利用该类创建具体的对象,其属性值可以相同或不同。我们在抽象成一个类时,编程所关注的无非是对象的部分属性和方法,例如学生类(Student),我们关注的属性可能有:学号、姓名、出生日期、籍贯、班级等,还有很多属性我们可能不需要关注而已,例如是否双眼皮、头上是否有两个旋等。
在VBA中,已经定义了许许多多的类和对象供大家使用,例如Excel中的工作簿(workbook)、工作表(worksheet)、单元格(range或cell)、行(row)等等。一般我们可能不需要自定义类就可以实现所需的功能,但是如果理解类和对象,并加以运用,可能会更好。
二、类的定义
类的定义在不同的语言中是不同的,这里先给出java语言中类的定义:
java 类定义的示例:
public class Person { public string name; // 声明字符串类型的变量name,用于保存姓名,是对象的姓名属性 private int age; // 声明int类型的变量age,用于保存年龄,是对象的年龄属性,int就是VBA中的Integer public void setAge(int age){ // 间接对属性age赋值的方法,并且进行一些控制 if(age <= 0 || age > 130){ throw new RuntimeException("年龄范围不合法"); } this.age= age; } public int getAge(){ // 间接获取age的值 return this.age; } // 定义方法 speak() 说话 public void speak() { System.out.println("我姓名是" + name + "今年" + age + "岁了!"); } }
class是关键字,其中文含义就是“类”,Person就是类名,业内约定首字母大写以表示类,从而与其他名称相区别。这个类中声明了两个属性和一个方法。其中name属性前面使用了修饰符public,其含义是公共的,外面可以直接访问该成员,起不到封装作用;而age属性前面使用了 private(私有的)修饰符,这样外面不能直接访问,所以要定义 setter、getter方法,提供对该属性进行赋值或获取,也可以在方法内进行一些控制,起到了类的封装作用;当然也可以只提供getter方法,就能实现只读功能。
一般类具有三大特征:封装、继承和多态。
-
关于封装,上面的示例主要体现了封装特性,就好像我们买来的电视机,外面都是有材料包裹的,你看不到内部细节,但是会留出几个接口供你使用,你可以使用遥控器或直接按压按钮操作接口。
-
关于继承,可以这样理解,你父亲有些财产(属性),也有一些秘诀(方法),你都能继承(如果不是父类私有的),这时父亲就是父类,你就是子类,子类继承父类。在面向对象编程中,始终有一个公共的父类object,任何系统提供的类和自定义的类都会默认继承这个object类,这点VBA中也是如此。
-
关于多态,子类继承了父类,那么,在子类基层上创建的对象都可以赋值给父类,就好像父亲可以代表子女处理他们的事件。具体代码在此不表。
VBA中类的定义
在VBA中也有类似的概念,用面向对象编程的术语,类为基于它创建的所有对象定义了属性(Properties),方法(methods)和事件(Events),其中的属性、方法和事件统称为类成员。到目前为止,我使用过vba中的类的封装和多态,还没有特别使用过vba的类的继承,是可以继承的!下面自定义类的两个事件就是继承而来的。
注意在VBA中要定义类比较特殊,首先需要插入一个类模块,这个类模块就是一个类,此时出现名称为“类1”的模块,我们把“类1”的名称更改为 Student(就是类名),在右侧的代码区就可以声明属性,定义方法等。这样就相当于java类的定义了。
例如: Excel中的“学生明细”表的数据格式(为了简单,字段和记录少点,实际工作上可能较多)如下:
这时,在VBE中可以插入一个类模块,并把名称改为 Student:
在右侧代码区输入:
' 类模块代码区 Public id As Integer ' 唯一代表学生的学号,因为姓名可能会重名, 声明 id 为整数 Public name As String ' 姓名,声明name 为字符串,public代表公共的,表示创建的对象这个属性可以直接访问和修改 Private mBirthday As Date ' 出生日期,声明为日期, m表示me,由于没有this.birthday这种形式,只能在变量名上做文章 Public Property Let birthday(aBirthday As Date) ' aBirthday中第一字符a表示为参数argument If aBirthday < DateAdd("yyyy", -130, Date) Or aBirthday >= Date Then Then Err.Raise 5555, "Student Class module", "年龄范围不合法" ' 如果不符合要求,抛出一个自定义异常 Else mBirthday = aBirthday End If End Property Public Property Get birthday() As Date ' 属性的getter方法,Property 中文含义是 “属性” birthday = mBirthday End Property Public Property Get age() As Integer ' 属性的getter方法(这里没有使用set ,如果要赋值的是对象的,把Get换成Set) age = DateDiff("yyyy", mBirthday, Date) End Property Public Sub speak() ' 这个方法在后面没有使用,类中可以定义而已 MsgBox "我姓名是" & name & "今年" & age & "岁了!" End Sub
这样就在VBA中定义了学生类,其中实现了类的部分封装特性。对于需要封装的属性,需要对多个变量名进行区分,例如:name、mName和aName,这点在java或python比较方便。
属性方法有三个:
Public Property Let|Set 方法名 ( 参数 as 类型) Public Property Get 方法名() As 返回类型 '其中Let和Set选一个,依据是传递过来的参数类型是否是对象,如果是使用Set,否则,使用Let。
然后,插入模块(普通模块)进行测试,由于以下示例中使用了字典对象,想要使用该工具需要通过菜单 工具–> 引用后才可用,可以参考 Excel VBA 中使用集合和字典对比总结 :
代码区输入如下代码,进入调试模式,可以看到所有的学生对象已经进入字典中:
在VBA编程中,对于处理这种多条记录的,我一般会在类模块中先定义一个类,用以保存表中各条记录的各个字段内容,类属性名对应于excel表中的字段名,不想直接使用数组编程,而是利用数组获取表中的记录,然后把各条记录的内容逐个赋值给基于定义类的创建的对象属性中,然后把对象逐个保存到字典中后使用。这是因为直接采用数组,在编程中那个位置对应什么属性不直观,使用对象的属性来记录数据直观,利于理解和今后的维护。例如在编程中看到 student.name 与 arrStudent(i,2) 两种不同的表示形式,喜欢哪一个?!' 普通模块 Public Sub testClass() Dim i As Integer Dim iLastRow as Integer ' 保存最后一行的行号,i 表示 integer Dim arrStudent As Variant ' 保存所有学生记录的数组,arr 表示 array Dim objStudent As Student ' 学生对象,就是Student类的实例,obj 表示 object Dim dicStudent As New Dictionary ' 字典,保存所有学生对象, dic 表示dictionary 'Dim dicStudent as Object ' 这样声明也是可以的, 然后可以把实例化的字典对象的引用赋值给这个变量,就是使用了vba中类的多态! With Sheets("学生明细") iLastRow = .Range("A" & .Cells.Rows.Count).End(xlUp).Row ' 假设一定有学生记录, 找到最后一行的行号 arrStudent = .Range("A2:C" & iLastRow ).Value ' 把所有的学生记录保存到数组中 End With For i = 1 To UBound(arrStudent) Set objStudent = New Student ' 创建一个Student类的对象,又称类的实例化,并把进行引用赋值,注意要使用set关键字! objStudent.id = arrStudent(i, 1) ' 把对应的学号赋值给对象的属性id上 objStudent.Name = arrStudent(i, 2) ' 把对应的姓名赋值给对象的属性name上 objStudent.birthday = arrStudent(i, 3) ' 把对应的出生日期赋值给对象的属性birthday 上,其实这时对象的age属性也有了 set dicStudent(objStudent.id) = objStudent ' 把这个对象存入字典这样的集合中 Next i End Sub ' 备注: ' 我认为利用 对象 来保存 记录,然后全部存入 字典 中,这样的联合使用比较好。 ' Dim dicStudent As New Dictionary , Set objStudent = New Student 这两条语句中第一语句是在声明同时进行字典的实例化, ' 而后一句,是实例化学生对象,然后把对象的引用(地址)赋值给 objStudent 变量, 注意对象引用的赋值需要set关键字! ' 这样在后续编程中,可以遍历字典,取出学生对象就可以使用其属性,例如: objStudent.id, objStudent.name 等。
三、类详细定义
前面我们已经看到了类的一些基本定义,下面详细了解一下类定义中的细节。下面把大部分属性设置成私有的,需要通过属性方法赋值和获取,实现类的封装特性。
在我们插入类模块时,VBA默认继承了2个事件:初始化事件(Class_Initialize),销毁事件(Class_Terminate)。我们可以从代码区上侧看到:
Class_Initialize初始化事件(构造方法),顾名思义,是在类实例化的时候自动触发,就在执行set objStudent = New Student这条语句的时候触发。类的初始化事件是我们初始化类属性的最佳场合,例如可以在该方法内,设置一些默认值。而销毁事件(Class_Terminate)用于在销毁对象时,把一些资源予以清理。但是这种事件不能传递参数,即不能在实例化时传递参数。
下面给出比较详细的定义:
'Student 类代码 Option Explicit '变量要求显示声明才能使用 ' 类模块代码区 Public id As Integer ' 唯一代表学生的学号,因为姓名可能会重名, 声明 id 为整数 Private mName As String ' 姓名,声明name 为字符串,public代表公共的,表示创建的对象这个属性可以直接访问和修改 Private mBirthday As Date ' 出生日期,声明为日期, m表示me,由于没有this.birthday这种形式,只能在变量名上做文章 Private mGender As enuGender ' 性别,设置为枚举类型 Public Enum enuGender ' 对性别进行枚举类型(Enumeration)声明 Female = 0 ' 女 Male = 1 ' 男 End Enum Private Sub Class_Initialize() ' 构造方法,就是在实例化时该方法会被触发 Debug.Print "对象创建初始化!" ' 这种特殊的方法是事件,不能传递参数 mGender = enuGender.Male ' 可以设置默认值,例如性别默认为男 End Sub Private Sub Class_Terminate() ' 析构时间,对象被销毁时,该方法会被触发 Debug.Print "对象被销毁了!" End Sub Public Property Let name(aName As String) ' 姓名name属性的setter方法,可以控制赋值过程 If Len(aName) <= 4 Then mName = aName Else Err.Raise 5555, "Student Class module", "姓名不能超过4个字符" End If End Property Public Property Get name() As String ' 姓名属性的getter方法, name = mName End Property Public Property Let birthday(aBirthday As Date) ' aBirthday中第一字符a表示为参数argument If aBirthday < DateAdd("yyyy", -130, Date) Or aBirthday >= Date Then Err.Raise 5555, "Student Class module", "年龄范围不合法" ' 如果不符合要求,抛出一个自定义异常 Else mBirthday = aBirthday End If End Property Public Property Get birthday() As Date ' 生日属性的getter方法 birthday = mBirthday End Property Public Property Get age() As Integer ' 生日属性的getter方法(这里没有使用set ,如果要赋值的是对象的,把Get换成Set) age = DateDiff("yyyy", mBirthday, Date) End Property Public Property Get gender() As enuGender gender = mGender End Property Public Property Let gender(aGender As enuGender) Static blnFlag As Boolean If blnFlag = False Then blnFlag = True mGender = aGender Else Err.Raise 5555, "Student Class module", "性别一旦设定,就无法修改" End If End Property Public Sub speak() ' 类中定义普通方法 MsgBox "我姓名是 " & mName & " 今年 " & age & " 岁了!" End Sub
关于封装
以上Student类中除了id属性外,其他属性都予以封装。性别属性使用了枚举类型,便于编程时可以自动提示,更加方便和专业。同时,为了防止对性别任意修改,只允许赋值一次,这里使用了 static 修饰符,能够确保在excel未关闭的前提下,不能进行第二次修改。姓名属性要求输入的字符个数必须大于4个,否则,抛出一个自定义异常。出生日期属性要求输入的日期必须符合一定的要求,使产生的年龄在1~130之间。正是有了属性方法,才能实现对赋值的检查,以及实现输入出生日期,就可以得到年龄属性值。对象编程中的一个思想是,对象本身的属性或方法,由对象自身来完成,例如年龄的计算在类中完成。
用于封装的属性方法是比较特殊的:按理说,既然是方法,那么在使用时应该使用方法调用,例如:
objStudent.speak ' vba中普通方法的调用可以不加小括号,后面加空格后直接提供参数(如果有参数时)
而属性方法的使用就不一样,就好像不是方法,而是属性,例如:
objStudent.gender = male
关于多态
前面提到了字典对象的使用,在声明时可以使用以下方式,先把变量dic定义为对象的老祖宗 object 类型,再使用CreateObject方式创建字典对象,然后把字典的引用赋值给变量,在这个过程中,其实是使用了对象的多态特性。object类型的变量被赋值了具体的对象的引用。
dim dic as object ' 声明dic为object类型 set dic = CreateObject("scripting.dictionary") ' 创建一个字典对象,并赋值给 变量 dic
前面使用了这样的声明:
Dim arrStudent() As Variant ' 声明一个数组
这里的 Variant 是一种数据类型,是未明确声明为某一其他类型(例如,integer、string等)的所有变量的数据类型。Variant 是一个特殊数据类型,它包含除固定长度 String 数据以外的任何类型的数据(包含现在支持用户定义的类型)。Variant 还可以包含特殊值 Empty、Error、Nothing 和 Null。 可以使用 VarType 函数或 TypeName 函数来确定如何对待 Variant 中的数据。这种方法的使用是否有点类似于多态,不过VBA不使用这种说法。
关于异常
大家应该遇到过,例如出现 2 / 0 ,就会抛出一个除数是0的异常。自定义异常非常简单,具体提示为:
Err.Raise Number:=vbObjectError + 513, Source:="Student Class module", Description:="年龄范围不合法"
解释:
- Err 异常对象, .Raise 异常对象的方法,借用java概念就是抛出
- Number: 异常的编号。给错误编号为vbObjectError+513,为什么弄这么一个奇怪的编号?因为vbObjectError+512之前的号码都被占用了。vbObjectError是一个系统常量,通过语句 Debug.Print vbObjectError 打印出来的是 -2147221504 。
- Source: 异常来源于哪里
- Description: 异常描述
测试:
' 普通模块 Public Sub testStu() Dim objStudent As New Student ' 声明一个学生类对象 ' Dim objStudent As Student ' 使用这样声明可以 ' Set objStudent = New Student ' 实例化对象 objStudent.birthday = #1/1/1966# ' 给对象的出生日期属性赋值 objStudent.id = 1001 ' 给对象的学号赋值 objStudent.name = "john" ' 给对象的姓名赋值 objStudent.speak ' 调用对象的普通方法 End Sub
运行这段代码,就会跳出一个:
在调试模式下,可以看到对象的属性值:关于自定义事件
前面示例了继承自object类的两个事件,下面演示如何进行自定义事件,从代码上看是出乎意料的简单。在VBE中插入一个类模块,并更名为 TimerState。
在右侧代码区域输入:Option Explicit Public Event UpdateTime(ByVal dblJump As Double) ' 声明自定义事件UpdateTime Public Event ChangeText() ' 声明自定义事件ChangeText Public Sub TimerTask(ByVal Duration As Double) ' 时间任务方法,参数是 用时 Dim dblStart As Double ' 开始时间 Dim dblSoFar As Double ' 到目前的时间 dblStart = Timer ' 记住开始的时间值 dblSoFar = dblStart ' 保存到目前的时间值 Do While Timer < dblStart + Duration ' 当前时间小于开始时间加上需要的用时,执行以下代码 If Timer - dblSoFar >= 1 Then ' 如果当前时间与过去的时间相差大于定于1 dblSoFar = dblSoFar + 1 ' 到目前的时间加1 RaiseEvent UpdateTime(Timer - dblStart) ' 触发 UpdateTime 更新时间的方法,参数是当前时间与开始时间的差值 End If Loop RaiseEvent ChangeText ' 触发 ChangeText 改变文体的方法 End Sub
从语法上看,自定义事件的关键字是 Event,以上代码定义了两个事件,其基本语法:
[ Public ] Event procedurename [ (arglist) ]
public 是可选的,procedurename 必需的,是事件的名称(过程名),后面是参数列举(如果有参数的话)。
然后,插入用户窗体Form1,并在上面设置一个标签Label1,两个文本框,Text1和Text2,一个命令按钮Cammand1,以上控件需要在属性窗口中的(名称)中设置成以上的名称。
右键查看代码,输入以下代码:Option Explicit Private WithEvents mText As TimerState ' mText 声明为 Timerstate 事件类型 Private Sub Command1_Click() ' 命令按钮点击事件 Text1.Text = "跑步用时" ' 第一个文本框 显示 信息 Text2.Text = "0" ' 第二个文本框 显示 信息 Call mText.TimerTask(9.84) ' 调用对象mText相应的时间任务方法,传入 9.84 秒参数 End Sub Private Sub UserForm_Initialize() ' 窗体初始化 Command1.Caption = "点击开始计时" ' 命令按钮上显示文字 Text1.Text = "" ' 两个文本框初始化为空字符串 Text2.Text = "" Label1.Caption = "最快100米跑步用时(单位:秒):" ' 标签上显示的文字 Set mText = New TimerState ' 创建TimerState类型的对象并赋值引用(地址)给mText End Sub Private Sub mText_ChangeText() ' mText对象的文本改变事件 Text1.Text = "计时结束" Text2.Text = "9.84" End Sub Private Sub mText_UpdateTime(ByVal dblJump As Double) ' mText对象的更新时间事件 Text2.Text = Str(Format(dblJump, "0")) ' 格式化时间为字符串,并显示在第二文本框中 DoEvents ' 交出执行控制权,以便操作系统能够处理其他事件 End Sub
以上语句中(Private WithEvents mText As TimerState)就是进行变量类型声明,这里使用了 WithEvents 关键字,其含义是同时带上事件,如果没有使用该关键字,TimerState类中的事件不能使用。
Private WithEvents mText As TimerState ' mText 声明为 Timerstate 事件类型
如果在声明时不带关键字 WithEvents ,则没有相应的事件。以上代码使用了关键字 DoEvents ,其含义是 交出执行控制权,以便操作系统能够处理其他事件
DoEvents ' 交出执行控制权,以便操作系统能够处理其他事件
启动程序后,出现以下界面:
四、结论:
VBA的类的定义和使用对于编程帮助较大,建议予以使用。只是各种语言的语法各异,这里也是给自己进行记录,便于今后查询使用。
-
-
VBA类模块介绍
2021-04-27 01:42:37vba类模块创建,属性、方法、事件使用同其他编程语言一样,VBA有可以创建类对象,VBA的类对象有自己的属性、方法、事件
公共变量和私有变量的区别
- 公共变量:可以跨模块调用,使用public关键词,在模块最上方声明
- 私有变量:只能在本模块类使用,使用private关键词定义,在模块最上方声明
- 函数、过程未使用关键词声明,默认为public,可以跨模块使用
public i as long ' 声明公共变量,不同模块都可以调用 private num as byte ' 声明私有变量,只在当前模块内可以使用 ' 未带关键词声明,等同于public声明 Sub public_test() MsgBox "未带关键词,默认为public,可以跨模块调用该方法" End Sub ' 使用private声明,只能在当前模块使用 Private Sub private_test() MsgBox "使用private私有定义,只能在当前模块使用" End Sub
插入类模块
打开VBE窗口,点击插入-插入类模块,win系统界面可能会有些许区别(win快捷键:alt+f11)
面板左下方,可以修改类命名,这里我们修改类命名为Person
类的属性
- 直接在类模块声明变量:可随便修改
- 使用关键词Property定义类属性:可设置只读,修改有效性验证等
外部过程修改属性值时,会执行Property过程。过程中可以编写代码,对设置的值进行检查,控制其在一个规定的范围中。Property过程有以下3种形式:- Property Let:这类过程用来设置类模块的属性值。
- Property Get:这类过程用来读取类模块的属性值。
- Property Set:这类过程用来设置对对象的引用。
在使用Property过程设置属性值时,类模块中的变量可声明为Private类型,避免过程直接修改类模块中变量的值。
每个Property Get的过程声明行都需要指定属性的名称和数据类型
Public public_var as long ' 可以在类模块顶部通过public声明一个属性,不过该属性可以在其他模块直接修改 Private m_age As Byte ' 定义年龄属性 Private m_sex As String ' 定义性别属性 Private m_name As String ' 定义姓名属性 Private m_weight As Long ' 定义体重属性 Property Get age() as Byte age = m_age ' 返回年龄值 End Property Property Let weight(m_weight As Long) ' let过程必须带一个变量参数赋值 weight = m_weight ' 设置体重属性 End Property ' Exit Propert语句可以结束Propert过程 Property Let age(a as long) If a >= 0 And a <= 100 Then ' 设置范围,变量为数值,且在0-100之间 m_age = a Else MsgBox "您设置的年龄无效,请输入0-100的数字" Exit Property ' 如果变量无效则结束property过程 End If End Property
类的方法
同过程、函数,直接在类模块里写函数、过程,即为类的方法
Sub class_method(str as String) MsgBox "在类里面直接写过程过函数,即为类方法" MsgBox "向class_method方法里传入参数:" & str End Sub ' 当然也可以写函数,跟模块里写函数是一样的 Public Function func(num As Integer) MsgBox num End Function
类的事件
同工作表,工作簿一样,类也有事件
- Initialize事件:实例初次创建时触发
- Terminate事件:最后一个指针释放或破坏时触发
可以用Initialize事件设置对象类的默认属性值;用Terminate事件进行销毁对象前的整理工作。
在类模块下拉列表选择Class,右侧选择对应的事件。
Private Sub Class_Initialize() m_weight = 60 ' 初始化m_weight变量 End Sub
类的实例调用
现在我们写了这样一个类模块
Public var As Long Private m_age As Byte ' 定义年龄属性 Private m_sex As String ' 定义性别属性 Private m_name As String ' 定义姓名属性 Private m_weight As Long ' 定义体重属性 Private Sub Class_Initialize() m_weight = 66 ' 初始化m_weight变量 End Sub Property Let weight(w As Long) m_weight = w ' 设置weight属性 End Property Property Get weight() As Long weight = m_weight ' 读取weight属性 End Property Sub class_method() MsgBox "在类里面直接写过程过函数,即为类方法" End Sub Public Function func(num As Integer) MsgBox num End Function Property Let age(a As byte) If a >= 0 And a <= 100 Then ' 设置范围,变量为数值,且在0-100之IsNumeric(a) And间 m_age = a Else MsgBox "您设置的年龄无效,请输入0-100的数字" Exit Property ' 如果变量无效则结束property过程 End If End Property Property Get age() As Byte age = m_age ' 读取年龄属性 End Property
我们插入一个模块,创建类实例调用
Sub test1() Dim m As New Person m.weight = 171 '设置体重属性 MsgBox m.weight m.class_method ' 调用类方法 m.func (111) ' 调用类函数 m.age = 190 m.age = 27 Debug.Print m.age End Sub
-
VBA--类模块学习
2019-05-30 13:52:18不废话,从基础开始学VBA的类模块。 主要分下面几个部分: 1.什么是类 2.为什么要学习类 3.类的预备知识 4.创建类属性 5.创建类方法 6.创建类事件 7.一个完整的类实例 8.未完的结尾 什么是类 在我们身边...不废话,从基础开始学VBA的类模块。
主要分下面几个部分:
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:
此主题相关图片如下:在通用框选择app后,左边的声明框便会显示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显示
(三)未尽之处自由发挥,假定用户操作规范,可不考虑错误处理。 -
VB类模块的使用
2020-03-15 21:59:26一、创建类模块及类的定义 1.1 打开Excel中 开发工具“ Visual Basic” 如下图 1.2 在VB工程中添加类模块 1.3 类模块的定义 代码如下:定义了三个属性,Let赋值,Get取值 Private pmethod As String Private ... -
Excel宏(VBA)类模块的创建与调用
2020-12-23 22:05:29Excel宏VBA类模块的创建与调用1 类模块的介绍2 为什么要使用类模块3 如何创建类模块4 如何调用类模块5 代码 1 类模块的介绍 2 为什么要使用类模块 3 如何创建类模块 类模块的新建只需右键选择插入,类模块即可 ... -
VBA中窗体模块、标准模块和类模块的区别
2019-03-04 22:05:26VBA中窗体模块、标准模块和类模块的区别 -
VB中的类模块
2019-02-18 14:21:22VB中的类模块 -
511遇见易语言注册调用乐玩插件类模块封装
2020-08-04 09:03:41第一课实现了易语言对乐玩插件的调用,我们以类模块,对象的方式封装,支持静态编译。教程录制时采用了最新的8.16 教程源码: .版本 2 .程序集 lwcom, , 公开 .程序集变量 obj, 对象 .子程序 _初始化, , , 当... -
VBA类模块初步
2011-11-17 10:36:12这里简单地介绍VBA中的类模块,使大家能够在应用程序中创建并使用简单的类。 类是对象的“模板”。对象可以是任何事物,而类不会做任何事情,也不会占用内存,只有当类成为对象并使用Set语句和New关键字实例化为... -
python导入模块中类的方法
2019-05-05 23:18:281、导入模块中的单类 》创建模块 #file:person.py class Person(): def __init__(self): print("I'm a person!") def name(self): print("I need a name!") def age(self): print("I have ... -
VB中窗体模块、标准模块、类模块的区别
2009-10-16 09:36:00在VB中提供了三种类型的模块:窗体模块、标准模块和类模块。 简单的应用程序可以只有一个窗体,所用的程序都驻留在窗体模块中,而当应用程序庞大复杂时,就要另外附加窗体。最终可能有几个窗体中有一些共同都要执行... -
在VB的类模块中使用定时器
2009-05-31 23:31:00长久以来,由于不能直接获得VB类成员函数指针,因为无法在VB的类模块中直接使用定时器控件或定时器API,基于俺编写的获得类成员函数指针的函数,俺编写了这个带定时器功能的类,希望给朋友们一些启发。 一、新建一... -
易语言如何引用模块和模块中的类
2018-08-10 23:26:071,打开易语言,新建一个“windows窗口程序”,工作夹→程序→双击“模块引用表”,选择欲加入的易模块文件,打开即可。 2,双击模块,可看到易模块公开信息。...以大漠模块为例,模块中包含很多类。 “类... -
Python 的类、模块、包的区别
2018-04-13 10:04:50个人理解:类是一个文件的一段代码;模块(module)是一个文件;包是多个文件,也可以说是多个模块,调用包需要加__init__.py文件,此文件可以是空,也可以有代码;包目录下为首的一个文件便是 __init__.py。然后是... -
python中对象,类,函数,方法,包,库和模块等区别
2020-10-17 07:53:54一,对象和类 Python是面向对象程序设计(Object Oriented Programming, OOP),其思想主要针对大型软件设计而提出。 其软件设计更加灵活,能够很好地支持代码复用和设计复用。 并且使得代码具有更好的可读性和可扩展性... -
Excel VBA之类模块 实例
2007-04-20 21:00:00怎么建立类模块:建立类模块的方法和建立模块的方法是一样的,只是选择建立的项目是类模块。 上面就是现在我对类模块的了解,不多,甚至自己也有很多不理解的地方,在类模块的使用方面,记忆里曾经在学VB时编一个... -
易语言创建大漠模块及免注册调用大漠
2020-06-14 11:01:141、什么是模块? 易语言模块相当于支持库其中可以包括子程序、常量、自定义数据类型,也相当于在程序中一些自己写的子程序、定义的数据类型和常量,可以在程序中直接调用,但调用时必须遵守模块使用的要求(比如:参数... -
VB.NET中的类和模块
2015-12-20 10:13:52但是在这之中出现了一个问题,那就是好像C#中只涉及到了各种类的使用,没有用到模块(module),但是在VB中要调用一个类中的字符串的时候却是出现了不能调用的问题,于是把类改为了模块就可以直接调用了,这又是为... -
VB 中的类模块与模块之一
2005-12-28 15:43:00类模块是扩展的Type,除了属性外还有方法事件而模块可以有全局(Public)变量、全局(API、常数、类型)申明、过程可将那些与特定窗体或控件无关的代码放入另一类型的模块— 标准模块(文件扩展名为 .BAS )中。... -
python模块的调用方法
2020-03-08 22:30:11python模块的调用方法 # 两种方法:以`numpy`module(模块)中的`array()`方法为例 1. import numpy 2. from numpy import array -
通用类App的模块划分
2016-07-22 02:41:29帖子列表、评论列表类大批量数据采用了File文件存储,原因是操作方便,只需要序列化和反序列化操作就能很方便地读出缓存并显示,这里要注意下你的bean类需要继承序列化接口。 4. 网络层 加载 全部是自己基于 ... -
VB 中的类模块与模块之二
2005-12-28 15:47:00Q: visual basic 6 windows98 请问在vb中的‘模块’、‘类模块’是什么?有何好处?A: 模块是.bas文件,主要用于将代码分别存放,便于管理。 类模块是.cls文件,主要用于定义类和设计ActiveX EXE、ActiveX DLL... -
App模块组成
2018-12-20 12:39:37最为一名工作了不短时间的iOS开发者,对一个完整的App所包含的功能模块有一点自己的认识。这里进行一个总结。 全局宏定义 打印函数、屏幕尺寸、weakself、iOS版本号、单例、去除警告等宏定义,配合预编译文件使用... -
TypeScript-模块化篇
2020-07-26 10:28:22这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的, 除非你明确地使用export形式之一导出它们。 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候, 你必须要导入它们,可以使用 ... -
易语言大漠多线程模块制作及多线程调用实战
2020-05-27 11:08:08首先是多线程模块的多种制作方法和调用方法,其次每种多线程模块都实现免注册到系统调用,和免杀软的查杀,更重要的是示范了多种多线程模块的调用方法。 以下是所有视频的链接: 全部教程播单:... -
python调用自定义模块
2017-02-08 15:10:24自定义的python模块有两种,一种是把模块写成类,类里面有要调用的方法。另一种写成单独的py文件,没有类,里面全是函数。(有类才叫方法,没有类叫函数),也是所谓的面向过程的函数。 第一种调用如下: 例如在b.... -
vb自定义类模块
2011-09-16 18:57:21类模块儿和一般常用的控件一样都是一种对象,具有事件、属性等性质。因此学会创建类模块儿对象,在编程中是非常重要的。 下面的自定义模块儿实现: 增加一个text属性;并自动验证前后两次字符串变量是否一致的功能...