VBA类之一(初识类)
第一章 开头篇
——认识类
Visual Basic是基于对象的编程(注:本文所有的代码和讨论将都以VB为基础模型,不过我会尽量使用一些大家在VBA中常见的例子来做说明的。),所以我们常见的一些东西其实都与类有关。不需惊讶,是的,类其实离我们很近,它们正和我们天天相处。可以夸张的说,如果离开了类,我们的VB(VBA)就无用武之地了。
我们还是先来看一条非常简单的语句,让我们亲身体会一下类与我们的距离:
- '例一
- Sub Hello()
- Range("A1").Value = "Hello,world"
- End Sub
复制代码
我们就暂且称之为VBA的Hello world吧,看到这个简单到不能再简单的例子,或许你笑了,这个根本就和类没有关系嘛。是的,我们在这里并没有明显的看到类,但是我们看到了Range("A1")这个对象,而在Range("A1")前面,还隐藏着一个对象:ActiveSheet,而在ActiveSheet前面又有一个ActiveWorkbook,而在ActiveWorkbook前面还有一个Application。是的,这些都是对象,我们依旧没有看到类。先别急,我们再从后面往前面看一遍:Range("A1").Value="Hello,World" Value是什么?Value是一个Range("A1")这个对象的一个属性。那它是从哪来的呢?它是由Range类定义的,是的,我们发现了第一个出现在我们的面前的类 Range。你或许一下还接受不了,你刚刚在前不是还说Range("A1")是一个对象吗?怎么现在又说Range又是一个类呢。呵呵,不着急,我们还是看看下面的这个例子,或许你就很快就会明白了。
- '例二
- Sub Hello()
- Dim Range1 As Range '引用一个Range类
- Set Range1 = Range("A1") '将类实例化
- Range1.Value = "Hello,world" '这时,Range1又是一个对象了
- Set Range1 = Nothing '销毁一个类的实例
- End Sub
复制代码
或许你现在已经发现了,我们引用了一个Range类,并将之实例化后修改了它的属性。而在例一中,我们只不过是将这一切都以隐藏起来,直接对一个对象Range("A1")修改它的属性,但Range("A1")这个对象正是引用了Ragne这个类,才具有了Range类的属性"Value"。类本身并不直接为我们做什么,但是,它却又一直默默的隐藏在幕后规化着我们的动作。是的,这就是类,它往往都是通过对象的方式展现在我们的面前,让我们无时无刻与之交流,却又常常在不经意间忽视了它的存在。现在再回到例一去看,就会很容易的发现,ActiveSheet实际引用了Worksheet类、ActiveWorkbook引用了Workbook类,而Application则引用了和它同名的Application类(这也正是我们会经常被混淆一个概念,一个对象可以和被它所引用的类同名),原来我们在短短一个赋值的语句中,已经在与这么多的类打交道。
类就像是我们呼吸的空气,我们一直深在其中,却又常常忘却了它的存在。那我们又要怎样来区分类和对象呢?其实它们经常成对的出现在我们面前,只是一个看得见摸得着的,一个却深藏不露。我们可以这样去理解类与对象:类是一个概念或是一种定义,每个类拥有其自己的特征和行为方式,而对象就是某个类的实例。就像"人","人"是一个定义,你一看到"人"就会想到它是直立行走、有头、五官……等各种特征,但当"人"被具体到你、我、或是某一个人的时候,它就是一个对象了。
既然类与我们和程序息息相关,那么我们更应该去好好的了解它了。让我们准备好行囊,到神秘的类的领地去好好浏览一番吧。
第二篇 走近类
——类的基本写法及注意项
如果说上一篇让我们看到类隐藏在神秘面纱后的面孔,那么现在我们将要做的就是真正的看清楚它。不过,在此之前我还是要让大家再次熟悉一下面的几个关键词:
Public 外部,可供类或是模块自身外调用;
Private 内部,只能由类或是模块内部直接调用;
Friend 友元,只能由工程内调用(即:在当前工程内部调用时相当于Public,而对外部工程相当于Private。)。
Sub、Function 过程(不返回值)和函数(返回值),在类中我们可将其视为类的方法;
Property Get 返回属性的值;
Property Let 设置属性的值;
Property Set 设置对象属性的值(即:该属性含有对象引用)。
[注:Property往往是成双出现在类中的某一个属性上,甚至可能三者同时出现,当一个类中某个属性只有Property Get时代表这一属性为只读属性,比如在VB工程中Form类的hWnd属性。]
Event 定义用户自定义的事件
[注:Event可以像声明过程的参数一样来声明事件的参数,但有以下不同:事件不能有带命名参数、Optional 参数、或者 ParamArray参数。事件没有返回值。];
RaiseEvent 引发在一个类、窗体、或者文档中的模块级中声明的一个事件。
Implements 指定要在包含该语句的类模块中实现的接口或类。
当我们熟悉上面的几个关键词后,再来分析类,就会发现类其实也挺简单。我们将从一个简单的类开始来正式的学习类的定义和使用方法。不过在此之前,我建议大家先去读读qee用兄写的一个帖子:VBA类:隐者的秘密,qee用兄在那个帖子中图文并茂,已经将类清晰的描绘出来,并亲自带领我们写了一个类。在此,让我们向qee用兄道声辛苦,并送上万分感激。好了,现在,我们来写一个非常简单的类,用它来实现两个数的四则运算。
- '类的名称定义为:四则运算
- Option Explicit
- Public Event OnError(ByVal Number As Long, ByVal Description As String, ByVal Source As String)
- Private lNum1 As Double, lNum2 As Double
- Public Property Let Number1(ByVal Number As Double)
- lNum1 = Number
- End Property
- Public Property Get Number1() As Double
- Number1 = lNum1
- End Property
- Public Property Let Number2(ByVal Number As Double)
- lNum2 = Number
- End Property
- Public Property Get Number2() As Double
- Number2 = lNum2
- End Property
- Public Property Get 和() As Double
- On Error GoTo Error01
- 和 = lNum1 + lNum2
- Exit Property
- Error01:
- RaiseEvent OnError(Err.Number, Err.Description, Err.Source)
- Err.Clear
- 和 = 0
- End Property
- Public Property Get 差() As Double
- On Error GoTo Error02
- 差 = lNum1 - lNum2
- Exit Property
- Error02:
- RaiseEvent OnError(Err.Number, Err.Description, Err.Source)
- Err.Clear
- 差 = 0
- End Property
- Public Property Get 积() As Double
- On Error GoTo Error03
- 积 = lNum1 * lNum2
- Exit Property
- Error03:
- RaiseEvent OnError(Err.Number, Err.Description, Err.Source)
- Err.Clear
- 积 = 0
- End Property
- Public Property Get 商() As Double
- On Error GoTo Error04
- 商 = lNum1 / lNum2
- Exit Property
- Error04:
- RaiseEvent OnError(Err.Number, Err.Description, Err.Source)
- Err.Clear
- 商 = 0
- End Property
- Public Sub ClearNumber()
- lNum1 = 0: lNum2 = 0
- End Sub
复制代码
然后,将下面的代码写到ThisWorkbook模块中:
- Option Explicit
- Private WithEvents Class1 As 四则运算
- Sub TestClass()
- If Class1 Is Nothing Then Set Class1 = New 四则运算
- Class1.ClearNumber
- Class1.Number1 = 9
- ' Class1.Number2 = 8
- Debug.Print Class1.和
- Debug.Print Class1.差
- Debug.Print Class1.积
- Debug.Print Class1.商
- End Sub
- Private Sub Class1_OnError(ByVal Number As Long, ByVal Description As String, ByVal Source As String)
- MsgBox "类中发生错误,错误代码:" & Number & " 错误提示:" & Description & " 错误源:" & Source
- End Sub
复制代码
这是一个非常简单的类的定义和应用(当然,实际编程时很少有人会为了四则运算而专门写个类。),虽然它很简单,不过它却几乎包含了类所有常见的特征:属性、方法、事件。运行TestClass后我们可以看到立即窗口中的输出,并接收到一个MsgBox的窗体,它提示了我们在类运行过程中产生了一个除数为零的错误,而这正是类中的OnError事件激发的。
虽然这个类本身没有什么实际的应用的意义,但是我们却可以用它来做一个模版,我们只要参照这个类,就可以很快的写出自己想要的类。
你现在是不是已经有了自己马上要编写一个类的冲动呢?那么我们开始准备动手吧,不过在你动手之前,我这里还要给你几点关于类的建议(这些都是笔者自身积累的经验或是前辈们的忠告。):
一、在正式编写一个类的前期,最好尽可能规定好所有要用到的接口与方法。一旦一个类被正式发行后(封装成DLL并被其它的工程引用),后期维护时尽量不要再去修改已有的接口函数及其参数,除非你打算将所有的工程全部重写;
二、为你类中的属性、方法、事件取个有意义的名字(特别是声明为Public方式的),如果允许的话,最好使用“工具”->“过程属性”为其添加一些描叙,说明它的意义或是调用方式,这样可以方便查看其属性并让客户(甚至是自己)能很快明白这个函数的用途;
三、只暴露必须的接口供外部调用,不要将一些可能仅在类内部使用属性和方法暴露给类外部;
四、不到万不得已,不要在类中定义Public方式的变量;
五、重新编译新版本的DLL时,最好是按“版本相同、版本兼容、版本不兼容”这个次序来选择编译后DLL版本兼容性,即:能用“版本相同”方式就不要用“版本兼容”,更不要用“版本不兼容”,因为“版本不兼容”方式就意味着你的旧工程在其重新编译前将无法使用这个DLL。一旦准备使用无兼容性版本,那么就要考虑如何减少部件的用户以后可能遇到的麻烦。如果以后的版本还要做一些可能破坏兼容性的修改,那么最好把这些修改集中在一次进行。计划对不兼容进行的修改时,把工程作为一个新的任务来对待,应该投入尽可能多的精力进行计划,就象在创建新的部件。创建不兼容版本,这要求三个步骤:改变工程名,改变文件名以及通过选定不兼容进行编译(注:这三个步骤最好是一定要做到,否则将会让我们在后面安装引用此类时会发生各种问题而导致我们无法使用类);
六、可能的话,不要忘了在类内部添加错误处理过程,尽管这些错误处理过程可能会损耗我们的运行时间,但它们能让我们的类更健壮;
七、类中不能定义Public方式的结构(Type),如果必需要这么做的话,可以使用一个类来代替Type;
八、在类的Class_Terminate()事件中释放被当前类所引用的类或是数据。当我们某个类的实例已经不再需要时,请将其使用 Set[I]Object[/I] = Nothing 的方式来销毁它;
九、……(留给大家填吧)……
第三篇 为什么要使用类
——类的作用及其重要性
现在,我们已经初步的认识了类,甚至我们已经可以自己开始写自己的类了。但是我们却还不是很清楚,类给我们带来了什么?我们为什么要用类?我们就先来看看类的好处吧。
1、代码重用;
2、降低程序的藕合度;
3、增强程序的可拓展性;
4、易修改性;
5、……
“藕合度”指的是程序模块间存在联系的紧密程度。如果一个程序各模块存在的联系太紧密时,就意味后期的修改将会非常复杂,甚至于有时为了修改某一个代码功能时可能要全部工程文件都修改一次,并且由于各模块间的函数与变量相互相交调用,严重的影响了程序的可读性。自然,我们不希望发生这种情况。而类正是我们规避这个情况发生的首选工具。因为类对于外部而言,我们所需要的仅仅是调用类的接口函数,而类内部的结构与运行我们并不需要去关心它。就像一个用来处理数据的类,我们只要约定好类的接口,就可以在外部直接获取或是修改我们想要的数据。但是数据存在哪,用什么方式或是结构组成我们根本就不需要了解,我们所要做的只是在外部直接调用就好了。这就意味着我们一旦约定好了接口,就可以同时开始开发前台与后台的数据处理块。而且,当我们需要修改数据的存储方式时,也会变得更加简单。比如:我们现有一个处理数据的类,它目前是使用文件来存储数据,现在我们需要把数据存储改为SQL数据库,这时我们要做的仅仅是在类内部修改要数据的处理过程,而对于整个工程而言,我们甚至不用做任何的修改就可以直接用了。这样对于大型工程而言,犹为重要,因为我们常常把类封装成DLL,这就意味我们仅仅只需要重新编译这个DLL,然后发布给用户升级就完成了这次修改,否则的话我们只能将整个工程全部修改后重新发布给用户才能使用。如果你经常使用VBA的话就一定会常常遇见Range这个类,Office每一次大版本升级,其内部的文件结构都会有变动,但是我们在VBA中对Range的操作却无需更改就可以直接使用了,这个正是因为Range类虽然内部改动了,但是它的接口还是一样,所以我们原有的VBA工程并不需要改动就可以直接使用了。
现在我们再来通过一个选中区域高亮显示的例子来看看类的其它几个特性吧:
- Option Explicit
- Private Type TypeRageLast
- Range As Range
- ColorIndex As Long
- End Type
- Private Const ColorIndex = 5&
- Private RangeLast() As TypeRageLast
- Private Sub Worksheet_SelectionChange(ByVal Target As Range)
- Dim I As Long
- Dim Range1 As Range
- On Error Resume Next
- If UBound(RangeLast) = -1 Then
- ReDim RangeLast(0)
- ElseIf UBound(RangeLast) Then
- For I = 1 To UBound(RangeLast)
- If RangeLast(I).Range.Interior.ColorIndex = ColorIndex Then
- RangeLast(I).Range.Interior.ColorIndex = RangeLast(I).ColorIndex
- End If
- Next I
- End If
- I = 0
- ReDim RangeLast(0)
- For Each Range1 In Target
- I = I + 1: ReDim Preserve RangeLast(I)
- RangeLast(I).ColorIndex = Range1.Interior.ColorIndex
- Set RangeLast(I).Range = Range1
- Next Range1
- Target.Interior.ColorIndex = ColorIndex
- End Sub
复制代码
这段代码的功能很简单(我们在这里并不去讨论它的功能),只是在工作表中,选中某个区域后就把该区域的背景色设置为并且恢复上次被选中区域的原有背景色。我们只要把上面的代码拷贝到我们需要的工作表的代码中就可以实现这个功能了。但是,当我在实际使用过程中就会发现一些新的问题:
1、如果我们的工作簿中有200个工作表需要这个功能,我们就要复制200次——晕倒……
2、复制完200次后,我们再在各个工作表中实际测试一下。就会发现新的问题:当我们在各个工作表之间切换焦点时,原工作表中最后一次被高亮的区域没有被还原!现在我们需要修改代码——在每一个工作表中Deactivate事件中加上相应处理代码。天啊,又得200次……又要晕了吧?
3、文档开始正式使用了,但是当我们新增一个工作表后发现:我们又得重新切换到VBA中再复制一次代码!如果这个文档是给客户使用的,难道我们还要要求客户每次增加工作表时自己去复制VBA代码?……这次彻底晕了吧?^_^
难道就没有更方便的方法吗?
当然有了。我们就用类来试试吧,我们先建一个clsSelHeightColor类:
- ‘类名:clsSelHeightColor
- Option Explicit
- Private Type TypeRageLast
- Range As Range
- ColorIndex As Long
- End Type
- Private Const ColorIndex = 5&
- Private WithEvents mSheet As Worksheet
- Private WithEvents Workbook As Workbook
- Private RangeLast() As TypeRageLast
- Private Sub mSheet_Activate()
- Dim I As Long
- Dim Range1 As Range
- On Error Resume Next
- I = 0
- ReDim RangeLast(0)
- For Each Range1 In Selection
- I = I + 1: ReDim Preserve RangeLast(I)
- RangeLast(I).ColorIndex = Range1.Interior.ColorIndex
- Set RangeLast(I).Range = Range1
- Range1.Interior.ColorIndex = ColorIndex
- Next Range1
- End Sub
- Private Sub mSheet_Deactivate()
- mSheet_SelectionChange Nothing
- End Sub
- Private Sub mSheet_SelectionChange(ByVal Target As Range)
- Dim I As Long
- Dim Range1 As Range
- On Error Resume Next
- If UBound(RangeLast) Then
- For I = 1 To UBound(RangeLast)
- If RangeLast(I).Range.Interior.ColorIndex = ColorIndex Then
- RangeLast(I).Range.Interior.ColorIndex = RangeLast(I).ColorIndex
- End If
- Next I
- End If
- I = 0
- ReDim RangeLast(0)
- For Each Range1 In Target
- I = I + 1: ReDim Preserve RangeLast(I)
- RangeLast(I).ColorIndex = Range1.Interior.ColorIndex
- Set RangeLast(I).Range = Range1
- Next Range1
- Target.Interior.ColorIndex = ColorIndex
- End Sub
- Public Property Set Sheet(ByVal mSheet1 As Worksheet)
- If Not (mSheet Is Nothing) Then mSheet_SelectionChange Nothing
- Set mSheet = mSheet1
- Call mSheet_Activate
- End Property
- Public Property Get Sheet() As Worksheet
- Set Sheet = mSheet
- End Property
- Private Sub Class_Initialize()
- ReDim RangeLast(0)
- End Sub
- Private Sub Class_Terminate()
- mSheet_SelectionChange Nothing
- Erase RangeLast
- End Sub
复制代码
然后再把下面的代码复制到ThisWorkbook中的代码框中:
- Option Explicit
- Dim mSheets As Collection
- Private Sub Workbook_BeforeClose(Cancel As Boolean)
- Set mSheets = Nothing
- End Sub
- Private Sub Workbook_NewSheet(ByVal Sh As Object)
- Dim clsSheet As New clsSelHeightColor
- Set clsSheet.Sheet = Sh
- mSheets.Add clsSheet
- Set clsSheet = Nothing
- End Sub
- Private Sub WorkBook_Open()
- Dim clsSheet As clsSelHeightColor
- Dim Sheet As Worksheet
- On Error Resume Next
- Set mSheets = New Collection
- For Each Sheet In Sheets
- Set clsSheet = New clsSelHeightColor
- Set clsSheet.Sheet = Sheet
- mSheets.Add clsSheet
- Set clsSheet = Nothing
- Next
- End Sub
复制代码
保存后重开工作簿测试……嘿嘿,全部搞定!而且当我们要增加的Deactivate事件中的代码仅仅只需类中添加一次,只有一行代码(上面的类中已经添加好了),方便吧?当我们新增工作表时,也不再需要去复制一次代码了,一行代码都不用写了就已经实现了这个功能。当其它工作簿也需要这个代码时,我们只要在ThisWorkbook代码中增加相应的代码,并导入这个类就行了,比一个一个表去复制代码方便多——一想到相同的代码要在每个工作表中复制一次就头晕了。
在此,我们对类的功能和特性有了一定的了解,不过,还有更多的东西在等待我们去发现。当下面向对象编程如此风行,类在其中的功劳是无与伦比的。
现在,还犹豫什么?快去和类做一次“0距离接触”吧……
在此,此文也终于要作一个小小的终结了。大家也跟随我初步对类作了点了解,也算是对类有了个第一印象吧。当然,类的高级应用和复杂型的类我们并没有去了解,或许哪天你会引领我去浏览一番?
我也谢谢大家浪费不少时间来看完这个帖子,如果对我有意见或是觉得我在胡说八道要拍我砖头的,请下手留点情——我很怕疼,有意见扔扔鸡蛋或是RMB就好了。如果觉得看完对你有所帮助的话就帮顶下吧,或是你看完有什么问题的话也可以直接问,只要我会的我一定回(太难的问题还请恕我无能为力)。
VBA类之一(初识类)相关推荐
- 理解 Delphi 的类(四) - 初识类的事件
先勾画一下思路: 1.建立一个类, 里面有年龄字段 FAge; 2.通过 Age 属性读写 FAge; 3.如果输入的年龄刚好是 100 岁, 将会激发一个事件, 这个事件我们给它命名为: OnHun ...
- 初识类(classstruct)及C/C++封装的差异
初识类(class&struct) 面向对象三大特性:封装.继承和多态.其中不得不谈的就是类,通过类创建一个对象的过程叫实例化,实例化后使用对象可以调用类成员函数和成员变量,其中类成员函数称为 ...
- 初识类的构造方法 c# 1214
初识类的构造方法 c# 1214 实例化时自动运行的方法 方法名与类名相同 私有化这个方法 private 私有化这个方法,实例化时就会报错 公有方法操做私有方法 在类的内部提供一个公有方法 让公有方 ...
- C++ 初识类与对象
C++是一门面向对象的编程语言,那类与对象也就是C++的核心部分,现在让我们一起来学习类与对象吧. 初识类与对象 类的定义 类的限定符 封装 类的实例化 类对象模型 类的大小 this指针 类的定义 ...
- python定义一个dog类 类属性有名字_python 基础 12 初识类,类方法,类属性
python 基础 12 初识类,类方法,类属性 # 面向过程 : 想要一个结果 写代码 实现计算结果 # 面向对象开发 : 有哪些角色 角色的属性和技能 两个角色之间是如何交互的 # 复杂的 拥有开 ...
- Excel VBA(09)类模块和数据库操作
类模块详解 一.类模块入门 vba 中的类模块的概念和其他面向对象的语言里面的类的概念是类似的,就是抽取共性进行封装以便能够重复使用 类模块的插入 类模块的相关语句如下 1.let:设置对象属性 2. ...
- java基础教程 类_Java零基础系列教程06初识类和对象
配套视频教程 万物皆对象 分类是人们认识世界的一个很自然的过程,在日常生活中会不自觉地进行分类 身边的对象 顾客 姓名-张浩 年龄-20 体重-60kg 操作: 购买商品 收银员 员工号-10001 ...
- 【C++】C++类的学习(一)——初识类
[fishing-pan:https://blog.csdn.net/u013921430转载请注明出处] 前言 C++在C语言的基础上做了一些改进,使得C++具有了面向对象编程(Object Ori ...
- C++入门>>初识类
文章目录 前言: 认识面向过程与面向对象 封装 浅谈C的结构体与C++类 类与对象–上篇 class的引入 类的定义 类中的限定符 类的实例化 如何计算类的大小 隐形的this指针!!!!非常重要 t ...
最新文章
- Apache的配置详解
- 十位数和个位数交换python_Python实现100以内十位数数字比个位数数字小的数
- MAC OSX在视图port哪个程序占用,杀死进程的方法
- Exception in thread main java.lang.NoClassDefFoundError
- Arturia Analog Lab for Mac - 原始混合合成器
- Linux深度系统卡在开机界面,deepin开机卡死在logo页面解决方案
- Filenet路线更新及新版官网上线公告 ​
- 杭州电子科技大学acm--2017
- MOOC北京理工《C语言程序设计(上)》第4周第2题:确定母亲节
- 使用idea运行CS项目后“客户端页面加载不出来“的解决方法
- mysql localhost值_jdbc:mysql://localhost:3306/mysql这句话中localhost具体指什么的localhost?能修改么?在哪里配置的?...
- Java根据图片提取文字
- Tomcat传url地址中的特殊字符无法识别问题
- 嵌入式软件测试(黑盒测试)-----三年嵌入式软件测试的理解
- “奔驰车主哭诉维权”续:双方再次协商无果
- 2022企业人效管理白皮书
- 按键精灵 容器 html,按键精灵容器控件
- Jsp_Servlet_JavaBean_Chapter01课堂笔记
- 计算机桌面最下边的横条叫做,在屏幕下方那个蓝色的横条并且带有开始的那个地方叫什么?...
- 被通知一个月后离职,我改了重要项目里的代码注释
热门文章
- Loj #6307. 「雅礼国庆 2017 Day1」Clique
- BZOJ2720: [Violet 5]列队春游
- daatable动态创建
- POJ 2485 Highways(最小生成树 Prim)
- SystemVerilog例子---traffic light
- 久等了,41连开班仪式!
- 301. Remove Invalid Parentheses
- 什么是压缩感知?[简单概括]
- OpenCV函数cvFindContours
- 如图所示是一种轧钢计算机控制系统,高速线材厂轧钢工艺培训(活套)